diff --git a/src/chocolatey/infrastructure.app/configuration/ChocolateyConfiguration.cs b/src/chocolatey/infrastructure.app/configuration/ChocolateyConfiguration.cs index 5a36efb131..549ac89cb8 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 do 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 a lot 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 set 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) + { + throw new ApplicationException("Unable to restore the value for the property '{0}'.".format_with(property.Name), ex); + } + } + + 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() { @@ -237,6 +320,7 @@ private void append_output(StringBuilder propertyValues, string append) [Obsolete("Side by Side installation is deprecated, and is pending removal in v2.0.0")] public bool AllowMultipleVersions { get; set; } + public bool AllowDowngrade { get; set; } public bool ForceDependencies { get; set; } public string DownloadChecksum { get; set; }