From c3171a16d416f2aa1ece9354d09aa1af7018ab48 Mon Sep 17 00:00:00 2001 From: AdmiringWorm Date: Tue, 20 Sep 2022 18:25:48 +0200 Subject: [PATCH] (#1443) Add ability to reset config with no ref lost This commit adds two new methods to the Chocolatey Configuration class that will be used to create a backup of the current values inside the Config class, without these being able to be changed. Once requested and the backup exists, we are then able to reset the Config file without loosing the reference to the class. This was something that is needed, as we rely in several places that the reference is updatable throughout the entire Chocolatey CLI codebase. This also means we can not replace the reference to the Config file itself without loosing the ability to make use of remembered arguments when multiple packages requires these to be used. --- .../configuration/ChocolateyConfiguration.cs | 83 +++++++++++++++++++ 1 file changed, 83 insertions(+) diff --git a/src/chocolatey/infrastructure.app/configuration/ChocolateyConfiguration.cs b/src/chocolatey/infrastructure.app/configuration/ChocolateyConfiguration.cs index 1e9d627c7e..cdc8713b10 100644 --- a/src/chocolatey/infrastructure.app/configuration/ChocolateyConfiguration.cs +++ b/src/chocolatey/infrastructure.app/configuration/ChocolateyConfiguration.cs @@ -30,6 +30,9 @@ namespace chocolatey.infrastructure.app.configuration [Serializable] public class ChocolateyConfiguration { + [NonSerialized] + private ChocolateyConfiguration _originalConfiguration; + public ChocolateyConfiguration() { RegularOutput = true; @@ -58,6 +61,86 @@ public ChocolateyConfiguration() #endif } + /// + /// Creates a backup of the current version of the configuration class. + /// + /// One or more objects in the class or child classes are not serializable. + public void start_backup() + { + // We de this the easy way to ensure that we have a clean copy + // of the original configuration file. + _originalConfiguration = this.deep_copy(); + } + + /// + /// Restore the backup that has previously been created to the initial + /// state, without making the class reference types the same to prevent + /// the initial configuration class being updated at the same time if a + /// value changes. + /// + /// Whether a backup that was previously made should be removed after resetting the configuration. + /// No backup has been created before trying to reset the current configuration, and removal of the backup was not requested. + /// + /// This call may make quite alot of allocations on the Gen0 heap, as such + /// it is best to keep the calls to this method at a minimum. + /// + public void reset_config(bool removeBackup = false) + { + if (_originalConfiguration == null) + { + if (removeBackup) + { + // If we will also be removing the backup, we do not care if it is already + // null or not, as that is the intended state when this method returns. + return; + } + + throw new InvalidOperationException("No backup has been created before trying to reset the current configuration, and removal of the backup was not requested."); + } + + var t = this.GetType(); + + foreach (var property in t.GetProperties(BindingFlags.Public | BindingFlags.Instance)) + { + try + { + var originalValue = property.GetValue(_originalConfiguration, new object[0]); + + if (removeBackup || property.DeclaringType.IsPrimitive || property.DeclaringType.IsValueType || property.DeclaringType == typeof(string)) + { + // If the property is a primitive, a value type or a string, then a copy of the value + // will be created by the .NET Runtime automatically, and we do not need to create a deep clone of the value. + // Additionally, if we will be removing the backup there is no need to create a deep copy + // for any reference types, as such we also sets the reference itself so it is not needed + // to allocate more memory. + property.SetValue(this, originalValue, new object[0]); + } + else if (originalValue != null) + { + // We need to do a deep copy of the value so it won't copy the reference itself, + // but rather the actual values we are interested in. + property.SetValue(this, originalValue.deep_copy(), new object[0]); + } + else + { + property.SetValue(this, null, new object[0]); + } + } + catch (Exception ex) + { + this.Log().Debug("Unable to restore the value for property '{0}' with the message '{1}'", property.Name, ex.Message); + } + } + + if (removeBackup) + { + // It is enough to set the original configuration to null to + // allow GC to clean it up the next time it runs on the stored Generation + // Heap Table. + _originalConfiguration = null; + } + } + // overrides public override string ToString() {