From 08a23114c998f3b8d0cc65ddfe198651c4314285 Mon Sep 17 00:00:00 2001 From: Mike Phares Date: Tue, 28 Jan 2025 13:29:28 -0700 Subject: [PATCH] Weighted Shortest Job First Hub --- .../DownloadWorkItems/FileRead.cs | 4 +- Adaptation/FileHandlers/Dummy/FileRead.cs | 6 +- Adaptation/FileHandlers/Kanban/ProcessData.cs | 8 +- .../FileHandlers/Priority/Aggregation.cs | 206 ++++++++++++++-- Adaptation/FileHandlers/Priority/FileRead.cs | 224 +++--------------- .../FileHandlers/Priority/Notification.cs | 82 +++++++ Adaptation/FileHandlers/Priority/Record.cs | 37 --- Adaptation/FileHandlers/Priority/Settings.cs | 43 ++++ Adaptation/FileHandlers/Priority/Startup.cs | 13 + .../Priority/WeightedShortestJobFirstHub.cs | 158 ++++++++++++ Adaptation/FileHandlers/Priority/WorkItem.cs | 205 ++++++++++++++++ Adaptation/MESAFIBACKLOG.Tests.csproj | 45 +++- .../Development/v2.58.0/MESAFIBACKLOG.cs | 13 + .../Development/v2.58.0/MESAFIBACKLOG.cs | 33 ++- MESAFIBACKLOG.csproj | 27 ++- 15 files changed, 834 insertions(+), 270 deletions(-) create mode 100644 Adaptation/FileHandlers/Priority/Notification.cs delete mode 100644 Adaptation/FileHandlers/Priority/Record.cs create mode 100644 Adaptation/FileHandlers/Priority/Settings.cs create mode 100644 Adaptation/FileHandlers/Priority/Startup.cs create mode 100644 Adaptation/FileHandlers/Priority/WeightedShortestJobFirstHub.cs create mode 100644 Adaptation/FileHandlers/Priority/WorkItem.cs diff --git a/Adaptation/FileHandlers/DownloadWorkItems/FileRead.cs b/Adaptation/FileHandlers/DownloadWorkItems/FileRead.cs index 991cf42..f222454 100644 --- a/Adaptation/FileHandlers/DownloadWorkItems/FileRead.cs +++ b/Adaptation/FileHandlers/DownloadWorkItems/FileRead.cs @@ -224,7 +224,7 @@ public class FileRead : Shared.FileRead, IFileRead string body = string.Concat(exception.Message, Environment.NewLine, Environment.NewLine, exception.StackTrace); try { - _SMTP.SendHighPriorityEmailMessage(subject, body); + _SMTP.SendHighPriorityEmailMessage(subject, body); File.WriteAllText(".email", body); } catch (Exception) { } @@ -242,7 +242,7 @@ public class FileRead : Shared.FileRead, IFileRead string body = string.Concat(exception.Message, Environment.NewLine, Environment.NewLine, exception.StackTrace); try { - _SMTP.SendHighPriorityEmailMessage(subject, body); + _SMTP.SendHighPriorityEmailMessage(subject, body); File.WriteAllText(".email", body); } catch (Exception) { } diff --git a/Adaptation/FileHandlers/Dummy/FileRead.cs b/Adaptation/FileHandlers/Dummy/FileRead.cs index 530c308..dd68bcb 100644 --- a/Adaptation/FileHandlers/Dummy/FileRead.cs +++ b/Adaptation/FileHandlers/Dummy/FileRead.cs @@ -138,7 +138,7 @@ public class FileRead : Shared.FileRead, IFileRead string body = string.Concat(exception.Message, Environment.NewLine, Environment.NewLine, exception.StackTrace); try { - _SMTP.SendHighPriorityEmailMessage(subject, body); + _SMTP.SendHighPriorityEmailMessage(subject, body); File.WriteAllText(".email", body); } catch (Exception) { } @@ -269,7 +269,7 @@ public class FileRead : Shared.FileRead, IFileRead string body = string.Concat(exception.Message, Environment.NewLine, Environment.NewLine, exception.StackTrace); try { - _SMTP.SendHighPriorityEmailMessage(subject, body); + _SMTP.SendHighPriorityEmailMessage(subject, body); File.WriteAllText(".email", body); } catch (Exception) { } @@ -285,7 +285,7 @@ public class FileRead : Shared.FileRead, IFileRead string body = string.Concat(exception.Message, Environment.NewLine, Environment.NewLine, exception.StackTrace); try { - _SMTP.SendHighPriorityEmailMessage(subject, body); + _SMTP.SendHighPriorityEmailMessage(subject, body); File.WriteAllText(".email", body); } catch (Exception) { } diff --git a/Adaptation/FileHandlers/Kanban/ProcessData.cs b/Adaptation/FileHandlers/Kanban/ProcessData.cs index 7675bf1..08903a2 100644 --- a/Adaptation/FileHandlers/Kanban/ProcessData.cs +++ b/Adaptation/FileHandlers/Kanban/ProcessData.cs @@ -110,6 +110,8 @@ public class ProcessData : IProcessData private static FileInfo GetFileInfoAndMaybeWriteFile(string directory, WorkItem workItem) { FileInfo result; + if (!Directory.Exists(directory)) + _ = Directory.CreateDirectory(directory); string json = JsonSerializer.Serialize(workItem, WorkItemSourceGenerationContext.Default.WorkItem); string singletonDirectory = Path.Combine(directory, $"{workItem.Id}"); if (Directory.Exists(singletonDirectory)) @@ -213,9 +215,13 @@ public class ProcessData : IProcessData { string old; string checkFile; + string checkDirectory; foreach (string iterationPath in distinct) { - checkFile = Path.Combine(destinationDirectory, iterationPath, "[].json"); + checkDirectory = Path.Combine(destinationDirectory, iterationPath); + if (!Directory.Exists(checkDirectory)) + _ = Directory.CreateDirectory(checkDirectory); + checkFile = Path.Combine(checkDirectory, "[].json"); old = File.Exists(checkFile) ? File.ReadAllText(checkFile) : string.Empty; if (old != json) File.WriteAllText(checkFile, json); diff --git a/Adaptation/FileHandlers/Priority/Aggregation.cs b/Adaptation/FileHandlers/Priority/Aggregation.cs index bcd89e0..b172e80 100644 --- a/Adaptation/FileHandlers/Priority/Aggregation.cs +++ b/Adaptation/FileHandlers/Priority/Aggregation.cs @@ -1,38 +1,192 @@ +using System; +using System.Collections.Generic; using System.Collections.ObjectModel; +using System.IO; +using System.Linq; +using System.Text.Json; using System.Text.Json.Serialization; namespace Adaptation.FileHandlers.Priority; +#nullable enable + public class Aggregation { [JsonConstructor] - public Aggregation( - string average, - int count, - int? inverse, - int maximum, - int minimum, - ReadOnlyCollection records, - int sum - ) + public Aggregation(double inverseAverage, + int valueCount, + double fibonacciAverage, + int? inverseValue, + int valueMaximum, + int valueMinimum, + Notification[] notifications, + int valueSum) { - Average = average; - Count = count; - Inverse = inverse; - Maximum = maximum; - Minimum = minimum; - Records = records; - Sum = sum; + InverseAverage = inverseAverage; + ValueCount = valueCount; + FibonacciAverage = fibonacciAverage; + InverseValue = inverseValue; + ValueMaximum = valueMaximum; + ValueMinimum = valueMinimum; + Notifications = notifications; + ValueSum = valueSum; } - [JsonPropertyName("Average")] public string Average { get; } - [JsonPropertyName("Count")] public int Count { get; } - [JsonPropertyName("Inverse")] public int? Inverse { get; } - [JsonPropertyName("Maximum")] public int Maximum { get; } - [JsonPropertyName("Minimum")] public int Minimum { get; } - [JsonPropertyName("Records")] public ReadOnlyCollection Records { get; } - [JsonPropertyName("Sum")] public int Sum { get; } + public double InverseAverage { get; } // [JsonPropertyName("InverseAverage")] + public int ValueCount { get; } // [JsonPropertyName("ValueCount")] + public double FibonacciAverage { get; } // [JsonPropertyName("Fibonacci")] + public int? InverseValue { get; } // [JsonPropertyName("InverseValue")] + public int ValueMaximum { get; } // [JsonPropertyName("ValueMaximum")] + public int ValueMinimum { get; } // [JsonPropertyName("ValueMinimum")] + public Notification[] Notifications { get; } // [JsonPropertyName("Notifications")] + public int ValueSum { get; } // [JsonPropertyName("ValueSum")] + + private static ReadOnlyDictionary GetKeyValuePairs(Settings settings, Dictionary> keyValuePairs) + { + Dictionary results = new(); + int? inverseValue; + double inverseAverage; + Aggregation aggregation; + double fibonacciAverage; + List collection = new(); + int averageFromInverseCeiling; + List inverseCollection = new(); + List fibonacciCollection = new(); + foreach (KeyValuePair> keyValuePair in keyValuePairs) + { + collection.Clear(); + inverseCollection.Clear(); + fibonacciCollection.Clear(); + foreach (Notification notification in keyValuePair.Value) + { + collection.Add(notification.Value); + if (notification.Inverse is null) + continue; + inverseCollection.Add(notification.Inverse.Value); + if (notification.Fibonacci is null) + continue; + fibonacciCollection.Add(notification.Fibonacci.Value); + } + if (inverseCollection.Count == 0 || fibonacciCollection.Count == 0) + continue; + inverseAverage = Math.Round(inverseCollection.Average(), settings.Digits); + averageFromInverseCeiling = (int)Math.Ceiling(inverseAverage); + inverseValue = Notification.GetInverse(averageFromInverseCeiling); + fibonacciAverage = Math.Round(fibonacciCollection.Average(), settings.Digits); + aggregation = new(inverseAverage: inverseAverage, + valueCount: collection.Count, + fibonacciAverage: fibonacciAverage, + inverseValue: inverseValue, + valueMaximum: collection.Max(), + valueMinimum: collection.Min(), + notifications: keyValuePair.Value.ToArray(), + valueSum: collection.Sum()); + results.Add(keyValuePair.Key, aggregation); + } + return new(results); + } + + private static ReadOnlyCollection GetNotifications(Settings settings, string directory) + { + List results = new(); + string text; + string[] files; + Notification? notification; + List? collection; + Dictionary> keyValuePairs = new(); + string[] directories = Directory.GetDirectories(directory, "*", SearchOption.TopDirectoryOnly); + foreach (string subDirectory in directories) + { + keyValuePairs.Clear(); + files = Directory.GetFiles(subDirectory, settings.SourceFileFilter, SearchOption.TopDirectoryOnly); + foreach (string file in files) + { + text = File.ReadAllText(file); + if (string.IsNullOrEmpty(text) || text[0] == '[') + continue; + notification = JsonSerializer.Deserialize(text, NotificationSourceGenerationContext.Default.Notification); + if (notification is null || notification.Id == 0) + continue; + if (string.IsNullOrEmpty(notification.RemoteIpAddress)) + continue; + if (!keyValuePairs.TryGetValue(notification.RemoteIpAddress, out collection)) + { + keyValuePairs.Add(notification.RemoteIpAddress, new()); + if (!keyValuePairs.TryGetValue(notification.RemoteIpAddress, out collection)) + throw new Exception(); + } + collection.Add(notification); + } + foreach (KeyValuePair> keyValuePair in keyValuePairs) + { + if (keyValuePair.Value.Count == 1) + results.Add(keyValuePair.Value[0]); + else + { + notification = keyValuePair.Value.Select(record => new KeyValuePair(record.Time, record)).OrderBy(pair => pair.Key).Last().Value; + results.Add(notification); + } + } + } + return new(results); + } + + private static ReadOnlyDictionary GetKeyValuePairs(Settings settings, string directory) + { + ReadOnlyDictionary results; + List? collection; + Dictionary> keyValuePairs = new(); + ReadOnlyCollection notifications = GetNotifications(settings, directory); + foreach (Notification notification in notifications) + { + if (!keyValuePairs.TryGetValue(notification.Id, out collection)) + { + keyValuePairs.Add(notification.Id, new()); + if (!keyValuePairs.TryGetValue(notification.Id, out collection)) + throw new Exception(); + } + collection.Add(notification); + } + results = GetKeyValuePairs(settings, keyValuePairs); + return results; + } + + internal static ReadOnlyDictionary> GetKeyValuePairsAndWriteFiles(Settings settings) + { + Dictionary> results = new(); + string json; + string jsonOld; + string jsonFile; + string directoryName; + ReadOnlyDictionary keyValuePairs; + if (!Directory.Exists(settings.SourceFileLocation)) + _ = Directory.CreateDirectory(settings.SourceFileLocation); + if (!Directory.Exists(settings.TargetFileLocation)) + _ = Directory.CreateDirectory(settings.TargetFileLocation); + string[] directories = Directory.GetDirectories(settings.SourceFileLocation, "*", SearchOption.TopDirectoryOnly); + foreach (string directory in directories) + { + directoryName = Path.GetFileName(directory); + keyValuePairs = GetKeyValuePairs(settings, directory); + jsonFile = Path.Combine(settings.TargetFileLocation, $"{directoryName}.json"); + json = JsonSerializer.Serialize(keyValuePairs, AggregationReadOnlyDictionarySourceGenerationContext.Default.ReadOnlyDictionaryInt32Aggregation); + // keyValuePairs = JsonSerializer.Deserialize(json, AggregationReadOnlyDictionarySourceGenerationContext.Default.ReadOnlyDictionaryInt32Aggregation); + jsonOld = File.Exists(jsonFile) ? File.ReadAllText(jsonFile) : string.Empty; + if (json != jsonOld) + File.WriteAllText(jsonFile, json); + results.Add(directoryName, keyValuePairs); + } + return new(results); + } + + internal static ReadOnlyDictionary GetKeyValuePairs(Settings settings, Notification notification) + { + ReadOnlyDictionary results; + Dictionary> keyValuePairs = new() { { notification.Id, new Notification[] { notification }.ToList() } }; + results = GetKeyValuePairs(settings, keyValuePairs); + return results; + } } @@ -46,4 +200,10 @@ internal partial class AggregationSourceGenerationContext : JsonSerializerContex [JsonSerializable(typeof(Aggregation[]))] internal partial class AggregationCollectionSourceGenerationContext : JsonSerializerContext { +} + +[JsonSourceGenerationOptions(WriteIndented = true)] +[JsonSerializable(typeof(ReadOnlyDictionary))] +internal partial class AggregationReadOnlyDictionarySourceGenerationContext : JsonSerializerContext +{ } \ No newline at end of file diff --git a/Adaptation/FileHandlers/Priority/FileRead.cs b/Adaptation/FileHandlers/Priority/FileRead.cs index 9a3da2e..d2c7e1e 100644 --- a/Adaptation/FileHandlers/Priority/FileRead.cs +++ b/Adaptation/FileHandlers/Priority/FileRead.cs @@ -3,28 +3,36 @@ using Adaptation.Ifx.Eaf.EquipmentConnector.File.Configuration; using Adaptation.Shared; using Adaptation.Shared.Duplicator; using Adaptation.Shared.Methods; +using log4net; using System; using System.Collections.Generic; -using System.Collections.ObjectModel; -using System.Diagnostics; using System.IO; -using System.Linq; using System.Text.Json; -using System.Threading; namespace Adaptation.FileHandlers.Priority; +#nullable enable + public class FileRead : Shared.FileRead, IFileRead { - private readonly Timer _Timer; + internal static ILog Log => _Log; + internal static Settings Settings => _Settings; + internal static Dictionary WorkItems => _WorkItems; +#pragma warning disable IDE0032, CS8618 + private static new ILog _Log; + private static Settings _Settings; + private static Dictionary _WorkItems; +#pragma warning restore IDE0032, CS8618 public FileRead(ISMTP smtp, Dictionary fileParameter, string cellInstanceName, int? connectionCount, string cellInstanceConnectionName, FileConnectorConfiguration fileConnectorConfiguration, string equipmentTypeName, string parameterizedModelObjectDefinitionType, IList modelObjectParameters, string equipmentDictionaryName, Dictionary> dummyRuns, Dictionary> staticRuns, bool useCyclicalForDescription, bool isEAFHosted) : base(new Description(), false, smtp, fileParameter, cellInstanceName, connectionCount, cellInstanceConnectionName, fileConnectorConfiguration, equipmentTypeName, parameterizedModelObjectDefinitionType, modelObjectParameters, equipmentDictionaryName, dummyRuns, staticRuns, useCyclicalForDescription, isEAFHosted: connectionCount is null) { + _WorkItems = new(); _MinFileLength = 10; - _NullData = string.Empty; _Logistics = new(this); + _NullData = string.Empty; + _Log = LogManager.GetLogger(typeof(FileRead)); if (_FileParameter is null) throw new Exception(cellInstanceConnectionName); if (_ModelObjectParameterDefinitions is null) @@ -33,12 +41,23 @@ public class FileRead : Shared.FileRead, IFileRead throw new Exception(cellInstanceConnectionName); if (_IsEAFHosted) NestExistingFiles(_FileConnectorConfiguration); - if (!Debugger.IsAttached && fileConnectorConfiguration.PreProcessingMode != FileConnectorConfiguration.PreProcessingModeEnum.Process) - _Timer = new Timer(Callback, null, (int)(fileConnectorConfiguration.FileScanningIntervalInSeconds * 1000), Timeout.Infinite); - else + string parentDirectory = Path.GetDirectoryName(_FileConnectorConfiguration.TargetFileLocation) ?? throw new Exception(); + _Settings = new(digits: 5, + parentDirectory: parentDirectory, + priorities: 3, + priorityGroups: 9, + sourceFileFilter: _FileConnectorConfiguration.SourceFileFilter, + sourceFileLocation: _FileConnectorConfiguration.SourceFileLocation, + targetFileLocation: _FileConnectorConfiguration.TargetFileLocation); + string? json = WeightedShortestJobFirstHub.PopulatedWorkItemsAndGetJson(_Settings); + if (!string.IsNullOrEmpty(json)) + WeightedShortestJobFirstHub.WriteJson(json); + string cellInstanceNamed = string.Concat("CellInstance.", _EquipmentType); + string url = GetPropertyValue(cellInstanceConnectionName, modelObjectParameters, $"{cellInstanceNamed}.Microsoft.Owin.Hosting.WebApp.Start.URL"); + if (_IsEAFHosted) { - _Timer = new Timer(Callback, null, Timeout.Infinite, Timeout.Infinite); - Callback(null); + _ = Microsoft.Owin.Hosting.WebApp.Start(url); + _Log.Info($"Server running on {url}"); } } @@ -91,7 +110,7 @@ public class FileRead : Shared.FileRead, IFileRead DateTime dateTime = DateTime.Now; results = GetExtractResult(reportFullPath, dateTime); if (results.Item3 is null) - results = new Tuple>(results.Item1, Array.Empty(), JsonSerializer.Deserialize("[]"), results.Item4); + results = new Tuple>(results.Item1, Array.Empty(), JsonSerializer.Deserialize("[]") ?? throw new Exception(), results.Item4); if (results.Item3.Length > 0 && _IsEAFHosted) WritePDSF(this, results.Item3); UpdateLastTicksDuration(DateTime.Now.Ticks - dateTime.Ticks); @@ -107,187 +126,6 @@ public class FileRead : Shared.FileRead, IFileRead return results; } -#nullable enable - - private static ReadOnlyCollection GetRecords(string directory, string searchPattern) - { - List results = new(); - string text; - Record? record; - string[] files; - List? collection; - Dictionary> keyValuePairs = new(); - string[] directories = Directory.GetDirectories(directory, "*", SearchOption.TopDirectoryOnly); - foreach (string subDirectory in directories) - { - keyValuePairs.Clear(); - files = Directory.GetFiles(subDirectory, searchPattern, SearchOption.TopDirectoryOnly); - foreach (string file in files) - { - text = File.ReadAllText(file); - if (string.IsNullOrEmpty(text) || text[0] == '[') - continue; - record = JsonSerializer.Deserialize(text); - if (record is null || record.Id == 0) - continue; - if (!keyValuePairs.TryGetValue(record.RemoteIpAddress, out collection)) - { - keyValuePairs.Add(record.RemoteIpAddress, new()); - if (!keyValuePairs.TryGetValue(record.RemoteIpAddress, out collection)) - throw new Exception(); - } - collection.Add(record); - } - foreach (KeyValuePair> keyValuePair in keyValuePairs) - { - if (keyValuePair.Value.Count == 1) - results.Add(keyValuePair.Value[0]); - else - { - record = keyValuePair.Value.Select(record => new KeyValuePair(record.Time, record)).OrderBy(pair => pair.Key).Last().Value; - results.Add(record); - } - } - } - return new(results); - } - - private static int? GetInverse(int value) => - value switch - { - 1 => 3, - 2 => 2, - 3 => 1, - _ => null - }; - - private static int? GetInverse(double value) - { - int? result; - if (value > 3) - result = null; - else if (value > 2) - result = 1; - else if (value > 1) - result = 2; - else if (value > 0) - result = 3; - else - result = null; - return result; - } - - private static ReadOnlyDictionary GetKeyValuePairs(Dictionary> keyValuePairs) - { - Dictionary results = new(); - Aggregation aggregation; - int? inverse; - double average; - List collection = new(); - foreach (KeyValuePair> keyValuePair in keyValuePairs) - { - collection.Clear(); - foreach (Record record in keyValuePair.Value) - { - inverse = GetInverse(record.Value); - if (inverse is null) - continue; - collection.Add(inverse.Value); - } - average = collection.Average(); - inverse = GetInverse(average); - aggregation = new(average.ToString("0.000"), - keyValuePair.Value.Count, - inverse, - keyValuePair.Value.Max(record => record.Value), - keyValuePair.Value.Min(record => record.Value), - new(keyValuePair.Value), - keyValuePair.Value.Sum(record => record.Value)); - results.Add(keyValuePair.Key, aggregation); - } - return new(results); - } - - private static ReadOnlyDictionary GetKeyValuePairs(string directory, string searchPattern) - { - ReadOnlyDictionary results; - List? collection; - Dictionary> keyValuePairs = new(); - ReadOnlyCollection records = GetRecords(directory, searchPattern); - foreach (Record record in records) - { - if (!keyValuePairs.TryGetValue(record.Id, out collection)) - { - keyValuePairs.Add(record.Id, new()); - if (!keyValuePairs.TryGetValue(record.Id, out collection)) - throw new Exception(); - } - collection.Add(record); - } - results = GetKeyValuePairs(keyValuePairs); - return results; - } - - private static void WriteFiles(string sourceFileLocation, string sourceFileFilter, string targetFileLocation) - { - string json; - string jsonFile; - string directoryName; - if (!Directory.Exists(sourceFileLocation)) - _ = Directory.CreateDirectory(sourceFileLocation); - if (!Directory.Exists(targetFileLocation)) - _ = Directory.CreateDirectory(targetFileLocation); - ReadOnlyDictionary keyValuePairs; - JsonSerializerOptions jsonSerializerOptions = new() { WriteIndented = true }; - string[] directories = Directory.GetDirectories(sourceFileLocation, "*", SearchOption.TopDirectoryOnly); - foreach (string directory in directories) - { - directoryName = Path.GetFileName(directory); - keyValuePairs = GetKeyValuePairs(directory, sourceFileFilter); - jsonFile = Path.Combine(targetFileLocation, $"{directoryName}.json"); - json = JsonSerializer.Serialize(keyValuePairs, jsonSerializerOptions); - File.WriteAllText(jsonFile, json); - } - } - - private void Callback(object state) - { - try - { - if (_IsEAFHosted) - WriteFiles(_FileConnectorConfiguration.SourceFileLocation, _FileConnectorConfiguration.SourceFileFilter, _FileConnectorConfiguration.TargetFileLocation); - } - catch (Exception exception) - { - string subject = string.Concat("Exception:", _CellInstanceConnectionName); - string body = string.Concat(exception.Message, Environment.NewLine, Environment.NewLine, exception.StackTrace); - try - { - _SMTP.SendHighPriorityEmailMessage(subject, body); - File.WriteAllText(".email", body); - } - catch (Exception) { } - } - try - { - if (_FileConnectorConfiguration?.FileScanningIntervalInSeconds is null) - throw new Exception(); - TimeSpan timeSpan = new(DateTime.Now.AddSeconds(_FileConnectorConfiguration.FileScanningIntervalInSeconds.Value).Ticks - DateTime.Now.Ticks); - _ = _Timer.Change((long)timeSpan.TotalMilliseconds, Timeout.Infinite); - } - catch (Exception exception) - { - string subject = string.Concat("Exception:", _CellInstanceConnectionName); - string body = string.Concat(exception.Message, Environment.NewLine, Environment.NewLine, exception.StackTrace); - try - { - _SMTP.SendHighPriorityEmailMessage(subject, body); - File.WriteAllText(".email", body); - } - catch (Exception) { } - } - } - private Tuple> GetExtractResult(string reportFullPath, DateTime dateTime) { Tuple> results; diff --git a/Adaptation/FileHandlers/Priority/Notification.cs b/Adaptation/FileHandlers/Priority/Notification.cs new file mode 100644 index 0000000..805de18 --- /dev/null +++ b/Adaptation/FileHandlers/Priority/Notification.cs @@ -0,0 +1,82 @@ +using System.Text.Json.Serialization; + +namespace Adaptation.FileHandlers.Priority; + +#nullable enable + +public class Notification +{ + + [JsonConstructor] + public Notification(int? fibonacci, + int id, + int? inverse, + string page, + string? remoteIpAddress, + string? site, + long time, + int value) + { + int? i = inverse is not null ? inverse : GetInverse(value); + Fibonacci = fibonacci is not null ? fibonacci : i is null ? null : GetFibonacci(i.Value); + Id = id; + Inverse = i; + Page = page; + RemoteIpAddress = remoteIpAddress is not null ? remoteIpAddress : null; + Site = site is not null ? site : "MES"; + Time = time; + Value = value; + } + + [JsonPropertyName("id")] public int Id { get; } + [JsonPropertyName("fibonacci")] public int? Fibonacci { get; } + [JsonPropertyName("inverse")] public int? Inverse { get; } + [JsonPropertyName("page")] public string Page { get; } + [JsonPropertyName("RemoteIpAddress")] public string? RemoteIpAddress { get; } + [JsonPropertyName("site")] public string? Site { get; } + [JsonPropertyName("time")] public long Time { get; } + [JsonPropertyName("value")] public int Value { get; } + + internal static int? GetInverse(int value) => + value switch + { + 1 => 5, + 2 => 4, + 3 => 3, + 4 => 2, + 5 => 1, + _ => null + }; + + private static int? GetFibonacci(int value) => + value switch + { + 9 => 55, + 8 => 34, + 7 => 21, + 6 => 13, + 5 => 8, + 4 => 5, + 3 => 3, + 2 => 2, + 1 => 1, + _ => null + }; + + internal static Notification GetNotification(Notification notification, string? remoteIpAddress, string? connectionId) => + new(notification.Fibonacci, + notification.Id, + notification.Inverse, + notification.Page, + remoteIpAddress ?? connectionId, + notification.Site, + notification.Time, + notification.Value); + +} + +[JsonSourceGenerationOptions(WriteIndented = true)] +[JsonSerializable(typeof(Notification))] +public partial class NotificationSourceGenerationContext : JsonSerializerContext +{ +} \ No newline at end of file diff --git a/Adaptation/FileHandlers/Priority/Record.cs b/Adaptation/FileHandlers/Priority/Record.cs deleted file mode 100644 index dd08a19..0000000 --- a/Adaptation/FileHandlers/Priority/Record.cs +++ /dev/null @@ -1,37 +0,0 @@ -using System.Collections.ObjectModel; -using System.Text.Json.Serialization; - -namespace Adaptation.FileHandlers.Priority; - -public class Record -{ - - [JsonConstructor] - public Record( - string json, - int id, - string page, - string queryString, - string remoteIpAddress, - long time, - int value - ) - { - Json = json; - Id = id; - Page = page; - QueryString = queryString; - RemoteIpAddress = remoteIpAddress; - Time = time; - Value = value; - } - - [JsonPropertyName("Json")] public string Json { get; } - [JsonPropertyName("id")] public int Id { get; } - [JsonPropertyName("page")] public string Page { get; } - [JsonPropertyName("QueryString")] public string QueryString { get; } - [JsonPropertyName("RemoteIpAddress")] public string RemoteIpAddress { get; } - [JsonPropertyName("time")] public long Time { get; } - [JsonPropertyName("value")] public int Value { get; } - -} \ No newline at end of file diff --git a/Adaptation/FileHandlers/Priority/Settings.cs b/Adaptation/FileHandlers/Priority/Settings.cs new file mode 100644 index 0000000..c5921f6 --- /dev/null +++ b/Adaptation/FileHandlers/Priority/Settings.cs @@ -0,0 +1,43 @@ +using System.Collections.Generic; +using System.Text.Json.Serialization; + +namespace Adaptation.FileHandlers.Priority; + +#nullable enable + +public class Settings +{ + + [JsonConstructor] + public Settings(int digits, + string parentDirectory, + int priorities, + int priorityGroups, + string sourceFileFilter, + string sourceFileLocation, + string targetFileLocation) + { + Digits = digits; + ParentDirectory = parentDirectory; + Priorities = priorities; + PriorityGroups = priorityGroups; + SourceFileFilter = sourceFileFilter; + SourceFileLocation = sourceFileLocation; + TargetFileLocation = targetFileLocation; + } + + public int Digits { get; } // [JsonPropertyName("Digits")] + public string ParentDirectory { get; } // [JsonPropertyName("ParentDirectory")] + public int Priorities { get; } // [JsonPropertyName("Priorities")] + public int PriorityGroups { get; } // [JsonPropertyName("PriorityGroups")] + public string SourceFileFilter { get; } // [JsonPropertyName("SourceFileFilter")] + public string SourceFileLocation { get; } // [JsonPropertyName("SourceFileLocation")] + public string TargetFileLocation { get; } // [JsonPropertyName("TargetFileLocation")] + +} + +[JsonSourceGenerationOptions(WriteIndented = true)] +[JsonSerializable(typeof(Dictionary))] +internal partial class SettingsDictionarySourceGenerationContext : JsonSerializerContext +{ +} \ No newline at end of file diff --git a/Adaptation/FileHandlers/Priority/Startup.cs b/Adaptation/FileHandlers/Priority/Startup.cs new file mode 100644 index 0000000..5b1757b --- /dev/null +++ b/Adaptation/FileHandlers/Priority/Startup.cs @@ -0,0 +1,13 @@ +using Microsoft.Owin.Cors; +using Owin; + +public class Startup +{ + + public void Configuration(IAppBuilder app) + { + _ = app.UseCors(CorsOptions.AllowAll); + _ = app.MapSignalR(); + } + +} \ No newline at end of file diff --git a/Adaptation/FileHandlers/Priority/WeightedShortestJobFirstHub.cs b/Adaptation/FileHandlers/Priority/WeightedShortestJobFirstHub.cs new file mode 100644 index 0000000..5e211e7 --- /dev/null +++ b/Adaptation/FileHandlers/Priority/WeightedShortestJobFirstHub.cs @@ -0,0 +1,158 @@ +using System; +using System.Collections.Generic; +using System.Collections.ObjectModel; +using System.IO; +using System.Linq; +using System.Text.Json; + +#nullable enable + +namespace Adaptation.FileHandlers.Priority; + +public class WeightedShortestJobFirstHub : Microsoft.AspNet.SignalR.Hub +{ + + // public async Task Send(int n) + // { + // await Clients.All.send(n); + // } + + private string? GetRemoteIpAddress() => + Context?.Headers?.Get("X-Real-IP"); + + public void Send(string name, string message) + { + Console.WriteLine($"{name}:{message};"); + // FileRead.Logger.LogWarning($"{name}:{message};"); + // FileRead.Log?.Info($"{name}:{message};"); + Console.WriteLine(Context?.ConnectionId); + // FileRead.Logger.LogWarning(Context?.ConnectionId); + // FileRead.Log?.Info(Context?.ConnectionId); + string? remoteIpAddress = GetRemoteIpAddress(); + Console.WriteLine(remoteIpAddress); + // FileRead.Logger.LogWarning(remoteIpAddress); + // FileRead.Log?.Info(remoteIpAddress); + Clients.All.addMessage(name, message); + } + + private static void FileWriteAllText(Settings settings, Notification n) + { + string json = JsonSerializer.Serialize(n, NotificationSourceGenerationContext.Default.Notification); + string directory = Path.Combine(settings.SourceFileLocation, n.Page, n.Id.ToString()); + if (!Directory.Exists(directory)) + _ = Directory.CreateDirectory(directory); + string checkFile = Path.Combine(directory, $"{n.Time}.json"); + File.WriteAllText(checkFile, json); + } + + internal static void WriteJson(string json) + { + string jsonFile = Path.Combine(FileRead.Settings.ParentDirectory, "{}.json"); + string jsonFileWith = Path.Combine(FileRead.Settings.ParentDirectory, "{[]}.json"); + string jsonOld = File.Exists(jsonFileWith) ? File.ReadAllText(jsonFileWith) : string.Empty; + if (json != jsonOld) + { + File.WriteAllText(jsonFileWith, json); + Dictionary w = JsonSerializer.Deserialize(json.Replace($"\"{nameof(Aggregation.Notifications)}\":", "\"ignore\":"), WorkItemDictionarySourceGenerationContext.Default.DictionaryInt32WorkItem) ?? throw new Exception(); + json = JsonSerializer.Serialize(w, WorkItemDictionarySourceGenerationContext.Default.DictionaryInt32WorkItem); + File.WriteAllText(jsonFile, json); + } + } + + internal static string? PopulatedWorkItemsAndGetJson(Settings settings) + { + string? result = null; + ReadOnlyDictionary workItems = WorkItem.GetKeyValuePairs(settings); + int useCount = (from l in workItems where l.Value.CostOfDelay is not null select true).Count(); + double prioritySize = useCount / settings.Priorities; + double priorityGroupSize = useCount / settings.PriorityGroups; + WorkItem[] sorted = (from l in workItems + where l.Value is not null + orderby l.Value.Site is not null, + l.Value.Site descending, + l.Value.CostOfDelay is not null, + l.Value.CostOfDelay descending, + l.Value.BusinessValue?.FibonacciAverage is not null, + l.Value.BusinessValue?.FibonacciAverage descending, + l.Key + select l.Value).ToArray(); + lock (FileRead.WorkItems) + { + int j = 0; + WorkItem w; + double value; + int lastId = -1; + int? sortBeforeId; + WorkItem workItem; + int? sortPriority; + int? sortPriorityGroup; + FileRead.WorkItems.Clear(); + for (int i = 0; i < sorted.Length; i++) + { + w = sorted[i]; + if (w.CostOfDelay is null) + { + sortBeforeId = null; + sortPriority = null; + sortPriorityGroup = null; + } + else + { + j += 1; + sortBeforeId = lastId; + value = (j / prioritySize) + 1; + sortPriority = (int)Math.Floor(value); + if (sortPriority > settings.Priorities) + sortPriority = settings.Priorities; + value = (j / priorityGroupSize) + 1; + sortPriorityGroup = (int)Math.Floor(value); + if (sortPriorityGroup > settings.PriorityGroups) + sortPriorityGroup = settings.PriorityGroups; + } + workItem = WorkItem.GetWorkItem(w, i, sortBeforeId, sortPriority, sortPriorityGroup); + FileRead.WorkItems.Add(workItem.Id, workItem); + lastId = w.Id; + } + result = JsonSerializer.Serialize(FileRead.WorkItems, WorkItemDictionarySourceGenerationContext.Default.DictionaryInt32WorkItem); + } + return result; + } + + private static WorkItem GetWorkItem(Notification notification) + { + WorkItem? result; + lock (FileRead.WorkItems) + { + if (!FileRead.WorkItems.TryGetValue(notification.Id, out result)) + throw new Exception(); + } + return result; + } + + public void NotifyAll(Notification notification) + { + try + { + string? json = null; + string? remoteIpAddress = GetRemoteIpAddress(); + Notification n = Notification.GetNotification(notification, remoteIpAddress, Context?.ConnectionId); + Console.WriteLine(n.ToString()); + // FileRead.Logger.LogWarning(n.ToString()); + // FileRead.Log?.Info(n.ToString()); + FileWriteAllText(FileRead.Settings, n); + json = PopulatedWorkItemsAndGetJson(FileRead.Settings); + if (!string.IsNullOrEmpty(json)) + WriteJson(json); + if (!string.IsNullOrEmpty(n.RemoteIpAddress)) + { + WorkItem workItem = GetWorkItem(n); + Clients.All.updateWorkItem(n.Page, workItem); + } + } + catch (Exception ex) + { Console.WriteLine($"{ex.Message}{Environment.NewLine}{ex.StackTrace}"); } + // { FileRead.Logger.LogError(ex, "Error!"); } + // { FileRead.Log?.Error("Error!", ex); } + } + +} \ No newline at end of file diff --git a/Adaptation/FileHandlers/Priority/WorkItem.cs b/Adaptation/FileHandlers/Priority/WorkItem.cs new file mode 100644 index 0000000..3ad8fc8 --- /dev/null +++ b/Adaptation/FileHandlers/Priority/WorkItem.cs @@ -0,0 +1,205 @@ +using System; +using System.Collections.Generic; +using System.Collections.ObjectModel; +using System.Text.Json.Serialization; + +namespace Adaptation.FileHandlers.Priority; + +#nullable enable + +public class WorkItem +{ + + [JsonConstructor] + public WorkItem(double? costOfDelay, + Aggregation? businessValue, + Aggregation? effort, + int id, + int? sortBeforeId, + int? sortPriority, + int? sortPriorityGroup, + Aggregation? riskReductionOpportunityEnablement, + string? site, + int? sortOrder, + Aggregation? timeCriticality, + double? weightedShortestJobFirst) + { + CostOfDelay = costOfDelay; + BusinessValue = businessValue; + Effort = effort; + Id = id; + Site = site; + SortBeforeId = sortBeforeId; + SortPriority = sortPriority; + SortPriorityGroup = sortPriorityGroup; + RiskReductionOpportunityEnablement = riskReductionOpportunityEnablement; + SortOrder = sortOrder; + TimeCriticality = timeCriticality; + WeightedShortestJobFirst = weightedShortestJobFirst; + } + + const string _PageEffort = "effort"; + const string _PageTimeCriticality = "time"; + const string _PageBusinessValue = "business"; + const string _PageRiskReductionOpportunityEnablement = "risk"; + + public double? CostOfDelay { get; } // [JsonPropertyName("CostOfDelay")] + public Aggregation? BusinessValue { get; } // [JsonPropertyName("BusinessValue")] + public Aggregation? Effort { get; } // [JsonPropertyName("Effort")] + public int Id { get; } // [JsonPropertyName("Id")] + public string? Site { get; } // [JsonPropertyName("Site")] + public int? SortBeforeId { get; } // [JsonPropertyName("SortBeforeId")] + public int? SortPriority { get; } // [JsonPropertyName("SortPriority")] + public int? SortPriorityGroup { get; } // [JsonPropertyName("SortPriorityGroup")] + public Aggregation? RiskReductionOpportunityEnablement { get; } // [JsonPropertyName("RiskReductionOpportunityEnablement")] + public int? SortOrder { get; } // [JsonPropertyName("SortOrder")] + public Aggregation? TimeCriticality { get; } // [JsonPropertyName("TimeCriticality")] + public double? WeightedShortestJobFirst { get; } // [JsonPropertyName("WeightedShortestJobFirst")] + + internal static WorkItem GetWorkItem(WorkItem workItem, int i, int? sortBeforeId, int? sortPriority, int? sortPriorityGroup) => + new(workItem.CostOfDelay, + workItem.BusinessValue, + workItem.Effort, + workItem.Id, + sortBeforeId, + sortPriority, + sortPriorityGroup, + workItem.RiskReductionOpportunityEnablement, + workItem.Site, + i, + workItem.TimeCriticality, + workItem.WeightedShortestJobFirst); + + private static string? GetSite(Aggregation? effort, Aggregation? businessValue, Aggregation? timeCriticality, Aggregation? riskReductionOpportunityEnablement) + { + string? result = null; + if (result is null && effort is not null) + { + foreach (Notification notification in effort.Notifications) + { + if (notification.Site is not null) + { + result = notification.Site; + break; + } + } + } + if (result is null && businessValue is not null) + { + foreach (Notification notification in businessValue.Notifications) + { + if (notification.Site is not null) + { + result = notification.Site; + break; + } + } + } + if (result is null && timeCriticality is not null) + { + foreach (Notification notification in timeCriticality.Notifications) + { + if (notification.Site is not null) + { + result = notification.Site; + break; + } + } + } + if (result is null && riskReductionOpportunityEnablement is not null) + { + foreach (Notification notification in riskReductionOpportunityEnablement.Notifications) + { + if (notification.Site is not null) + { + result = notification.Site; + break; + } + } + } + return result; + } + + internal static ReadOnlyDictionary GetWorkItems(Settings settings, ReadOnlyDictionary> keyValuePairs) + { + Dictionary results = new(); + string? site; + WorkItem? workItem; + double? costOfDelay; + Aggregation? effort; + List ids = new(); + Aggregation? businessValue; + Aggregation? timeCriticality; + double? weightedShortestJobFirst; + Aggregation? riskReductionOpportunityEnablement; + Dictionary effortCollection = new(); + Dictionary businessValueCollection = new(); + Dictionary timeCriticalityCollection = new(); + Dictionary riskReductionOpportunityEnablementCollection = new(); + foreach (KeyValuePair> keyValuePair in keyValuePairs) + { + foreach (KeyValuePair keyValue in keyValuePair.Value) + { + if (!ids.Contains(keyValue.Key)) + ids.Add(keyValue.Key); + if (keyValuePair.Key == _PageEffort) + effortCollection.Add(keyValue.Key, keyValue.Value); + else if (keyValuePair.Key == _PageTimeCriticality) + timeCriticalityCollection.Add(keyValue.Key, keyValue.Value); + else if (keyValuePair.Key == _PageBusinessValue) + businessValueCollection.Add(keyValue.Key, keyValue.Value); + else if (keyValuePair.Key == _PageRiskReductionOpportunityEnablement) + riskReductionOpportunityEnablementCollection.Add(keyValue.Key, keyValue.Value); + else + throw new NotImplementedException(); + } + } + foreach (int id in ids) + { + if (!effortCollection.TryGetValue(id, out effort)) + effort = null; + if (!businessValueCollection.TryGetValue(id, out businessValue)) + businessValue = null; + if (!timeCriticalityCollection.TryGetValue(id, out timeCriticality)) + timeCriticality = null; + if (!riskReductionOpportunityEnablementCollection.TryGetValue(id, out riskReductionOpportunityEnablement)) + riskReductionOpportunityEnablement = null; + site = GetSite(effort, businessValue, timeCriticality, riskReductionOpportunityEnablement); + costOfDelay = businessValue is null + || timeCriticality is null + || riskReductionOpportunityEnablement is null ? null : businessValue.FibonacciAverage + + timeCriticality.FibonacciAverage + + riskReductionOpportunityEnablement.FibonacciAverage; + weightedShortestJobFirst = costOfDelay is null || effort is null ? null : Math.Round(costOfDelay.Value / effort.FibonacciAverage, settings.Digits); + workItem = new(costOfDelay: costOfDelay, + businessValue: businessValue, + effort: effort, + id: id, + sortBeforeId: null, + sortPriority: null, + sortPriorityGroup: null, + riskReductionOpportunityEnablement: riskReductionOpportunityEnablement, + site: site, + sortOrder: null, + timeCriticality: timeCriticality, + weightedShortestJobFirst: weightedShortestJobFirst); + results.Add(id, workItem); + } + return new(results); + } + + internal static ReadOnlyDictionary GetKeyValuePairs(Settings settings) + { + ReadOnlyDictionary results; + ReadOnlyDictionary> keyValuePairs = Aggregation.GetKeyValuePairsAndWriteFiles(settings); + results = GetWorkItems(settings, keyValuePairs); + return results; + } + +} + +[JsonSourceGenerationOptions(WriteIndented = true)] +[JsonSerializable(typeof(Dictionary))] +internal partial class WorkItemDictionarySourceGenerationContext : JsonSerializerContext +{ +} \ No newline at end of file diff --git a/Adaptation/MESAFIBACKLOG.Tests.csproj b/Adaptation/MESAFIBACKLOG.Tests.csproj index 9e19c51..b3f3909 100644 --- a/Adaptation/MESAFIBACKLOG.Tests.csproj +++ b/Adaptation/MESAFIBACKLOG.Tests.csproj @@ -37,13 +37,27 @@ - NU1701 - NU1701 - NU1701 - NU1701 - NU1701 - NU1701 - NU1701 + + NU1701 + + + NU1701 + + + NU1701 + + + NU1701 + + + NU1701 + + + NU1701 + + + NU1701 + @@ -60,7 +74,9 @@ - NU1701 + + NU1701 + @@ -78,7 +94,9 @@ - NU1701 + + NU1701 + @@ -87,6 +105,15 @@ + + + + + + + + + Always diff --git a/Adaptation/_Tests/CreateSelfDescription/Development/v2.58.0/MESAFIBACKLOG.cs b/Adaptation/_Tests/CreateSelfDescription/Development/v2.58.0/MESAFIBACKLOG.cs index 96e7d85..ccc9259 100644 --- a/Adaptation/_Tests/CreateSelfDescription/Development/v2.58.0/MESAFIBACKLOG.cs +++ b/Adaptation/_Tests/CreateSelfDescription/Development/v2.58.0/MESAFIBACKLOG.cs @@ -87,5 +87,18 @@ public class MESAFIBACKLOG : EAFLoggingUnitTesting EAFLoggingUnitTesting.Logger.LogInformation(string.Concat(methodBase.Name, " - Exit")); } +#if DEBUG + [Ignore] +#endif + [TestMethod] + public void Development__v2_58_0__MESAFIBACKLOG__Priority() + { + string check = "*.json"; + MethodBase methodBase = new StackFrame().GetMethod(); + EAFLoggingUnitTesting.Logger.LogInformation(string.Concat(methodBase.Name, " - Getting configuration")); + _ = AdaptationTesting.GetWriteConfigurationGetFileRead(methodBase, check, EAFLoggingUnitTesting.AdaptationTesting); + EAFLoggingUnitTesting.Logger.LogInformation(string.Concat(methodBase.Name, " - Exit")); + } + } #endif \ No newline at end of file diff --git a/Adaptation/_Tests/Extract/Development/v2.58.0/MESAFIBACKLOG.cs b/Adaptation/_Tests/Extract/Development/v2.58.0/MESAFIBACKLOG.cs index 3d96e62..01280dd 100644 --- a/Adaptation/_Tests/Extract/Development/v2.58.0/MESAFIBACKLOG.cs +++ b/Adaptation/_Tests/Extract/Development/v2.58.0/MESAFIBACKLOG.cs @@ -1,5 +1,6 @@ #if true using Adaptation.FileHandlers.json.WorkItems; +using Adaptation.FileHandlers.Priority; using Adaptation.Shared; using Adaptation.Shared.Methods; using Microsoft.VisualStudio.TestTools.UnitTesting; @@ -173,8 +174,8 @@ public class MESAFIBACKLOG { string check = "*.json"; bool validatePDSF = false; - _MESAFIBACKLOG.Development__v2_58_0__MESAFIBACKLOG__ADO(); MethodBase methodBase = new StackFrame().GetMethod(); + _MESAFIBACKLOG.Development__v2_58_0__MESAFIBACKLOG__ADO(); Assert.IsFalse(string.IsNullOrEmpty(_MESAFIBACKLOG.AdaptationTesting.TestContext.FullyQualifiedTestClassName)); string[] variables = _MESAFIBACKLOG.AdaptationTesting.GetVariables(methodBase, check, validatePDSF); IFileRead fileRead = _MESAFIBACKLOG.AdaptationTesting.Get(methodBase, sourceFileLocation: variables[2], sourceFileFilter: variables[3], useCyclicalForDescription: false); @@ -185,5 +186,35 @@ public class MESAFIBACKLOG NonThrowTryCatch(); } +#if DEBUG + [Ignore] +#endif + [TestMethod] + public void Development__v2_58_0__MESAFIBACKLOG__Priority638323658386612552__Normal() + { + string check = "*.json"; + bool validatePDSF = false; + MethodBase methodBase = new StackFrame().GetMethod(); + _MESAFIBACKLOG.Development__v2_58_0__MESAFIBACKLOG__Priority(); + Assert.IsFalse(string.IsNullOrEmpty(_MESAFIBACKLOG.AdaptationTesting.TestContext.FullyQualifiedTestClassName)); + string[] variables = _MESAFIBACKLOG.AdaptationTesting.GetVariables(methodBase, check, validatePDSF); + IFileRead fileRead = _MESAFIBACKLOG.AdaptationTesting.Get(methodBase, sourceFileLocation: variables[2], sourceFileFilter: variables[3], useCyclicalForDescription: false); + Tuple> extractResult = fileRead.ReExtract(); + Assert.IsFalse(string.IsNullOrEmpty(extractResult?.Item1)); + Assert.IsNotNull(extractResult.Item3); + Assert.IsNotNull(extractResult.Item4); + WeightedShortestJobFirstHub weightedShortestJobFirstHub = new(); + Notification notification = new(fibonacci: null, + id: 1107438888, + inverse: null, + page: "effort", + remoteIpAddress: "10.95.36.87", + site: "MES", + time: 1737573418926, + value: 1); + weightedShortestJobFirstHub.NotifyAll(notification); + NonThrowTryCatch(); + } + } #endif \ No newline at end of file diff --git a/MESAFIBACKLOG.csproj b/MESAFIBACKLOG.csproj index ba3ed4b..b12cfa5 100644 --- a/MESAFIBACKLOG.csproj +++ b/MESAFIBACKLOG.csproj @@ -150,7 +150,11 @@ - + + + + + @@ -209,6 +213,27 @@ 2.58.0 + + 2.4.3 + + + 2.4.3 + + + 1.0.0 + + + 2.1.0 + + + 2.1.0 + + + 2.1.0 + + + 2.1.0 + 16.205.1