namespace AIStudio.Settings; /// /// The type-independent part of the configuration metadata: which configuration plugin manages /// the setting, and in which way. /// /// /// The managed state lives here so that it can be processed without knowing the setting's type, /// e.g. when cleaning up settings whose configuration plugin was removed. /// public abstract record ConfigMetaBase(string SettingName) : IConfig { protected static SettingsManager SettingsManagerAccess => Program.SERVICE_PROVIDER.GetRequiredService(); protected static ILogger Log => Program.LOGGER_FACTORY.CreateLogger(nameof(ConfigMetaBase)); /// /// The persisted name of the configuration setting. /// public string SettingName { get; } = SettingName; /// /// Indicates whether the configuration is locked by a configuration plugin. /// public bool IsLocked { get; private set; } /// /// The ID of the plugin that locked this configuration. /// public Guid LockedByConfigPluginId { get; private set; } /// /// How this setting is managed by a configuration plugin, if at all. /// public ManagedConfigurationMode? ManagedMode { get; private set; } /// /// The ID of the plugin that currently provides an editable default value. /// public Guid EditableDefaultByConfigPluginId { get; private set; } /// /// The configuration plugins which contribute to this setting. /// /// /// Contributions are additive, so several configuration plugins may contribute at the same time /// and each of them keeps its own contribution. An organization might enable one preview feature /// for everybody and another one for a single department, for example. /// public abstract IReadOnlyCollection ContributingConfigPluginIds { get; } /// /// Indicates whether at least one configuration plugin contributes to this setting. /// public bool HasPluginContribution => this.ContributingConfigPluginIds.Count > 0; /// /// Locks the configuration state, indicating that it is controlled by a specific plugin. /// /// The ID of the plugin that is locking this configuration. public void LockConfiguration(Guid pluginId) { this.IsLocked = true; this.LockedByConfigPluginId = pluginId; this.ManagedMode = ManagedConfigurationMode.LOCKED; this.EditableDefaultByConfigPluginId = Guid.Empty; SettingsManagerAccess.ConfigurationData.ManagedLockedConfigurations[this.SettingName] = pluginId; } /// /// Restores persisted locked configuration metadata after settings were loaded. /// public void RestoreLockedConfiguration() { if (this.IsLocked || this.ManagedMode is not null) return; if (!SettingsManagerAccess.ConfigurationData.ManagedLockedConfigurations.TryGetValue(this.SettingName, out var pluginId) || pluginId == Guid.Empty) return; this.IsLocked = true; this.LockedByConfigPluginId = pluginId; this.ManagedMode = ManagedConfigurationMode.LOCKED; this.EditableDefaultByConfigPluginId = Guid.Empty; } /// /// Resets the locked state of the configuration, allowing it to be modified again. /// This will also reset the property to its default value. /// public void ResetLockedConfiguration() { SettingsManagerAccess.ConfigurationData.ManagedLockedConfigurations.Remove(this.SettingName); this.IsLocked = false; this.LockedByConfigPluginId = Guid.Empty; if (this.ManagedMode is ManagedConfigurationMode.LOCKED) this.ManagedMode = null; this.RestoreUserValueOrDefault(); } /// /// Unlocks the configuration state without changing the current value. /// public void UnlockConfiguration() { SettingsManagerAccess.ConfigurationData.ManagedLockedConfigurations.Remove(this.SettingName); this.IsLocked = false; this.LockedByConfigPluginId = Guid.Empty; if (this.ManagedMode is ManagedConfigurationMode.LOCKED) this.ManagedMode = null; } /// /// Marks the setting as having an editable default provided by a configuration plugin. /// public void SetEditableDefaultConfiguration(Guid pluginId) { SettingsManagerAccess.ConfigurationData.ManagedLockedConfigurations.Remove(this.SettingName); this.IsLocked = false; this.LockedByConfigPluginId = Guid.Empty; this.ManagedMode = ManagedConfigurationMode.EDITABLE_DEFAULT; this.EditableDefaultByConfigPluginId = pluginId; } /// /// Clears the editable-default state without changing the current value. /// public void ClearEditableDefaultConfiguration() { if (this.ManagedMode is ManagedConfigurationMode.EDITABLE_DEFAULT) this.ManagedMode = null; this.EditableDefaultByConfigPluginId = Guid.Empty; } /// /// Clears the editable-default state and hands the setting back to the user. /// /// /// Without a snapshot of the user's value, the current value stays as it is. That is the /// difference to a locked setting: the user was allowed to change an editable default all /// along, so its value is a plausible choice of theirs. Resetting it to the app's default would /// take away something nobody asked us to remove. /// /// /// True when the user has changed the value in the meantime. Their decision outlives the /// configuration plugin, so the snapshot is dropped instead of applied. /// public void ResetEditableDefaultConfiguration(bool keepCurrentValue) { this.ClearEditableDefaultConfiguration(); if (keepCurrentValue) this.ClearUserValueSnapshot(); else this.TryRestoreUserValueSnapshot(); } /// /// Removes the contribution of one configuration plugin without changing the current value. /// /// The configuration plugin whose contribution is removed. /// True when that plugin had a contribution, otherwise false. public abstract bool RemovePluginContribution(Guid configPluginId); /// /// Indicates whether the value the user had chosen before a configuration plugin took over /// this setting is still available. /// public bool HasUserValueSnapshot => SettingsManagerAccess.ConfigurationData.ManagedUserValueSnapshots.ContainsKey(this.SettingName); /// /// Remembers the current value as the user's value, so that it can be restored once no /// configuration plugin manages this setting anymore. /// /// /// Only an unmanaged setting holds a value which belongs to the user. When one configuration /// plugin takes a setting over from another, the current value belongs to the previous plugin, /// so the snapshot of the user's value must survive that handover untouched.

