/* * Copyright (c) 2024 Proton AG * * This file is part of ProtonVPN. * * ProtonVPN is free software: you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation, either version 3 of the License, or * (at your option) any later version. * * ProtonVPN is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with ProtonVPN. If not, see . */ using System.Collections; using System.Linq.Expressions; using System.Reflection; namespace ProtonVPN.Client.UI.Main.Settings.Bases; public class ChangedSettingArgs { private readonly Func _getNewValue; private readonly Func _getter; private readonly Action _setter; private readonly Func _comparer; public string Name { get; } public dynamic? NewValue => _getNewValue(); private ChangedSettingArgs( string name, Func getNewValue, Func getter, Action setter, Func comparer) { Name = name; _getNewValue = getNewValue; _getter = getter; _setter = setter; _comparer = comparer; } public bool HasChanged() { try { return !_comparer(_getter(), NewValue); } catch (Exception) { return false; } } public void ApplyChanges() { if (HasChanged()) { _setter.Invoke(NewValue); } } public static ChangedSettingArgs Create( Expression> propertyExpression, Func newValueProvider) { MemberExpression? memberExpression = propertyExpression.Body as MemberExpression; if (memberExpression is null && propertyExpression.Body is UnaryExpression { NodeType: ExpressionType.Convert } unaryExpression) { memberExpression = unaryExpression.Operand as MemberExpression; } if (memberExpression is null) { throw new ArgumentException("Expression must represent a property access.", nameof(propertyExpression)); } if (memberExpression.Member is not PropertyInfo propertyInfo) { throw new ArgumentException("Only property access expressions are supported.", nameof(propertyExpression)); } // Extract property name string propertyName = propertyInfo.Name; // Compile the getter and wrap it in a function returning dynamic Func strongTypedGetter = propertyExpression.Compile(); Func getter = () => strongTypedGetter(); // Convert the new value provider to a dynamic function Func getNewValue = () => newValueProvider(); // Compile the setter to accept a dynamic value Func instanceExpression = Expression.Lambda>(memberExpression.Expression!).Compile(); object instance = instanceExpression(); Action setter = value => propertyInfo.SetValue(instance, value); // Build comparer Func comparer; if (typeof(IEnumerable).IsAssignableFrom(typeof(T)) && typeof(T) != typeof(string)) { comparer = (a, b) => a is IEnumerable aSeq && b is IEnumerable bSeq ? aSeq.Cast().SequenceEqual(bSeq.Cast()) : Equals(a, b); } else { comparer = (a, b) => EqualityComparer.Default.Equals((T)a!, (T)b!); } return new ChangedSettingArgs(propertyName, getNewValue, getter, setter, comparer); } }