Modules/Config/ConfigManager.vb
2019-04-15 14:01:09 +02:00

261 lines
9.4 KiB
VB.net

Imports System.IO
Imports System.Reflection
Imports System.Xml.Serialization
Imports DigitalData.Modules.Logging
Imports DigitalData.Modules.Config.ConfigAttributes
Public Class ConfigManager(Of T)
Private Const USER_CONFIG_NAME As String = "UserConfig.xml"
Private Const COMPUTER_CONFIG_NAME As String = "ComputerConfig.xml"
Private ReadOnly _LogConfig As LogConfig
Private ReadOnly _Logger As Logger
Private ReadOnly _File As Filesystem.File
Private ReadOnly _UserPath As String
Private ReadOnly _ComputerPath As String
Private _ForceUserConfig As Boolean
''' <summary>
''' The blueprint class from which the default config is created
''' </summary>
Private ReadOnly _Blueprint As T
Private ReadOnly _Serializer As XmlSerializer
Public ReadOnly Property Config As T
''' <summary>
''' Creates a new instance of the ConfigManager
''' </summary>
''' <seealso cref="ConfigSample"/>
''' <param name="LogConfig">LogConfig instance</param>
''' <param name="UserConfigPath">The path to check for a user config file, eg. AppData</param>
''' <param name="ComputerConfigPath">The path to check for a computer config file, eg. ProgramData</param>
''' <param name="ForceUserConfig">Override values from ComputerConfig with UserConfig</param>
Public Sub New(LogConfig As LogConfig, UserConfigPath As String, ComputerConfigPath As String, Optional ForceUserConfig As Boolean = False)
_LogConfig = LogConfig
_Logger = LogConfig.GetLogger()
_File = New Filesystem.File(_LogConfig)
_UserPath = Path.Combine(UserConfigPath, USER_CONFIG_NAME)
_ComputerPath = Path.Combine(ComputerConfigPath, COMPUTER_CONFIG_NAME)
_ForceUserConfig = ForceUserConfig
_Blueprint = Activator.CreateInstance(Of T)
_Serializer = New XmlSerializer(_Blueprint.GetType)
If Not Directory.Exists(UserConfigPath) Then
_Logger.Debug("UserConfigPath {0} did not exist and was created", UserConfigPath)
Directory.CreateDirectory(UserConfigPath)
End If
If Not _File.TestPathIsDirectory(UserConfigPath) Then
_Logger.Warn("UserConfigPath {0} is not a directory", UserConfigPath)
Throw New ArgumentException($"Path {UserConfigPath} is not a directory!")
End If
If Not Directory.Exists(ComputerConfigPath) Then
_Logger.Debug("ComputerConfigPath {0} did not exist and was created", ComputerConfigPath)
Directory.CreateDirectory(ComputerConfigPath)
End If
If Not _File.TestPathIsDirectory(ComputerConfigPath) Then
_Logger.Warn("ComputerConfigPath {0} is not a directory", ComputerConfigPath)
Throw New ArgumentException($"Path {ComputerConfigPath} is not a directory!")
End If
_Config = LoadConfig()
End Sub
''' <summary>
''' Creates a new ConfigManager with a single (user)config path
''' </summary>
''' <param name="LogConfig"></param>
''' <param name="ConfigPath"></param>
Public Sub New(LogConfig As LogConfig, ConfigPath As String)
MyClass.New(LogConfig, ConfigPath, ConfigPath, ForceUserConfig:=True)
End Sub
''' <summary>
''' Save the current config object to `UserConfigPath`
''' </summary>
''' <returns>True if save was successful, False otherwise</returns>
Public Function Save() As Boolean
Try
WriteToFile(_Config, _UserPath)
Return True
Catch ex As Exception
_Logger.Error(ex)
Return False
End Try
End Function
Private Sub CopyValues(Of T)(Source As T, Target As T, Optional ExcludedAttributeTypes As List(Of Type) = Nothing)
Dim oType As Type = GetType(T)
Dim oExcludedAttributeTypes = IIf(IsNothing(ExcludedAttributeTypes), New List(Of Type), ExcludedAttributeTypes)
Dim oProperties = oType.GetProperties().
Where(Function(p) p.CanRead And p.CanWrite).
Where(Function(p)
For Each oAttributeType As Type In oExcludedAttributeTypes
If Attribute.IsDefined(p, oAttributeType) Then
Return False
End If
Next
Return True
End Function)
For Each oProperty As PropertyInfo In oProperties
Dim oValue = oProperty.GetValue(Source, Nothing)
If Not IsNothing(oValue) Then
oProperty.SetValue(Target, oValue, Nothing)
End If
Next
End Sub
Private Function LoadConfig() As T
' first create an empty/default config object
Dim oConfig = Activator.CreateInstance(_Blueprint.GetType)
' then Try to load computer config
oConfig = LoadComputerConfig(oConfig)
' now try to load userconfig
oConfig = LoadUserConfig(oConfig)
Return oConfig
End Function
Private Function LoadComputerConfig(ByVal Config As T) As T
If File.Exists(_ComputerPath) Then
Try
Dim oComputerConfig = ReadFromFile(_ComputerPath)
' if a computer config exists, copy values
' from computer config to final config
If Not IsNothing(oComputerConfig) Then
CopyValues(oComputerConfig, Config)
End If
Catch ex As Exception
_Logger.Error(ex)
_Logger.Warn("Computer config could not be loaded!")
End Try
Else
_ForceUserConfig = True
End If
Return Config
End Function
Private Function LoadUserConfig(ByVal Config As T) As T
If File.Exists(_UserPath) Then
Try
Dim oUserConfig = ReadFromFile(_UserPath)
' if user config exists
If Not IsNothing(oUserConfig) Then
Dim oExcludedAttributes As New List(Of Type)
If Not _ForceUserConfig Then
oExcludedAttributes.Add(GetType(ConnectionStringAttribute))
End If
' Copy values from user config to final config
CopyValues(oUserConfig, Config, oExcludedAttributes)
End If
'Dim oConnectionProperty = TestHasAttribute(oConfig, GetType(ConnectionStringAttribute))
Catch ex As Exception
_Logger.Error(ex)
_Logger.Warn("User config could not be loaded!")
End Try
End If
Return Config
End Function
Private Function LoadDefaultConfig() As T
_Logger.Debug("Creating default config in UserPath: {0}", _UserPath)
Dim oConfig = Activator.CreateInstance(_Blueprint.GetType)
Try
WriteToFile(oConfig, _UserPath)
Catch ex As Exception
_Logger.Warn("Could not create default config in UserPath: {0}", _UserPath)
End Try
Return oConfig
End Function
Private Function TestHasAttribute(Config As T, AttributeType As Type) As String
For Each oProperty As PropertyInfo In Config.GetType.GetProperties()
If Attribute.IsDefined(oProperty, GetType(ConnectionStringAttribute)) Then
Return oProperty.Name
End If
Next
Return Nothing
End Function
''' <summary>
''' Serialize a config object to byte array
''' </summary>
''' <param name="Data"></param>
''' <returns></returns>
Private Function Serialize(Data As T) As Byte()
Try
_Logger.Debug("Serializing config object")
Using oStream = New MemoryStream()
_Serializer.Serialize(oStream, Data)
Return oStream.ToArray()
End Using
Catch ex As Exception
_Logger.Error(ex)
Throw ex
End Try
End Function
''' <summary>
''' Write an object to disk as xml
''' </summary>
''' <param name="Data">The object to write</param>
''' <param name="Path">The file name to write to</param>
Private Sub WriteToFile(Data As T, Path As String)
Try
_Logger.Debug("Saving config to: {0}", Path)
Dim oBytes = Serialize(Data)
Using oFileStream = New FileStream(Path, FileMode.Create, FileAccess.Write)
oFileStream.Write(oBytes, 0, oBytes.Length)
oFileStream.Flush()
End Using
Catch ex As Exception
_Logger.Error(ex)
Throw ex
End Try
End Sub
''' <summary>
''' Reads an xml from disk and deserializes to object
''' </summary>
''' <returns></returns>
Private Function ReadFromFile(Path As String) As T
Try
_Logger.Debug("Loading config from: {0}", Path)
Dim oConfig As T
Using oReader As New StreamReader(Path)
oConfig = _Serializer.Deserialize(oReader)
End Using
' If oConfig is Nothing, a config file was created but nothing was written to it.
' In this case we need to create oConfig from defaults so we have at least some config object
If oConfig Is Nothing Then
_Logger.Debug("Config file is valid but empty. Loading default values")
oConfig = Activator.CreateInstance(_Blueprint.GetType)
End If
Return oConfig
Catch ex As Exception
_Logger.Error(ex)
Throw ex
End Try
End Function
End Class