388 lines
14 KiB
VB.net
388 lines
14 KiB
VB.net
Imports System.IO
|
|
Imports System.Reflection
|
|
Imports System.Xml.Serialization
|
|
Imports DigitalData.Modules.Logging
|
|
Imports DigitalData.Modules.Encryption
|
|
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 Const APP_CONFIG_NAME As String = "AppConfig.xml"
|
|
|
|
Private ReadOnly _LogConfig As LogConfig
|
|
Private ReadOnly _Logger As Logger
|
|
Private ReadOnly _File As Filesystem.File
|
|
|
|
Private ReadOnly _UserDirectory As String
|
|
Private ReadOnly _UserConfigPath As String
|
|
Private ReadOnly _ComputerDirectory As String
|
|
Private ReadOnly _ComputerConfigPath As String
|
|
Private ReadOnly _AppConfigDirectory As String
|
|
Private ReadOnly _AppConfigPath As String
|
|
|
|
Private ReadOnly _TestMode As Boolean = False
|
|
|
|
Private ReadOnly _Blueprint As T
|
|
Private ReadOnly _BlueprintType As Type
|
|
Private ReadOnly _Serializer As XmlSerializer
|
|
|
|
Private ReadOnly _ExcludedAttributes = New List(Of Type) From {
|
|
GetType(ConnectionStringAttribute),
|
|
GetType(ConnectionStringAppServerAttribute),
|
|
GetType(ConnectionStringTestAttribute),
|
|
GetType(EDMIAppServerAttribute),
|
|
GetType(GlobalSettingAttribute)
|
|
}
|
|
|
|
Private ReadOnly _ConnectionStringAttributes = New List(Of Type) From {
|
|
GetType(ConnectionStringAttribute),
|
|
GetType(ConnectionStringAppServerAttribute),
|
|
GetType(ConnectionStringTestAttribute)
|
|
}
|
|
|
|
''' <summary>
|
|
''' Signals that all properties will be written to (and read from) the UserConfig.xml
|
|
'''
|
|
''' If Value is `True`:
|
|
''' - AppConfig.xml does NOT exist
|
|
''' - ComputerConfig.xml does NOT exist
|
|
''' - ConnectionStrings will be saved to or read from UserConfig.xml
|
|
'''
|
|
''' If Value is `False`:
|
|
''' - No ConnectionStrings will be saved to or read from UserConfig.xml
|
|
'''
|
|
''' Can be overwritten by optional parameter `ForceUserConfig`
|
|
''' </summary>
|
|
Private _WriteAllValuesToUserConfig As Boolean = False
|
|
|
|
''' <summary>
|
|
''' Returns the currently loaded config object
|
|
''' </summary>
|
|
''' <returns></returns>
|
|
Public ReadOnly Property Config As T
|
|
|
|
''' <summary>
|
|
''' Path to the current user config.
|
|
''' </summary>
|
|
''' <returns></returns>
|
|
Public ReadOnly Property UserConfigPath As String
|
|
Get
|
|
Return _UserConfigPath
|
|
End Get
|
|
End Property
|
|
|
|
''' <summary>
|
|
''' Path to the current computer config.
|
|
''' </summary>
|
|
''' <returns></returns>
|
|
Public ReadOnly Property ComputerConfigPath As String
|
|
Get
|
|
Return _ComputerConfigPath
|
|
End Get
|
|
End Property
|
|
|
|
''' <summary>
|
|
''' Path to the current Application config.
|
|
''' </summary>
|
|
''' <returns></returns>
|
|
Public ReadOnly Property AppConfigPath As String
|
|
Get
|
|
Return _AppConfigPath
|
|
End Get
|
|
End Property
|
|
|
|
''' <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 (Usually Application.UserAppDataPath or Application.LocalUserAppDataPath)</param>
|
|
''' <param name="ComputerConfigPath">The path to check for a computer config file, eg. ProgramData (Usually Application.CommonAppDataPath)</param>
|
|
''' <param name="ApplicationStartupPath">The path to check for a third config file. This is useful when running the Application in an environment where AppData/ProgramData directories are not available</param>
|
|
''' <param name="ForceUserConfig">Override values from ComputerConfig with UserConfig</param>
|
|
Public Sub New(LogConfig As LogConfig, UserConfigPath As String, ComputerConfigPath As String, Optional ApplicationStartupPath As String = "", Optional ForceUserConfig As Boolean = False)
|
|
_LogConfig = LogConfig
|
|
_Logger = LogConfig.GetLogger()
|
|
_File = New Filesystem.File(_LogConfig)
|
|
|
|
_Blueprint = Activator.CreateInstance(Of T)
|
|
_BlueprintType = _Blueprint.GetType
|
|
_Serializer = New XmlSerializer(_BlueprintType)
|
|
|
|
_UserDirectory = _File.CreateDirectory(UserConfigPath)
|
|
_UserConfigPath = Path.Combine(_UserDirectory, USER_CONFIG_NAME)
|
|
|
|
If ComputerConfigPath <> String.Empty Then
|
|
If IO.File.Exists(ComputerConfigPath) Then
|
|
_ComputerDirectory = _File.CreateDirectory(ComputerConfigPath, False)
|
|
Else
|
|
_ComputerDirectory = ComputerConfigPath
|
|
End If
|
|
_ComputerConfigPath = Path.Combine(_ComputerDirectory, COMPUTER_CONFIG_NAME)
|
|
End If
|
|
|
|
If ApplicationStartupPath <> String.Empty Then
|
|
_Logger.Info($"AppConfig is being used: [{ApplicationStartupPath}]")
|
|
_AppConfigPath = Path.Combine(ApplicationStartupPath, APP_CONFIG_NAME)
|
|
End If
|
|
|
|
_WriteAllValuesToUserConfig = ForceUserConfig
|
|
|
|
_Config = LoadConfig()
|
|
End Sub
|
|
|
|
''' <summary>
|
|
''' Creates a new ConfigManager with a single (user)config path
|
|
''' </summary>
|
|
''' <param name="LogConfig">LogConfig instance</param>
|
|
''' <param name="ConfigPath">The path to check for a user config file, eg. AppData (Usually Application.UserAppDataPath or Application.LocalUserAppDataPath)</param>
|
|
Public Sub New(LogConfig As LogConfig, ConfigPath As String)
|
|
MyClass.New(LogConfig, ConfigPath, String.Empty, String.Empty, ForceUserConfig:=True)
|
|
End Sub
|
|
|
|
''' <summary>
|
|
''' Save the current config object to `UserConfigPath`
|
|
''' </summary>
|
|
''' <param name="ForceAll">Force saving all attributes including the attributes marked as excluded</param>
|
|
''' <returns>True if save was successful, False otherwise</returns>
|
|
Public Function Save(Optional ForceAll As Boolean = False) As Boolean
|
|
Try
|
|
WriteToFile(Config, _UserConfigPath, ForceAll)
|
|
Return True
|
|
Catch ex As Exception
|
|
_Logger.Error(ex)
|
|
Return False
|
|
End Try
|
|
End Function
|
|
|
|
''' <summary>
|
|
''' Reloads the config object from file.
|
|
''' </summary>
|
|
''' <returns>True if reload was successful, False otherwise</returns>
|
|
Public Function Reload() As Boolean
|
|
Try
|
|
_Config = LoadConfig()
|
|
Return True
|
|
Catch ex As Exception
|
|
_Logger.Error(ex)
|
|
Return False
|
|
End Try
|
|
End Function
|
|
|
|
''' <summary>
|
|
''' Copies all properties from Source to Target, except those who have an attribute
|
|
''' listed in ExcludedAttributeTypes
|
|
''' </summary>
|
|
''' <param name="Source">Source config object</param>
|
|
''' <param name="Target">Target config object</param>
|
|
''' <param name="ExcludedAttributeTypes">List of Attribute type to exclude</param>
|
|
Private Sub CopyValues(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
|
|
' TODO: Process individual Subfields of class-objects
|
|
' to allow for the PasswordAttribute to be set on class properies aka nested properties
|
|
|
|
Dim oValue = oProperty.GetValue(Source, Nothing)
|
|
If Not IsNothing(oValue) Then
|
|
oProperty.SetValue(Target, oValue, Nothing)
|
|
End If
|
|
Next
|
|
End Sub
|
|
|
|
''' <summary>
|
|
''' Filters a config object by copying all values except `ExcludedAttributeTypes`
|
|
''' </summary>
|
|
''' <param name="Data">Config object</param>
|
|
''' <param name="ExcludedAttributeTypes">List of Attribute type to exclude</param>
|
|
''' <returns></returns>
|
|
Private Function FilterValues(ByVal Data As T, ExcludedAttributeTypes As List(Of Type)) As T
|
|
Dim oResult As T = Activator.CreateInstance(Of T)
|
|
|
|
CopyValues(Data, oResult, ExcludedAttributeTypes)
|
|
Return oResult
|
|
End Function
|
|
|
|
Private Function LoadConfig() As T
|
|
' first create an empty/default config object
|
|
Dim oConfig As T = Activator.CreateInstance(_BlueprintType)
|
|
|
|
' try to load the special app config
|
|
oConfig = LoadAppConfig(oConfig)
|
|
|
|
' try to load the computer config
|
|
oConfig = LoadComputerConfig(oConfig)
|
|
|
|
' now try to load userconfig
|
|
oConfig = LoadUserConfig(oConfig)
|
|
Return oConfig
|
|
End Function
|
|
|
|
Private Function LoadAppConfig(ByVal Config As T) As T
|
|
If Not String.IsNullOrEmpty(_AppConfigPath) AndAlso File.Exists(_AppConfigPath) Then
|
|
Try
|
|
Dim oAppConfig = ReadFromFile(_AppConfigPath)
|
|
CopyValues(oAppConfig, Config)
|
|
|
|
_Logger.Info("AppConfig exists and will be used. [{0}]", _AppConfigPath)
|
|
Catch ex As Exception
|
|
_Logger.Error(ex)
|
|
_Logger.Warn("ApplicationConfig could not be loaded!")
|
|
End Try
|
|
|
|
_WriteAllValuesToUserConfig = False
|
|
Else
|
|
_Logger.Debug("ApplicationConfig does not exist.")
|
|
_WriteAllValuesToUserConfig = True
|
|
End If
|
|
|
|
Return Config
|
|
End Function
|
|
|
|
Private Function LoadComputerConfig(ByVal Config As T) As T
|
|
If _WriteAllValuesToUserConfig = False Then
|
|
_Logger.Info("AppConfig exists. ComputerConfig will NOT be used")
|
|
ElseIf File.Exists(_ComputerConfigPath) Then
|
|
Try
|
|
Dim oComputerConfig = ReadFromFile(_ComputerConfigPath)
|
|
CopyValues(oComputerConfig, Config)
|
|
|
|
_Logger.Info("ComputerConfig exists and will be used. [{0}]", _ComputerConfigPath)
|
|
Catch ex As Exception
|
|
_Logger.Error(ex)
|
|
_Logger.Warn("Computer config could not be loaded!")
|
|
End Try
|
|
_WriteAllValuesToUserConfig = False
|
|
Else
|
|
_Logger.Debug("Computer config does not exist.")
|
|
_WriteAllValuesToUserConfig = True
|
|
End If
|
|
|
|
Return Config
|
|
End Function
|
|
|
|
Private Function LoadUserConfig(ByVal Config As T) As T
|
|
If File.Exists(_UserConfigPath) Then
|
|
Try
|
|
Dim oUserConfig = ReadFromFile(_UserConfigPath)
|
|
_Logger.Debug("UserConfig exists and will be used. [{0}]", _UserConfigPath)
|
|
|
|
' if user config exists
|
|
If Not IsNothing(oUserConfig) Then
|
|
' Copy values from user config to final config
|
|
If _WriteAllValuesToUserConfig Then
|
|
CopyValues(oUserConfig, Config, New List(Of Type))
|
|
Else
|
|
CopyValues(oUserConfig, Config, _ExcludedAttributes)
|
|
End If
|
|
End If
|
|
Catch ex As Exception
|
|
_Logger.Error(ex)
|
|
_Logger.Warn("User config could not be loaded!")
|
|
End Try
|
|
Else
|
|
_Logger.Debug("User config does not exist. Default config will be created")
|
|
WriteToFile(Config, _UserConfigPath, False)
|
|
End If
|
|
|
|
Return Config
|
|
End Function
|
|
|
|
Private Function TestHasAttribute(Config As T, AttributeType As Type) As Boolean
|
|
For Each oProperty As PropertyInfo In Config.GetType.GetProperties()
|
|
If Attribute.IsDefined(oProperty, GetType(ConnectionStringAttribute)) Then
|
|
Return True
|
|
End If
|
|
Next
|
|
|
|
Return False
|
|
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, ForceAll As Boolean)
|
|
Try
|
|
_Logger.Debug("Saving config to: {0}", Path)
|
|
|
|
' If config was loaded from computer config,
|
|
' DO NOT save connection string, etc. to user config
|
|
If _WriteAllValuesToUserConfig = False And ForceAll = False Then
|
|
Data = FilterValues(Data, _ExcludedAttributes)
|
|
End If
|
|
|
|
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.Warn("Could not save config to {0}", Path)
|
|
_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(_BlueprintType)
|
|
End If
|
|
|
|
Return oConfig
|
|
Catch ex As Exception
|
|
_Logger.Warn("Could not load config from {0}", Path)
|
|
_Logger.Error(ex)
|
|
Throw ex
|
|
End Try
|
|
End Function
|
|
End Class
|