Skip to content

Commit

Permalink
(chocolatey#1443) Add ability to reset config with no ref lost
Browse files Browse the repository at this point in the history
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.
  • Loading branch information
AdmiringWorm committed Sep 21, 2022
1 parent aa184b3 commit 777e476
Showing 1 changed file with 83 additions and 0 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -30,6 +30,9 @@ namespace chocolatey.infrastructure.app.configuration
[Serializable]
public class ChocolateyConfiguration
{
[NonSerialized]
private ChocolateyConfiguration _originalConfiguration;

public ChocolateyConfiguration()
{
RegularOutput = true;
Expand Down Expand Up @@ -58,6 +61,86 @@ public ChocolateyConfiguration()
#endif
}

/// <summary>
/// Creates a backup of the current version of the configuration class.
/// </summary>
/// <exception cref="System.Runtime.Serialization.SerializationException">One or more objects in the class or child classes are not serializable.</exception>
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();
}

/// <summary>
/// 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.
/// </summary>
/// <param name="removeBackup">Whether a backup that was previously made should be removed after resetting the configuration.</param>
/// <exception cref="InvalidOperationException">No backup has been created before trying to reset the current configuration, and removal of the backup was not requested.</exception>
/// <remarks>
/// 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.
/// </remarks>
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()
{
Expand Down

0 comments on commit 777e476

Please sign in to comment.