/// The persisted editable default counts as managed as well: unlike a locked setting, it is not /// restored into the in-memory state when the settings are loaded, so right after a start it is /// the only evidence that a configuration plugin is already in charge. ///
public void CaptureUserValueSnapshot() { if (this.ManagedMode is not null || SettingsManagerAccess.ConfigurationData.ManagedEditableDefaults.ContainsKey(this.SettingName)) return; var snapshots = SettingsManagerAccess.ConfigurationData.ManagedUserValueSnapshots; if (snapshots.ContainsKey(this.SettingName)) return; snapshots[this.SettingName] = this.SerializeCurrentValueAsJson(); } /// /// Restores the value the user had chosen before a configuration plugin took over this setting. /// /// /// The snapshot is consumed either way: when it cannot be applied, keeping it would mean trying /// the same broken value again on every start. /// /// True when a snapshot was available and could be applied, otherwise false. private bool TryRestoreUserValueSnapshot() { var snapshots = SettingsManagerAccess.ConfigurationData.ManagedUserValueSnapshots; if (!snapshots.Remove(this.SettingName, out var snapshot)) return false; return this.TrySetValueFromJson(snapshot); } /// /// Drops the snapshot of the user's value without changing the current value. /// /// True when a snapshot was dropped, otherwise false. public bool ClearUserValueSnapshot() => SettingsManagerAccess.ConfigurationData.ManagedUserValueSnapshots.Remove(this.SettingName); /// /// Serializes the current value the same way the managed states record it. /// /// /// This is meant for comparisons, e.g. to tell whether the user has changed an editable default /// in the meantime. It is not meant for restoring a value: the representation is lossy. /// public abstract string SerializeCurrentValue(); /// /// Restores the user's value, or falls back to the default value when no snapshot is available. /// /// /// Settings which a configuration plugin managed before this app version has no snapshot, and /// neither has a setting whose value the user never changed. The default value is the best /// answer in both cases. /// private void RestoreUserValueOrDefault() { if (this.TryRestoreUserValueSnapshot()) return; this.Reset(); } /// /// Serializes the current value as JSON, so that it can be restored without losing information. /// protected abstract string SerializeCurrentValueAsJson(); /// /// Applies a value which was serialized by SerializeCurrentValueAsJson. /// /// The serialized value. /// True when the value could be applied, otherwise false. protected abstract bool TrySetValueFromJson(string json); /// /// Resets the configuration property to its default value. /// protected abstract void Reset(); }