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