Fixed managed settings keeping their plugin value after the configuration plugin was removed

This commit is contained in:
Thorsten Sommer 2026-08-09 20:36:11 +02:00
parent 4bdf64b262
commit 6951f00135
Signed by untrusted user who does not match committer: tsommer
GPG Key ID: 371BBA77A02C0108
3 changed files with 56 additions and 3 deletions

View File

@ -139,6 +139,29 @@ public abstract record ConfigMetaBase(string SettingName) : IConfig
this.EditableDefaultByConfigPluginId = Guid.Empty; this.EditableDefaultByConfigPluginId = Guid.Empty;
} }
/// <summary>
/// Clears the editable-default state and hands the setting back to the user.
/// </summary>
/// <remarks>
/// 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.
/// </remarks>
/// <param name="keepCurrentValue">
/// 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.
/// </param>
public void ResetEditableDefaultConfiguration(bool keepCurrentValue)
{
this.ClearEditableDefaultConfiguration();
if (keepCurrentValue)
this.ClearUserValueSnapshot();
else
this.TryRestoreUserValueSnapshot();
}
/// <summary> /// <summary>
/// Removes the contribution of one configuration plugin without changing the current value. /// Removes the contribution of one configuration plugin without changing the current value.
/// </summary> /// </summary>

View File

@ -1024,7 +1024,7 @@ public static partial class ManagedConfiguration
case false when configMeta.ManagedMode is ManagedConfigurationMode.EDITABLE_DEFAULT case false when configMeta.ManagedMode is ManagedConfigurationMode.EDITABLE_DEFAULT
&& TryGetEditableDefaultState(settingName, out var editableDefaultStateToRemove) && TryGetEditableDefaultState(settingName, out var editableDefaultStateToRemove)
&& editableDefaultStateToRemove.ConfigPluginId == configPluginId: && editableDefaultStateToRemove.ConfigPluginId == configPluginId:
configMeta.ClearEditableDefaultConfiguration(); configMeta.ResetEditableDefaultConfiguration(HasUserChangedEditableDefault(configMeta, editableDefaultStateToRemove));
ClearEditableDefaultState(settingName); ClearEditableDefaultState(settingName);
break; break;
} }

View File

@ -361,6 +361,19 @@ public static partial class ManagedConfiguration
configMeta.RemovePluginContribution(contributingConfigPluginId); configMeta.RemovePluginContribution(contributingConfigPluginId);
wasChanged = true; wasChanged = true;
} }
//
// Finally, drop any snapshot of the user's value which nobody claims anymore. Without
// this, a setting which stopped being managed outside of the paths above would keep its
// snapshot in the settings file forever. The persisted editable default counts as a
// claim as well: it survives a configuration plugin which is deployed but could not be
// loaded, and that plugin is still in charge:
//
if (configMeta.ManagedMode is null && !TryGetEditableDefaultState(configMeta.SettingName, out _) && configMeta.ClearUserValueSnapshot())
{
Log.LogInformation($"Dropping the snapshot of the user's value for the setting '{configMeta.SettingName}': no configuration plugin manages it anymore.");
wasChanged = true;
}
} }
// Remove persisted states which belong to settings that do not exist anymore: // Remove persisted states which belong to settings that do not exist anymore:
@ -405,6 +418,13 @@ public static partial class ManagedConfiguration
wasChanged = true; wasChanged = true;
} }
foreach (var settingName in configurationData.ManagedUserValueSnapshots.Keys.Where(x => !registeredSettingNames.Contains(x)).ToList())
{
Log.LogInformation($"Removing the snapshot of the user's value for the setting '{settingName}': this setting does not exist anymore.");
configurationData.ManagedUserValueSnapshots.Remove(settingName);
wasChanged = true;
}
return wasChanged; return wasChanged;
} }
@ -447,7 +467,7 @@ public static partial class ManagedConfiguration
if (configMeta.ManagedMode is not ManagedConfigurationMode.EDITABLE_DEFAULT) if (configMeta.ManagedMode is not ManagedConfigurationMode.EDITABLE_DEFAULT)
return false; return false;
configMeta.ClearEditableDefaultConfiguration(); configMeta.ResetEditableDefaultConfiguration(keepCurrentValue: false);
return true; return true;
} }
@ -455,7 +475,17 @@ public static partial class ManagedConfiguration
return false; return false;
Log.LogInformation($"Clearing the editable default of the setting '{configMeta.SettingName}': the configuration plugin '{editableDefaultState.ConfigPluginId}' is not available anymore."); Log.LogInformation($"Clearing the editable default of the setting '{configMeta.SettingName}': the configuration plugin '{editableDefaultState.ConfigPluginId}' is not available anymore.");
configMeta.ClearEditableDefaultConfiguration(); configMeta.ResetEditableDefaultConfiguration(HasUserChangedEditableDefault(configMeta, editableDefaultState));
return ClearEditableDefaultState(configMeta.SettingName); return ClearEditableDefaultState(configMeta.SettingName);
} }
/// <summary>
/// Checks whether the user has changed an editable default themselves.
/// </summary>
/// <remarks>
/// The user may change an editable default at any time. When the current value is not the one
/// the configuration plugin applied last, the user decided against that value, and their
/// decision outlives the plugin.
/// </remarks>
private static bool HasUserChangedEditableDefault(ConfigMetaBase configMeta, ManagedEditableDefaultState editableDefaultState) => !string.Equals(configMeta.SerializeCurrentValue(), editableDefaultState.LastAppliedValue, StringComparison.Ordinal);
} }