Imports System.IO Imports System.Security.Policy Imports System.Text.RegularExpressions Namespace configs Public Module ConfigsLoader Const MaxIntervalSeconds As Integer = 3600 'Max of 6 hours Public Const GridColumnAmount As Integer = 5 'Batch, DueOut, Product, Naar, Opmerking Sub New() Try Dim configsTextMapper As New ConfigsTextmapper 'Load the configs that change for every work post. Dim dynamicConfigFileName = (New CommandLineMapper).ConfigurationName LoadDynamicConfigLines(configsTextMapper.GetAllValues(Path.Combine(My.Application.Info.DirectoryPath, dynamicConfigFileName))) ValidateDynamicConfigs() 'Load the general configs. LoadGeneralConfigLines(configsTextMapper.GetAllValues(Path.Combine(My.Application.Info.DirectoryPath, "GeneralConfigs.txt"))) ValidateGeneralConfigs() Catch ex As Exception 'Show error message, it's just easier to handle it here. MessageBox.Show(ex.Message, "An error occured...", MessageBoxButtons.OK, MessageBoxIcon.Error) Application.Exit() End Try End Sub #Region "Help functions" ''' ''' Extract the identification of the config from the line. Example => 'identification'=600 ''' ''' ''' The identifier Private Function ExtractConfigIdentifier(configLine As String) As String Try 'Everything in front of the '='. Return configLine.Substring(0, configLine.IndexOf("=", StringComparison.Ordinal)).Trim() Catch ex As Exception Throw New Exception($"Invalid configuration entry. Can't extract the identifier for the config (the part before the '='): '{configLine}'.") End Try End Function ''' ''' Extract the value of the config from the line. Example => RefreshFrequence='value' ''' ''' ''' The identifier Private Function ExtractConfigValue(configLine As String) As String Try 'Everything after the '='. Return configLine.Substring(configLine.IndexOf("=", StringComparison.Ordinal) + 1).Trim() Catch ex As Exception Throw New Exception($"Invalid configuration entry. Can't extract the value for the config (the part after the '='): '{configLine}'.") End Try End Function ''' ''' Get the color corresponding to the hexadecimal string code. In seperate function for error handling. ''' ''' ''' Private Function GetColorFromHexStringValue(hexadecimalColorCode As String) As Color Try 'Convert to color and return. Return ColorTranslator.FromHtml(hexadecimalColorCode) Catch ex As Exception Throw New ConfigurationException($"The value '{hexadecimalColorCode}' cannot be converted to a color.") End Try End Function #End Region #Region "Dynamic configs" #Region "Properties" Public ReadOnly Property AppName As String Public ReadOnly Property AppId As String Public ReadOnly Property AppWidth As Integer Public ReadOnly Property AppHeight As Integer Public ReadOnly Property NumberOfGrids As Integer Private ReadOnly _gridLabelNames As New List(Of String) Public ReadOnly Property GridLabelNames As List(Of String) Get Return New List(Of String)(_gridLabelNames) End Get End Property Private ReadOnly _gridWidths As New List(Of Integer) Public ReadOnly Property GridWidths As List(Of Integer) Get Return New List(Of Integer)(_gridWidths) End Get End Property Private ReadOnly _gridHeights As New List(Of Integer) Public ReadOnly Property GridHeights As List(Of Integer) Get Return New List(Of Integer)(_gridHeights) End Get End Property Private ReadOnly _gridsColumnHeaderNames As New List(Of String()) Public ReadOnly Property GridColumnHeaderNames As List(Of String()) Get Return New List(Of String())(_gridsColumnHeaderNames) End Get End Property Private ReadOnly _numbersOfActivities As New List(Of Integer) Public ReadOnly Property NumbersOfActivities As List(Of Integer) Get Return New List(Of Integer)(_numbersOfActivities) End Get End Property Private ReadOnly _sqlQueries As New List(Of String) Public ReadOnly Property SqlQueries As List(Of String) Get Return New List(Of String)(_sqlQueries) End Get End Property Public ReadOnly Property NumberOfEmployees As Integer #End Region ''' ''' Loads in all the present configs specific for a work post. ''' ''' Private Sub LoadDynamicConfigLines(dynamicConfigLines As IReadOnlyCollection(Of String)) Try 'Loop through each line. For Each line In dynamicConfigLines 'Get key and value of a config entry. Dim identifier = ExtractConfigIdentifier(line) Dim value = ExtractConfigValue(line) If identifier.Equals("AppName") Then _AppName = value ElseIf identifier.Equals("AppID") Then _AppId = value ElseIf identifier.Equals("AppDimension") Then 'Split into width and height. Dim values = value.Split("x") 'Check if there are two values. If values.Count() <> 2 Then Throw New ConfigurationException("The dimensions of the program have an invalid format.") 'Save values. _AppWidth = values(0) 'Width _AppHeight = values(1) 'Height ElseIf identifier.Equals("NumberOfGrids") Then _NumberOfGrids = value ElseIf identifier.Contains("GridLabel") Then _gridLabelNames.Add(value) ElseIf identifier.Contains("DimensionsGrid") Then 'Split into width and height. Dim values = value.Split("x") 'Check if there are two values. If values.Count() <> 2 Then Throw New ConfigurationException("The dimensions of a grid have an invalid format.") 'Save values. _gridWidths.Add(values(0)) 'Width _gridHeights.Add(values(1)) 'Height ElseIf identifier.Contains("ColumnHeaderGrid") Then _gridsColumnHeaderNames.Add(value.Split(",")) ElseIf identifier.Contains("NumberOfActivitiesGrid") Then _numbersOfActivities.Add(value) ElseIf identifier.Contains("SQL") Then _sqlQueries.Add(value) ElseIf identifier.Equals("NumberOfEmployees") Then _NumberOfEmployees = value Else Throw New Exception($"Unknown configuration: '{line}'.") End If Next Catch ex As InvalidCastException Throw New ConfigurationException($"One of the dynamic config values has an invalid type.{vbNewLine}{ex.Message}") End Try End Sub ''' ''' Validate that all the required fields were present. ''' Private Sub ValidateDynamicConfigs() 'App name. If AppName Is Nothing OrElse AppName.Equals(String.Empty) Then Throw New ConfigurationException("App name is not defined or invalid.") End If 'App id. If AppId Is Nothing OrElse AppId.Equals(String.Empty) Then Throw New ConfigurationException("App id is not defined or invalid.") End If 'App width. If AppWidth = 0 Then Throw New ConfigurationException("The app width is not defined or too small.") ElseIf AppWidth < 0 Then Throw New ConfigurationException("The app width is too small.") End If 'App height. If AppHeight = 0 Then Throw New ConfigurationException("The app height is not defined or too small.") ElseIf AppHeight < 0 Then Throw New ConfigurationException("The app height is too small.") End If 'Number of grids. If _NumberOfGrids <= 0 Then Throw New ConfigurationException("Number of grids is not defined or invalid.") End If 'Grid label names. If _gridLabelNames.Count = 0 Then Throw New ConfigurationException("There are no grid labels defined.") ElseIf _gridLabelNames.Count <> NumberOfGrids Then Throw New ConfigurationException("The amount of 'grid labels' is not equal to the 'number of grids'.") End If 'Grid dimensions. If _gridWidths.Count = 0 Or _gridHeights.Count = 0 Then Throw New ConfigurationException("There are no grid dimensions defined.") ElseIf _gridWidths.Count <> NumberOfGrids Or _gridHeights.Count <> NumberOfGrids Then Throw New ConfigurationException("The amount of 'grid dimensions' is not equal to the 'number of grids'.") ElseIf _gridWidths.Any(Function(width) width <= 0) Then Throw New ConfigurationException("One or more of the 'grid dimensions' has a width that is too small.") ElseIf _gridHeights.Any(Function(height) height <= 0) Then Throw New ConfigurationException("One or more of the 'grid dimensions' has a height that is too small.") End If 'Column headers of grids. If _gridsColumnHeaderNames.Count = 0 Then Throw New ConfigurationException("There are no grid column header collections defined.") ElseIf _gridsColumnHeaderNames.Count <> NumberOfGrids Then Throw New ConfigurationException("The amount of 'grid column header collections' is not equal to the 'number of grids'.") ElseIf Not _gridsColumnHeaderNames.All(Function(headerNameCollection) headerNameCollection.Count() = GridColumnAmount) Then Throw New ConfigurationException($"The amount of 'grid column headers' is not equal to the number of headers: {GridColumnAmount}.") End If 'Numbers of activities. If _numbersOfActivities.Count = 0 Then Throw New ConfigurationException("There are no numbers of activities defined.") ElseIf _numbersOfActivities.Count <> NumberOfGrids Then Throw New ConfigurationException("The amount of 'activity numbers' is not equal to the 'number of grids'.") End If 'SQL queries. If _sqlQueries.Count = 0 Then Throw New ConfigurationException("There are no SQL queries defined.") ElseIf _sqlQueries.Count <> NumberOfGrids Then Throw New ConfigurationException("The amount of 'SQL queries' is not equal to the 'number of grids'.") End If 'Number of users. If NumberOfEmployees < 0 Then Throw New ConfigurationException("Number of users is not defined or invalid.") End If End Sub #End Region #Region "General configs" #Region "Properties" Public ReadOnly Property RefreshIntervalSeconds As Integer Public ReadOnly Property RefreshTimeStart As TimeSpan Public ReadOnly Property RefreshTimeEnd As TimeSpan Public ReadOnly Property CacheWebserviceUrl As Url Public ReadOnly Property StartBeforeTimeThreshold As TimeSpan Public ReadOnly Property StartAlmostTimeThreshold As TimeSpan #Region "Colors and images" Public ReadOnly Property AgColorBatch As Color Public ReadOnly Property AgColorProductGroup As Color Public ReadOnly Property AgColorTo As Color Public ReadOnly Property ActivityBeforeStatusImage As Image Public ReadOnly Property ActivityAlmostStatusImage As Image Public ReadOnly Property ActivityLateStatusImage As Image Private ReadOnly Property _colorsActivities As New Dictionary(Of ActivityStateEnum, Color) Public ReadOnly Property ColorsActivities As Dictionary(Of ActivityStateEnum, Color) Get Return New Dictionary(Of ActivityStateEnum, Color)(_colorsActivities) End Get End Property Private ReadOnly Property _colorsActivityReadOnly As New Dictionary(Of ActivityStateEnum, Color) Public ReadOnly Property ColorsActivitiesReadOnly As Dictionary(Of ActivityStateEnum, Color) Get Return New Dictionary(Of ActivityStateEnum, Color)(_colorsActivityReadOnly) End Get End Property Public ReadOnly Property AllFinishedActivitiesColor As Color Public ReadOnly Property EmployeePhotoPath As String Private ReadOnly _colorsEmployees As New List(Of Color) Public ReadOnly Property ColorsEmployees As List(Of Color) Get Return New List(Of Color)(_colorsEmployees) End Get End Property #End Region #End Region ''' ''' Loads in all the general configs. ''' ''' Private Sub LoadGeneralConfigLines(generalConfigLines As IReadOnlyCollection(Of String)) Try 'Loop through each line. For Each line In generalConfigLines 'Get key and value of a config entry. Dim identifier = ExtractConfigIdentifier(line) Dim value = ExtractConfigValue(line) If identifier.Equals("RefreshFrequence") Then _RefreshIntervalSeconds = value ElseIf identifier.Equals("RefreshTimeStart") Then _RefreshTimeStart = TimeSpan.FromHours(value) ElseIf identifier.Equals("RefreshTimeEnd") Then _RefreshTimeEnd = TimeSpan.FromHours(value) ElseIf identifier.Equals("CacheWebserviceURL") Then _CacheWebserviceUrl = New Url(value) ElseIf identifier.Equals("StartBeforeTimeThreshold") Then _StartBeforeTimeThreshold = TimeSpan.FromMinutes(value) ElseIf identifier.Equals("StartAlmostTimeThreshold") Then _StartAlmostTimeThreshold = TimeSpan.FromMinutes(value) ElseIf identifier.Equals("AgColorBatch") Then _AgColorBatch = GetColorFromHexStringValue(value) ElseIf identifier.Equals("AgColorProductGroup") Then _AgColorProductGroup = GetColorFromHexStringValue(value) ElseIf identifier.Equals("AgColorTo") Then _AgColorTo = GetColorFromHexStringValue(value) ElseIf identifier.Equals("ImageBefore") Then If File.Exists(value) Then _ActivityBeforeStatusImage = Image.FromFile(value) End If ElseIf identifier.Equals("ImageAlmost") Then If File.Exists(value) Then _ActivityAlmostStatusImage = Image.FromFile(value) End If ElseIf identifier.Equals("ImageLate") Then If File.Exists(value) Then _ActivityLateStatusImage = Image.FromFile(value) End If ElseIf identifier.Contains("AgColorActivityReadOnly") Then AddColorActivity(identifier, value, True) ElseIf identifier.Contains("AgColorActivity") Then AddColorActivity(identifier, value, False) ElseIf identifier.Equals("ColorActivityAllFinished") Then _AllFinishedActivitiesColor = GetColorFromHexStringValue(value) ElseIf identifier.Contains("ColorEmployee") Then _colorsEmployees.Add(GetColorFromHexStringValue(value)) ElseIf identifier.Equals("EmployeePhotoPath") Then _EmployeePhotoPath = value Else Throw New Exception($"Unknown configuration: '{line}'.") End If Next Catch ex As InvalidCastException Throw New ConfigurationException($"One of the general config values has an invalid type.{vbNewLine}{ex.Message}") Catch ex As Exception Throw New ConfigurationException($"One of the general config values is invalid.{vbNewLine}{ex.Message}") End Try End Sub ''' ''' Add a color for a due out state to the dictionary. ''' ''' The identifier of the state ''' The hexadecimal string value of the color ''' Whether or not the activity is readonly Private Sub AddColorActivity(identifier As String, value As String, isReadOnly As Boolean) Try 'Extract the state name. 'Invoke the Match method, all text after AgColorDueOut. Dim m As Match = Regex.Match(identifier, If(isReadOnly, "AgColorActivityReadOnly(.*)", "AgColorActivity(.*)")) If Not m.Success Then 'No results. Throw New ConfigurationException($"Could not get the state for assigning a color for an activity. Identifier is '{identifier}'.") End If 'If successful, extract the state name. Dim stateName = m.Groups(1).Value 'Convert string value to enum value. Dim enumValue = ActivityStateEnumMapper.GetEnumValueFromString(stateName) 'Add to library, key is the enum value, value is the color. If isReadOnly Then 'It's readonly. _colorsActivityReadOnly.Add(enumValue, GetColorFromHexStringValue(value)) Else 'It's not. _colorsActivities.Add(enumValue, GetColorFromHexStringValue(value)) End If Catch ex As Exception Throw New ConfigurationException($"Color activity with name: '{identifier}' and value '{value}' is invalid.{vbNewLine}{ex.Message}") End Try End Sub ''' ''' Validate that all the required fields are present. ''' Private Sub ValidateGeneralConfigs() 'Refresh interval. If RefreshIntervalSeconds <= 0 Then Throw New ConfigurationException("The refresh interval is too low.") ElseIf RefreshIntervalSeconds > MaxIntervalSeconds Then Throw New ConfigurationException($"Refresh interval is above the maximum of {MaxIntervalSeconds} seconds.") End If 'Refresh time start. If RefreshTimeStart = TimeSpan.Zero Then Throw New ConfigurationException("Refresh time start is not defined.") End If 'Refresh time end. If RefreshTimeEnd = TimeSpan.Zero Then Throw New ConfigurationException("Refresh time end is not defined.") End If 'Caché webservice URL. If CacheWebserviceUrl Is Nothing Then Throw New ConfigurationException("The Caché webservice URL is not defined.") End If 'Ag color batch. If AgColorBatch = Color.Empty Then Throw New ConfigurationException("The Ag color batch is not defined.") End If 'Ag color production group. If AgColorProductGroup = Color.Empty Then Throw New ConfigurationException("The Ag color production group is not defined.") End If 'Caché webservice URL. If AgColorTo = Color.Empty Then Throw New ConfigurationException("The Ag color to is not defined.") End If 'Before activity status image. If _ActivityBeforeStatusImage Is Nothing Then Throw New ConfigurationException("The activity before status image is not correctly defined.") End If 'Almost activity status image. If _ActivityAlmostStatusImage Is Nothing Then Throw New ConfigurationException("The activity almost status image is not correctly defined.") End If 'Late activity status image. If _ActivityLateStatusImage Is Nothing Then Throw New ConfigurationException("The activity late status image is not correctly defined.") End If 'Colors activities. If _colorsActivities.Keys.Count <> [Enum].GetValues(GetType(ActivityStateEnum)).Length Then 'Amount of enum values and entries in the dictionary differ. Throw New ConfigurationException($"The amount of colors defined for the different states of activities differs from the amount of states. There are {[Enum].GetValues(GetType(ActivityStateEnum)).Length} total states and {_colorsActivities.Keys.Count} colors are defined.") End If 'Colors activities readonly. If _colorsActivityReadOnly.Keys.Count <> ([Enum].GetValues(GetType(ActivityStateEnum)).Length) Then 'Amount of enum values and entries in the dictionary differ. ReadOnly doesn't have a color value for the 2 special activities. Throw New ConfigurationException($"The amount of colors defined for the different states of readonly activities differs from the amount of states. There are {[Enum].GetValues(GetType(ActivityStateEnum)).Length} total states and {_colorsActivityReadOnly.Keys.Count} colors are defined.") End If 'All finished activity color. If AllFinishedActivitiesColor = Color.Empty Then Throw New ConfigurationException("The color for all finished activities is not defined.") End If 'Colors users. If _colorsEmployees.Count < NumberOfEmployees Then Throw New ConfigurationException($"The colors for the employees are not defined or are fewer than the amount of users {NumberOfEmployees}.") End If If _EmployeePhotoPath Is String.Empty Then Throw New ConfigurationException("The path to employeephoto's is not defined.") End If End Sub #End Region End Module End Namespace