diff --git a/app/MindWork AI Studio/Settings/ManagedConfiguration.Parsing.cs b/app/MindWork AI Studio/Settings/ManagedConfiguration.Parsing.cs new file mode 100644 index 00000000..d62d477c --- /dev/null +++ b/app/MindWork AI Studio/Settings/ManagedConfiguration.Parsing.cs @@ -0,0 +1,736 @@ +using System.Globalization; +using System.Linq.Expressions; + +using AIStudio.Settings.DataModel; + +using Lua; + +namespace AIStudio.Settings; + +public static partial class ManagedConfiguration +{ + /// + /// Attempts to process the configuration settings from a Lua table for enum types. + /// + /// + /// When the configuration is successfully processed, it updates the configuration metadata with the configured value. + /// Furthermore, it locks the managed state of the configuration metadata to the provided configuration plugin ID. + /// The setting's value is set to the configured value. + /// + /// The ID of the related configuration plugin. + /// The Lua table containing the settings to process. + /// The expression to select the configuration class. + /// The expression to select the property within the configuration class. + /// When true, the method will not apply any changes, but only check if the configuration can be read. + /// An unused parameter to help with type inference for enum types. You might ignore it when calling the method. + /// The type of the configuration class. + /// The type of the property within the configuration class. + /// True when the configuration was successfully processed, otherwise false. + public static bool TryProcessConfiguration( + Expression> configSelection, + Expression> propertyExpression, + Guid configPluginId, + LuaTable settings, + bool dryRun, + TValue? _ = default) + where TValue : Enum + { + // + // Handle configured enum values + // + + // Check if that configuration was registered: + if(!TryGet(configSelection, propertyExpression, out var configMeta)) + return false; + + var successful = false; + var configuredValue = configMeta.Default; + + // Step 1 -- try to read the Lua value out of the Lua table: + if(settings.TryGetValue(SettingsManager.ToSettingName(propertyExpression), out var configuredEnumValue)) + { + // Step 2 -- try to read the Lua value as a string: + if(configuredEnumValue.TryRead(out var configuredEnumText)) + { + // Step 3 -- try to parse the string as the enum type: + if (Enum.TryParse(typeof(TValue), configuredEnumText, true, out var configuredEnum)) + { + configuredValue = (TValue)configuredEnum; + successful = true; + } + } + } + + if(dryRun) + return successful; + + return HandleParsedValue(configPluginId, dryRun, successful, configMeta, configuredValue); + } + + /// + /// Attempts to process the configuration settings from a Lua table for ISpanParsable types. + /// + /// + /// When the configuration is successfully processed, it updates the configuration metadata with the configured value. + /// Furthermore, it locks the managed state of the configuration metadata to the provided configuration plugin ID. + /// The setting's value is set to the configured value. + /// + /// The ID of the related configuration plugin. + /// The Lua table containing the settings to process. + /// The expression to select the configuration class. + /// The expression to select the property within the configuration class. + /// When true, the method will not apply any changes, but only check if the configuration can be read. + /// An unused parameter to help with type inference. You might ignore it when calling the method. + /// The type of the configuration class. + /// The type of the property within the configuration class. + /// True when the configuration was successfully processed, otherwise false. + public static bool TryProcessConfiguration( + Expression> configSelection, + Expression> propertyExpression, + Guid configPluginId, + LuaTable settings, + bool dryRun, + ISpanParsable? _ = null) + where TValue : ISpanParsable + { + // + // Handle configured ISpanParsable values + // + + // Check if that configuration was registered: + if(!TryGet(configSelection, propertyExpression, out var configMeta)) + return false; + + var successful = false; + var configuredValue = configMeta.Default; + + // Step 1 -- try to read the Lua value out of the Lua table: + if (settings.TryGetValue(SettingsManager.ToSettingName(propertyExpression), out var configuredLuaValue)) + { + // Step 2a -- try to read the Lua value as a string: + if (configuredLuaValue.Type is LuaValueType.String && configuredLuaValue.TryRead(out var configuredLuaValueText)) + { + // Step 3 -- try to parse the string as the target type: + if (TValue.TryParse(configuredLuaValueText, CultureInfo.InvariantCulture, out var configuredParsedValue)) + { + configuredValue = configuredParsedValue; + successful = true; + } + } + + // Step 2b -- try to read the Lua value: + if(configuredLuaValue.TryRead(out var configuredLuaValueInstance)) + { + configuredValue = configuredLuaValueInstance; + successful = true; + } + } + + if(dryRun) + return successful; + + return HandleParsedValue(configPluginId, dryRun, successful, configMeta, configuredValue); + } + + /// + /// Attempts to process the configuration settings from a Lua table for string values. + /// + /// + /// When the configuration is successfully processed, it updates the configuration metadata with the configured value. + /// Furthermore, it locks the managed state of the configuration metadata to the provided configuration plugin ID. + /// The setting's value is set to the configured value. + /// + /// The ID of the related configuration plugin. + /// The Lua table containing the settings to process. + /// The expression to select the configuration class. + /// The expression to select the property within the configuration class. + /// When true, the method will not apply any changes, but only check if the configuration can be read. + /// The type of the configuration class. + /// True when the configuration was successfully processed, otherwise false. + public static bool TryProcessConfiguration( + Expression> configSelection, + Expression> propertyExpression, + Guid configPluginId, + LuaTable settings, + bool dryRun) + { + // + // Handle configured string values + // + + // Check if that configuration was registered: + if(!TryGet(configSelection, propertyExpression, out var configMeta)) + return false; + + var successful = false; + var configuredValue = configMeta.Default; + + // Step 1 -- try to read the Lua value out of the Lua table: + if(settings.TryGetValue(SettingsManager.ToSettingName(propertyExpression), out var configuredTextValue)) + { + // Step 2 -- try to read the Lua value as a string: + if(configuredTextValue.TryRead(out var configuredText)) + { + configuredValue = configuredText; + successful = true; + } + } + + return HandleParsedValue(configPluginId, dryRun, successful, configMeta, configuredValue); + } + + /// + /// Attempts to process the configuration settings from a Lua table for ISpanParsable list types. + /// + /// + /// When the configuration is successfully processed, it updates the configuration metadata with the configured value. + /// Furthermore, it locks the managed state of the configuration metadata to the provided configuration plugin ID. + /// The setting's value is set to the configured value. + /// + /// The ID of the related configuration plugin. + /// The Lua table containing the settings to process. + /// The expression to select the configuration class. + /// The expression to select the property within the configuration class. + /// When true, the method will not apply any changes but only check if the configuration can be read. + /// An unused parameter to help with type inference for ISpanParsable types. You might ignore it when calling the method. + /// The type of the configuration class. + /// The type of the property within the configuration class. It is also the type of the list + /// elements, which must implement ISpanParsable. + /// True when the configuration was successfully processed, otherwise false. + + // ReSharper disable MethodOverloadWithOptionalParameter + public static bool TryProcessConfiguration( + Expression> configSelection, + Expression>> propertyExpression, + Guid configPluginId, + LuaTable settings, + bool dryRun, + ISpanParsable? _ = null) + where TValue : ISpanParsable + { + // + // Handle configured ISpanParsable lists + // + + // Check if that configuration was registered: + if (!TryGet(configSelection, propertyExpression, out var configMeta)) + return false; + + var successful = false; + var configuredValue = configMeta.Default; + + // Step 1 -- try to read the Lua value (we expect a table) out of the Lua table: + if (settings.TryGetValue(SettingsManager.ToSettingName(propertyExpression), out var configuredLuaList) && + configuredLuaList.Type is LuaValueType.Table && + configuredLuaList.TryRead(out var valueTable)) + { + // Determine the length of the Lua table and prepare a list to hold the parsed values: + var len = valueTable.ArrayLength; + var list = new List(len); + + // Iterate over each entry in the Lua table: + for (var index = 1; index <= len; index++) + { + // Retrieve the Lua value at the current index: + var value = valueTable[index]; + + // Step 2a -- try to read the Lua value as a string: + if (value.Type is LuaValueType.String && value.TryRead(out var configuredLuaValueText)) + { + // Step 3 -- try to parse the string as the target type: + if (TValue.TryParse(configuredLuaValueText, CultureInfo.InvariantCulture, out var configuredParsedValue)) + list.Add(configuredParsedValue); + } + + // Step 2b -- try to read the Lua value: + if (value.TryRead(out var configuredLuaValueInstance)) + list.Add(configuredLuaValueInstance); + } + + configuredValue = list; + successful = true; + } + + if (dryRun) + return successful; + + return HandleParsedValue(configPluginId, dryRun, successful, configMeta, configuredValue); + } + + // ReSharper restore MethodOverloadWithOptionalParameter + + /// + /// Attempts to process the configuration settings from a Lua table for enum list types. + /// + /// + /// When the configuration is successfully processed, it updates the configuration metadata with the configured value. + /// Furthermore, it locks the managed state of the configuration metadata to the provided configuration plugin ID. + /// The setting's value is set to the configured value. + /// + /// The ID of the related configuration plugin. + /// The Lua table containing the settings to process. + /// The expression to select the configuration class. + /// The expression to select the property within the configuration class. + /// When true, the method will not apply any changes but only check if the configuration can be read. + /// The type of the configuration class. + /// The type of the property within the configuration class. It is also the type of the list + /// elements, which must be an enum. + /// True when the configuration was successfully processed, otherwise false. + public static bool TryProcessConfiguration( + Expression> configSelection, + Expression>> propertyExpression, + Guid configPluginId, + LuaTable settings, + bool dryRun) + where TValue : Enum + { + // + // Handle configured enum lists + // + + // Check if that configuration was registered: + if(!TryGet(configSelection, propertyExpression, out var configMeta)) + return false; + + var successful = false; + var configuredValue = configMeta.Default; + + // Step 1 -- try to read the Lua value (we expect a table) out of the Lua table: + if (settings.TryGetValue(SettingsManager.ToSettingName(propertyExpression), out var configuredLuaList) && + configuredLuaList.Type is LuaValueType.Table && + configuredLuaList.TryRead(out var valueTable)) + { + // Determine the length of the Lua table and prepare a list to hold the parsed values: + var len = valueTable.ArrayLength; + var list = new List(len); + + // Iterate over each entry in the Lua table: + for (var index = 1; index <= len; index++) + { + // Retrieve the Lua value at the current index: + var value = valueTable[index]; + + // Step 2 -- try to read the Lua value as a string: + if (value.Type is LuaValueType.String && value.TryRead(out var configuredLuaValueText)) + { + // Step 3 -- try to parse the string as the target type: + if (Enum.TryParse(typeof(TValue), configuredLuaValueText, true, out var configuredEnum)) + list.Add((TValue)configuredEnum); + } + } + + configuredValue = list; + successful = true; + } + + if(dryRun) + return successful; + + return HandleParsedValue(configPluginId, dryRun, successful, configMeta, configuredValue); + } + + /// + /// Attempts to process the configuration settings from a Lua table for string list types. + /// + /// + /// When the configuration is successfully processed, it updates the configuration metadata with the configured value. + /// Furthermore, it locks the managed state of the configuration metadata to the provided configuration plugin ID. + /// The setting's value is set to the configured value. + /// + /// The ID of the related configuration plugin. + /// The Lua table containing the settings to process. + /// The expression to select the configuration class. + /// The expression to select the property within the configuration class. + /// When true, the method will not apply any changes but only check if the configuration can be read. + /// The type of the configuration class. + /// True when the configuration was successfully processed, otherwise false. + public static bool TryProcessConfiguration( + Expression> configSelection, + Expression>> propertyExpression, + Guid configPluginId, + LuaTable settings, + bool dryRun) + { + // + // Handle configured string lists + // + + // Check if that configuration was registered: + if(!TryGet(configSelection, propertyExpression, out var configMeta)) + return false; + + var successful = false; + var configuredValue = configMeta.Default; + + // Step 1 -- try to read the Lua value (we expect a table) out of the Lua table: + if (settings.TryGetValue(SettingsManager.ToSettingName(propertyExpression), out var configuredLuaList) && + configuredLuaList.Type is LuaValueType.Table && + configuredLuaList.TryRead(out var valueTable)) + { + // Determine the length of the Lua table and prepare a list to hold the parsed values: + var len = valueTable.ArrayLength; + var list = new List(len); + + // Iterate over each entry in the Lua table: + for (var index = 1; index <= len; index++) + { + // Retrieve the Lua value at the current index: + var value = valueTable[index]; + + // Step 2 -- try to read the Lua value as a string: + if (value.Type is LuaValueType.String && value.TryRead(out var configuredLuaValueText)) + list.Add(configuredLuaValueText); + } + + configuredValue = list; + successful = true; + } + + if(dryRun) + return successful; + + return HandleParsedValue(configPluginId, dryRun, successful, configMeta, configuredValue); + } + + /// + /// Attempts to process the configuration settings from a Lua table for ISpanParsable set types. + /// + /// + /// When the configuration is successfully processed, it updates the configuration metadata with the configured value. + /// Furthermore, it locks the managed state of the configuration metadata to the provided configuration plugin ID. + /// The setting's value is set to the configured value. + /// + /// The ID of the related configuration plugin. + /// The Lua table containing the settings to process. + /// The expression to select the configuration class. + /// The expression to select the property within the configuration class. + /// When true, the method will not apply any changes but only check if the configuration can be read. + /// An unused parameter to help with type inference for ISpanParsable types. You might ignore it when calling the method. + /// The type of the configuration class. + /// The type of the property within the configuration class. It is also the type of the set + /// elements, which must implement ISpanParsable. + /// True when the configuration was successfully processed, otherwise false. + + // ReSharper disable MethodOverloadWithOptionalParameter + public static bool TryProcessConfiguration( + Expression> configSelection, + Expression>> propertyExpression, + Guid configPluginId, + LuaTable settings, + bool dryRun, + ISpanParsable? _ = null) + where TValue : ISpanParsable + { + // + // Handle configured ISpanParsable sets + // + + // Check if that configuration was registered: + if (!TryGet(configSelection, propertyExpression, out var configMeta)) + return false; + + var successful = false; + var configuredValue = configMeta.Default; + + // Step 1 -- try to read the Lua value (we expect a table) out of the Lua table: + if (settings.TryGetValue(SettingsManager.ToSettingName(propertyExpression), out var configuredLuaList) && + configuredLuaList.Type is LuaValueType.Table && + configuredLuaList.TryRead(out var valueTable)) + { + // Determine the length of the Lua table and prepare a set to hold the parsed values: + var len = valueTable.ArrayLength; + var set = new HashSet(len); + + // Iterate over each entry in the Lua table: + for (var index = 1; index <= len; index++) + { + // Retrieve the Lua value at the current index: + var value = valueTable[index]; + + // Step 2a -- try to read the Lua value as a string: + if (value.Type is LuaValueType.String && value.TryRead(out var configuredLuaValueText)) + { + // Step 3 -- try to parse the string as the target type: + if (TValue.TryParse(configuredLuaValueText, CultureInfo.InvariantCulture, out var configuredParsedValue)) + set.Add(configuredParsedValue); + } + + // Step 2b -- try to read the Lua value: + if (value.TryRead(out var configuredLuaValueInstance)) + set.Add(configuredLuaValueInstance); + } + + configuredValue = set; + successful = true; + } + + if (dryRun) + return successful; + + return HandleParsedValue(configPluginId, dryRun, successful, configMeta, configuredValue); + } + + // ReSharper restore MethodOverloadWithOptionalParameter + + /// + /// Attempts to process the configuration settings from a Lua table for enum set types. + /// + /// + /// When the configuration is successfully processed, it updates the configuration metadata with the configured value. + /// Furthermore, it locks the managed state of the configuration metadata to the provided configuration plugin ID. + /// The setting's value is set to the configured value. + /// + /// The ID of the related configuration plugin. + /// The Lua table containing the settings to process. + /// The expression to select the configuration class. + /// The expression to select the property within the configuration class. + /// When true, the method will not apply any changes but only check if the configuration can be read. + /// The type of the configuration class. + /// The type of the property within the configuration class. It is also the type of the set + /// elements, which must be an enum. + /// True when the configuration was successfully processed, otherwise false. + public static bool TryProcessConfiguration( + Expression> configSelection, + Expression>> propertyExpression, + Guid configPluginId, + LuaTable settings, + bool dryRun) + where TValue : Enum + { + // + // Handle configured enum sets + // + + // Check if that configuration was registered: + if(!TryGet(configSelection, propertyExpression, out var configMeta)) + return false; + + var successful = false; + var configuredValue = configMeta.Default; + + // Step 1 -- try to read the Lua value (we expect a table) out of the Lua table: + if (settings.TryGetValue(SettingsManager.ToSettingName(propertyExpression), out var configuredLuaList) && + configuredLuaList.Type is LuaValueType.Table && + configuredLuaList.TryRead(out var valueTable)) + { + // Determine the length of the Lua table and prepare a set to hold the parsed values: + var len = valueTable.ArrayLength; + var set = new HashSet(len); + + // Iterate over each entry in the Lua table: + for (var index = 1; index <= len; index++) + { + // Retrieve the Lua value at the current index: + var value = valueTable[index]; + + // Step 2 -- try to read the Lua value as a string: + if (value.Type is LuaValueType.String && value.TryRead(out var configuredLuaValueText)) + { + // Step 3 -- try to parse the string as the target type: + if (Enum.TryParse(typeof(TValue), configuredLuaValueText, true, out var configuredEnum)) + set.Add((TValue)configuredEnum); + } + } + + configuredValue = set; + successful = true; + } + + if(dryRun) + return successful; + + return HandleParsedValue(configPluginId, dryRun, successful, configMeta, configuredValue); + } + + /// + /// Attempts to process the configuration settings from a Lua table for string set types. + /// + /// + /// When the configuration is successfully processed, it updates the configuration metadata with the configured value. + /// Furthermore, it locks the managed state of the configuration metadata to the provided configuration plugin ID. + /// The setting's value is set to the configured value. + /// + /// The ID of the related configuration plugin. + /// The Lua table containing the settings to process. + /// The expression to select the configuration class. + /// The expression to select the property within the configuration class. + /// When true, the method will not apply any changes but only check if the configuration can be read. + /// The type of the configuration class. + /// True when the configuration was successfully processed, otherwise false. + public static bool TryProcessConfiguration( + Expression> configSelection, + Expression>> propertyExpression, + Guid configPluginId, + LuaTable settings, + bool dryRun) + { + // + // Handle configured string sets + // + + // Check if that configuration was registered: + if(!TryGet(configSelection, propertyExpression, out var configMeta)) + return false; + + var successful = false; + var configuredValue = configMeta.Default; + + // Step 1 -- try to read the Lua value (we expect a table) out of the Lua table: + if (settings.TryGetValue(SettingsManager.ToSettingName(propertyExpression), out var configuredLuaList) && + configuredLuaList.Type is LuaValueType.Table && + configuredLuaList.TryRead(out var valueTable)) + { + // Determine the length of the Lua table and prepare a set to hold the parsed values: + var len = valueTable.ArrayLength; + var set = new HashSet(len); + + // Iterate over each entry in the Lua table: + for (var index = 1; index <= len; index++) + { + // Retrieve the Lua value at the current index: + var value = valueTable[index]; + + // Step 2 -- try to read the Lua value as a string: + if (value.Type is LuaValueType.String && value.TryRead(out var configuredLuaValueText)) + set.Add(configuredLuaValueText); + } + + configuredValue = set; + successful = true; + } + + if(dryRun) + return successful; + + return HandleParsedValue(configPluginId, dryRun, successful, configMeta, configuredValue); + } + + /// + /// Attempts to process the configuration settings from a Lua table for string dictionary types. + /// + /// + /// When the configuration is successfully processed, it updates the configuration metadata with the configured value. + /// Furthermore, it locks the managed state of the configuration metadata to the provided configuration plugin ID. + /// The setting's value is set to the configured value. + /// + /// The ID of the related configuration plugin. + /// The Lua table containing the settings to process. + /// The expression to select the configuration class. + /// The expression to select the property within the configuration class. + /// When true, the method will not apply any changes but only check if the configuration can be read. + /// The type of the configuration class. + /// True when the configuration was successfully processed, otherwise false. + public static bool TryProcessConfiguration( + Expression> configSelection, + Expression>> propertyExpression, + Guid configPluginId, + LuaTable settings, + bool dryRun) + { + // + // Handle configured string dictionaries (both keys and values are strings) + // + + // Check if that configuration was registered: + if(!TryGet(configSelection, propertyExpression, out var configMeta)) + return false; + + var successful = false; + var configuredValue = configMeta.Default; + + // Step 1 -- try to read the Lua value (we expect a table) out of the Lua table: + if (settings.TryGetValue(SettingsManager.ToSettingName(propertyExpression), out var configuredLuaList) && + configuredLuaList.Type is LuaValueType.Table && + configuredLuaList.TryRead(out var valueTable)) + { + // Determine the length of the Lua table and prepare a dictionary to hold the parsed key-value pairs. + // Instead of using ArrayLength, we use HashMapCount to get the number of key-value pairs: + var len = valueTable.HashMapCount; + var dict = new Dictionary(len); + + // In order to iterate over all key-value pairs in the Lua table, we have to use TryGetNext. + // Thus, we initialize the previous key variable to Nil and keep calling TryGetNext until + // there are no more pairs: + var previousKey = LuaValue.Nil; + while(valueTable.TryGetNext(previousKey, out var pair)) + { + // Update the previous key for the next iteration: + previousKey = pair.Key; + + // Try to read both the key and the value as strings: + var hadKey = pair.Key.TryRead(out var key); + var hadValue = pair.Value.TryRead(out var value); + + // If both key and value were read successfully, add them to the dictionary: + if (hadKey && hadValue) + dict[key] = value; + } + + configuredValue = dict; + successful = true; + } + + if(dryRun) + return successful; + + return HandleParsedValue(configPluginId, dryRun, successful, configMeta, configuredValue); + } + + /// + /// Handles the parsed configuration value based on whether the parsing was successful and whether it's a dry run. + /// + /// The ID of the related configuration plugin. + /// When true, no changes will be applied. + /// Indicates whether the configuration value was successfully parsed. + /// The configuration metadata. + /// >The parsed configuration value. + /// The type of the configuration class. + /// The type of the configuration property value. + /// True when the configuration was successfully processed, otherwise false. + private static bool HandleParsedValue( + Guid configPluginId, + bool dryRun, + bool successful, + ConfigMeta configMeta, + TValue configuredValue) + { + if(dryRun) + return successful; + + switch (successful) + { + case true: + // + // Case: the setting was configured, and we could read the value successfully. + // + + // Set the configured value and lock the managed state: + configMeta.SetValue(configuredValue); + configMeta.LockManagedState(configPluginId); + break; + + case false when configMeta.IsLocked && configMeta.MangedByConfigPluginId == configPluginId: + // + // Case: the setting was configured previously, but we could not read the value successfully. + // This happens when the setting was removed from the configuration plugin. We handle that + // case only when the setting was locked and managed by the same configuration plugin. + // + // The other case, when the setting was locked and managed by a different configuration plugin, + // is handled by the IsConfigurationLeftOver method, which checks if the configuration plugin + // is still available. If it is not available, it resets the managed state of the + // configuration setting, allowing it to be reconfigured by a different plugin or left unchanged. + // + configMeta.ResetManagedState(); + break; + + case false: + // + // Case: the setting was not configured, or we could not read the value successfully. + // We do not change the setting, and it remains at whatever value it had before. + // + break; + } + + return successful; + } +} \ No newline at end of file diff --git a/app/MindWork AI Studio/Settings/ManagedConfiguration.Register.cs b/app/MindWork AI Studio/Settings/ManagedConfiguration.Register.cs new file mode 100644 index 00000000..bdd3c4c4 --- /dev/null +++ b/app/MindWork AI Studio/Settings/ManagedConfiguration.Register.cs @@ -0,0 +1,272 @@ +using System.Linq.Expressions; + +using AIStudio.Settings.DataModel; + +namespace AIStudio.Settings; + +public static partial class ManagedConfiguration +{ + /// + /// Registers a configuration setting with a default value. + /// + /// + /// When called from the JSON deserializer, the configSelection parameter will be null. + /// In this case, the method will return the default value without registering the setting. + /// + /// The expression to select the configuration class. + /// The expression to select the property within the configuration class. + /// The default value to use when the setting is not configured. + /// The type of the configuration class. + /// The type of the property within the configuration class. + /// The default value. + public static TValue Register( + Expression>? configSelection, + Expression> propertyExpression, + TValue defaultValue) + where TValue : struct + { + // When called from the JSON deserializer by using the standard constructor, + // we ignore the register call and return the default value: + if (configSelection is null) + return defaultValue; + + var configPath = Path(configSelection, propertyExpression); + + // If the metadata already exists for this configuration path, we return the default value: + if (METADATA.ContainsKey(configPath)) + return defaultValue; + + // Not registered yet, so we register it now: + METADATA[configPath] = new ConfigMeta(configSelection, propertyExpression) + { + Default = defaultValue, + }; + + return defaultValue; + } + + /// + /// Registers a configuration setting with a default value. + /// + /// + /// When called from the JSON deserializer, the configSelection parameter will be null. + /// In this case, the method will return the default value without registering the setting. + /// + /// The expression to select the configuration class. + /// The expression to select the property within the configuration class. + /// The default value to use when the setting is not configured. + /// The type of the configuration class. + /// The default value. + public static string Register( + Expression>? configSelection, + Expression> propertyExpression, + string defaultValue) + { + // When called from the JSON deserializer by using the standard constructor, + // we ignore the register call and return the default value: + if(configSelection is null) + return defaultValue; + + var configPath = Path(configSelection, propertyExpression); + + // If the metadata already exists for this configuration path, we return the default value: + if (METADATA.ContainsKey(configPath)) + return defaultValue; + + // Not registered yet, so we register it now: + METADATA[configPath] = new ConfigMeta(configSelection, propertyExpression) + { + Default = defaultValue, + }; + + return defaultValue; + } + + /// + /// Registers a configuration setting with a default value for a IList of TValue. + /// + /// + /// If the configSelection parameter is null, the method returns a list containing the default value + /// without registering the configuration setting. + /// + /// The expression to select the configuration class. + /// The expression to select the property within the configuration class. + /// The default value to use when the setting is not configured. + /// The type of the configuration class. + /// The type of the elements in the list within the configuration class. + /// A list containing the default value. + public static List Register( + Expression>? configSelection, + Expression>> propertyExpression, + TValue defaultValue) + { + // When called from the JSON deserializer by using the standard constructor, + // we ignore the register call and return the default value: + if(configSelection is null) + return [defaultValue]; + + var configPath = Path(configSelection, propertyExpression); + + // If the metadata already exists for this configuration path, we return the default value: + if (METADATA.ContainsKey(configPath)) + return [defaultValue]; + + // Not registered yet, so we register it now: + METADATA[configPath] = new ConfigMeta>(configSelection, propertyExpression) + { + Default = [defaultValue], + }; + + return [defaultValue]; + } + + /// + /// Registers a configuration setting with multiple default values. + /// + /// + /// When called with a null configSelection parameter, the method ignores the register call and directly returns the default values. + /// If the configuration path already exists in the metadata, the method also returns the default values without registering new metadata. + /// + /// The expression used to select the configuration class. + /// The expression used to select the property within the configuration class. + /// The list of default values to be used when the configuration setting is not defined. + /// The type of the configuration class. + /// The type of the elements within the property list. + /// The list of default values. + public static List Register( + Expression>? configSelection, + Expression>> propertyExpression, + IList defaultValues) + { + // When called from the JSON deserializer by using the standard constructor, + // we ignore the register call and return the default value: + if(configSelection is null) + return [..defaultValues]; + + var configPath = Path(configSelection, propertyExpression); + + // If the metadata already exists for this configuration path, we return the default value: + if (METADATA.ContainsKey(configPath)) + return [..defaultValues]; + + // Not registered yet, so we register it now: + METADATA[configPath] = new ConfigMeta>(configSelection, propertyExpression) + { + Default = [..defaultValues], + }; + + return [..defaultValues]; + } + + /// + /// Registers a configuration setting with a default value. + /// + /// + /// When called with a null configSelection, this method returns the default value without registering the setting. + /// + /// The expression to select the configuration class. + /// The expression to select the set within the configuration class. + /// The default value to use when the setting is not configured. + /// The type of the configuration class. + /// The type of the values within the set. + /// A set containing the default value. + public static HashSet Register( + Expression>? configSelection, + Expression>> propertyExpression, + TValue defaultValue) + { + // When called from the JSON deserializer by using the standard constructor, + // we ignore the register call and return the default value: + if (configSelection is null) + return [defaultValue]; + + var configPath = Path(configSelection, propertyExpression); + + // If the metadata already exists for this configuration path, we return the default value: + if (METADATA.ContainsKey(configPath)) + return [defaultValue]; + + // Not registered yet, so we register it now: + METADATA[configPath] = new ConfigMeta>(configSelection, propertyExpression) + { + Default = new HashSet { defaultValue }, + }; + + return [defaultValue]; + } + + /// + /// Registers a configuration setting with a collection of default values. + /// + /// + /// When the method is invoked with a null configSelection, the configuration path + /// is ignored, and the specified default values are returned without registration. + /// + /// The expression that selects the configuration class from the root Data model. + /// The expression to select the property within the configuration class. + /// The default collection of values to use when the setting is not configured. + /// The type of the configuration class from which the property is selected. + /// The type of the elements in the collection associated with the configuration property. + /// A set containing the default values. + public static HashSet Register( + Expression>? configSelection, + Expression>> propertyExpression, + IList defaultValues) + { + // When called from the JSON deserializer by using the standard constructor, + // we ignore the register call and return the default value: + if (configSelection is null) + return [..defaultValues]; + + var configPath = Path(configSelection, propertyExpression); + + // If the metadata already exists for this configuration path, we return the default value: + if (METADATA.ContainsKey(configPath)) + return [..defaultValues]; + + // Not registered yet, so we register it now: + METADATA[configPath] = new ConfigMeta>(configSelection, propertyExpression) + { + Default = new HashSet(defaultValues), + }; + + return [..defaultValues]; + } + + /// + /// Registers a configuration setting with a default dictionary of string key-value pairs. + /// + /// + /// When the method is invoked with a null configSelection, the configuration path + /// is ignored, and the specified default values are returned without registration. + /// + /// The expression that selects the configuration class from the root Data model. + /// The expression to select the property within the configuration class. + /// The default dictionary of values to use when the setting is not configured. + /// The type of the configuration class from which the property is selected. + /// A dictionary containing the default values. + public static Dictionary Register( + Expression>? configSelection, + Expression>> propertyExpression, + Dictionary defaultValues) + { + // When called from the JSON deserializer by using the standard constructor, + // we ignore the register call and return the default value: + if (configSelection is null) + return []; + + var configPath = Path(configSelection, propertyExpression); + + // If the metadata already exists for this configuration path, we return the default value: + if (METADATA.ContainsKey(configPath)) + return defaultValues; + + // Not registered yet, so we register it now: + METADATA[configPath] = new ConfigMeta>(configSelection, propertyExpression) + { + Default = defaultValues, + }; + + return defaultValues; + } +} \ No newline at end of file diff --git a/app/MindWork AI Studio/Settings/ManagedConfiguration.cs b/app/MindWork AI Studio/Settings/ManagedConfiguration.cs index 3767cd66..cdcfdd78 100644 --- a/app/MindWork AI Studio/Settings/ManagedConfiguration.cs +++ b/app/MindWork AI Studio/Settings/ManagedConfiguration.cs @@ -4,47 +4,11 @@ using System.Linq.Expressions; using AIStudio.Settings.DataModel; using AIStudio.Tools.PluginSystem; -using Lua; - namespace AIStudio.Settings; -public static class ManagedConfiguration +public static partial class ManagedConfiguration { private static readonly ConcurrentDictionary METADATA = new(); - - /// - /// Registers a configuration setting with a default value. - /// - /// - /// When called from the JSON deserializer, the configSelection parameter will be null. - /// In this case, the method will return the default value without registering the setting. - /// - /// The expression to select the configuration class. - /// The expression to select the property within the configuration class. - /// The default value to use when the setting is not configured. - /// The type of the configuration class. - /// The type of the property within the configuration class. - /// The default value. - public static TValue Register(Expression>? configSelection, Expression> propertyExpression, TValue defaultValue) - { - // When called from the JSON deserializer by using the standard constructor, - // we ignore the register call and return the default value: - if(configSelection is null) - return defaultValue; - - var configPath = Path(configSelection, propertyExpression); - - // If the metadata already exists for this configuration path, we return the default value: - if (METADATA.ContainsKey(configPath)) - return defaultValue; - - METADATA[configPath] = new ConfigMeta(configSelection, propertyExpression) - { - Default = defaultValue, - }; - - return defaultValue; - } /// /// Attempts to retrieve the configuration metadata for a given configuration selection and property expression. @@ -76,80 +40,6 @@ public static class ManagedConfiguration return false; } - /// - /// Attempts to process the configuration settings from a Lua table. - /// - /// - /// When the configuration is successfully processed, it updates the configuration metadata with the configured value. - /// Furthermore, it locks the managed state of the configuration metadata to the provided configuration plugin ID. - /// The setting's value is set to the configured value. - /// - /// The ID of the related configuration plugin. - /// The Lua table containing the settings to process. - /// The expression to select the configuration class. - /// The expression to select the property within the configuration class. - /// When true, the method will not apply any changes, but only check if the configuration can be read. - /// The type of the configuration class. - /// The type of the property within the configuration class. - /// True when the configuration was successfully processed, otherwise false. - public static bool TryProcessConfiguration(Expression> configSelection, Expression> propertyExpression, Guid configPluginId, LuaTable settings, bool dryRun) - { - if(!TryGet(configSelection, propertyExpression, out var configMeta)) - return false; - - var (configuredValue, successful) = configMeta.Default switch - { - Enum => settings.TryGetValue(SettingsManager.ToSettingName(propertyExpression), out var configuredEnumValue) && configuredEnumValue.TryRead(out var configuredEnumText) && Enum.TryParse(typeof(TValue), configuredEnumText, true, out var configuredEnum) ? ((TValue)configuredEnum, true) : (configMeta.Default, false), - Guid => settings.TryGetValue(SettingsManager.ToSettingName(propertyExpression), out var configuredGuidValue) && configuredGuidValue.TryRead(out var configuredGuidText) && Guid.TryParse(configuredGuidText, out var configuredGuid) ? ((TValue)(object)configuredGuid, true) : (configMeta.Default, false), - - string => settings.TryGetValue(SettingsManager.ToSettingName(propertyExpression), out var configuredTextValue) && configuredTextValue.TryRead(out var configuredText) ? ((TValue)(object)configuredText, true) : (configMeta.Default, false), - bool => settings.TryGetValue(SettingsManager.ToSettingName(propertyExpression), out var configuredBoolValue) && configuredBoolValue.TryRead(out var configuredState) ? ((TValue)(object)configuredState, true) : (configMeta.Default, false), - - int => settings.TryGetValue(SettingsManager.ToSettingName(propertyExpression), out var configuredIntValue) && configuredIntValue.TryRead(out var configuredInt) ? ((TValue)(object)configuredInt, true) : (configMeta.Default, false), - double => settings.TryGetValue(SettingsManager.ToSettingName(propertyExpression), out var configuredDoubleValue) && configuredDoubleValue.TryRead(out var configuredDouble) ? ((TValue)(object)configuredDouble, true) : (configMeta.Default, false), - float => settings.TryGetValue(SettingsManager.ToSettingName(propertyExpression), out var configuredFloatValue) && configuredFloatValue.TryRead(out var configuredFloat) ? ((TValue)(object)configuredFloat, true) : (configMeta.Default, false), - - _ => (configMeta.Default, false), - }; - - if(dryRun) - return successful; - - switch (successful) - { - case true: - // - // Case: the setting was configured, and we could read the value successfully. - // - configMeta.SetValue(configuredValue); - configMeta.LockManagedState(configPluginId); - break; - - case false when configMeta.IsLocked && configMeta.MangedByConfigPluginId == configPluginId: - // - // Case: the setting was configured previously, but we could not read the value successfully. - // This happens when the setting was removed from the configuration plugin. We handle that - // case only when the setting was locked and managed by the same configuration plugin. - // - // The other case, when the setting was locked and managed by a different configuration plugin, - // is handled by the IsConfigurationLeftOver method, which checks if the configuration plugin - // is still available. If it is not available, it resets the managed state of the - // configuration setting, allowing it to be reconfigured by a different plugin or left unchanged. - // - configMeta.ResetManagedState(); - break; - - case false: - // - // Case: the setting was not configured, or we could not read the value successfully. - // We do not change the setting, and it remains at whatever value it had before. - // - break; - } - - return successful; - } - /// /// Checks if a configuration setting is left over from a configuration plugin that is no longer available. /// If the configuration setting is locked and managed by a configuration plugin that is not available,