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 = False Private _TestMode As Boolean = False ''' ''' The blueprint class from which the default config is created ''' Private ReadOnly _Blueprint As T Private ReadOnly _Serializer As XmlSerializer Public ReadOnly Property Config As T ''' ''' Creates a new instance of the ConfigManager ''' ''' ''' LogConfig instance ''' The path to check for a user config file, eg. AppData ''' The path to check for a computer config file, eg. ProgramData ''' Override values from ComputerConfig with UserConfig 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 ''' ''' Creates a new ConfigManager with a single (user)config path ''' ''' ''' Public Sub New(LogConfig As LogConfig, ConfigPath As String) MyClass.New(LogConfig, ConfigPath, ConfigPath, ForceUserConfig:=True) End Sub ''' ''' Save the current config object to `UserConfigPath` ''' ''' True if save was successful, False otherwise Public Function Save() As Boolean Try WriteToFile(_Config, _UserPath) Return True Catch ex As Exception _Logger.Error(ex) Return False End Try End Function ''' ''' Copies all properties from Source to Target, except those who have an attribute ''' listed in ExcludedAttributeTypes ''' ''' Config Class ''' Source config object ''' Target config object ''' List of Attribute type to exclude 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) CopyValues(oComputerConfig, Config) 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) ' Copy values from user config to final config If _ForceUserConfig Then CopyValues(oUserConfig, Config, New List(Of Type)) Else CopyValues(oUserConfig, Config, New List(Of Type) From { GetType(ConnectionStringAttribute), GetType(ConnectionStringTestAttribute) }) End If 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 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 ''' ''' Serialize a config object to byte array ''' ''' ''' 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 ''' ''' Write an object to disk as xml ''' ''' The object to write ''' The file name to write to 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 ''' ''' Reads an xml from disk and deserializes to object ''' ''' 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