Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
6 changes: 3 additions & 3 deletions src/c#/GeneralUpdate.Core/Bootstrap/GeneralUpdateBootstrap.cs
Original file line number Diff line number Diff line change
Expand Up @@ -245,9 +245,9 @@ private void InitializeFromEnvironment()

private void ApplyRuntimeOptions()
{
_configInfo.Encoding = GetOption(UpdateOption.Encoding) ?? Encoding.Default;
_configInfo.Format = GetOption(UpdateOption.Format) ?? Format.ZIP;
_configInfo.DownloadTimeOut = GetOption(UpdateOption.DownloadTimeOut) ?? 60;
_configInfo.Encoding = GetOption(UpdateOptions.Encoding);
_configInfo.Format = GetOption(UpdateOptions.Format);
_configInfo.DownloadTimeOut = GetOption(UpdateOptions.DownloadTimeout) ?? 60;
}

private void InitBlackList()
Expand Down
28 changes: 5 additions & 23 deletions src/c#/GeneralUpdate.Core/Configuration/AbstractBootstrap.cs
Original file line number Diff line number Diff line change
Expand Up @@ -23,17 +23,6 @@ public abstract class AbstractBootstrap<TBootstrap, TStrategy>
protected internal AbstractBootstrap()
{
_options = new ConcurrentDictionary<UpdateOption, UpdateOptionValue>();
PopulateDefaults();
}

protected virtual void PopulateDefaults()
{
Option(UpdateOptions.MaxConcurrency, 3);
Option(UpdateOptions.RetryCount, 3);
Option(UpdateOptions.EnableResume, true);
Option(UpdateOptions.VerifyChecksum, true);
Option(UpdateOptions.SilentAutoInstall, false);
Option(UpdateOptions.DownloadTimeout, 30);
}

public abstract Task<TBootstrap> LaunchAsync();
Expand All @@ -50,19 +39,12 @@ public TBootstrap Option<T>(UpdateOption<T> option, T value)
return (TBootstrap)this;
}

protected T? GetOption<T>(UpdateOption<T>? option)
protected T GetOption<T>(UpdateOption<T>? option)
{
try
{
Debug.Assert(option != null && _options.Count != 0);
var val = _options[option];
if (val != null) return (T)val.GetValue();
return default;
}
catch
{
return default;
}
if (option == null) return default!;
if (_options.TryGetValue(option, out var val) && val != null)
return (T)val.GetValue();
return option.DefaultValue;
}

// ═══════════ Extension point registration ═══════════
Expand Down
1 change: 1 addition & 0 deletions src/c#/GeneralUpdate.Core/Configuration/IsExternalInit.cs
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
// ReSharper disable once CheckNamespace
namespace System.Runtime.CompilerServices
{
internal static class IsExternalInit { }
Expand Down
252 changes: 40 additions & 212 deletions src/c#/GeneralUpdate.Core/Configuration/UpdateOption.cs
Original file line number Diff line number Diff line change
@@ -1,240 +1,68 @@
using System;
using System.Collections.Generic;
using System.Diagnostics.Contracts;
using System.Text;
using System.Threading;
using GeneralUpdate.Core.Configuration;
using System.Collections.Concurrent;

namespace GeneralUpdate.Core.Configuration
{
public abstract class UpdateOption : AbstractConstant<UpdateOption>
/// <summary>
/// Base class for strongly-typed update option keys.
/// Simple implementation using ConcurrentDictionary registry,
/// replacing the earlier Netty ConstantPool pattern.
/// </summary>
public class UpdateOption
{
private class UpdateOptionPool : ConstantPool
{
protected override IConstant NewConstant<T>(int id, string name) => new UpdateOption<T>(id, name);
}

private static readonly UpdateOptionPool Pool = new();
private static readonly ConcurrentDictionary<string, UpdateOption> _registry = new();
private static readonly object _lock = new();

public static UpdateOption<T> ValueOf<T>(string name) => (UpdateOption<T>)Pool.ValueOf<T>(name);

/// <summary>
/// Update the file format of the package.
/// </summary>
public static readonly UpdateOption<string> Format = ValueOf<string>("COMPRESSFORMAT");

/// <summary>
/// Compress encoding.
/// </summary>
public static readonly UpdateOption<Encoding> Encoding = ValueOf<Encoding>("COMPRESSENCODING");

/// <summary>
/// Timeout period (unit: second). If this parameter is not specified, the default timeout period is 30 seconds.
/// </summary>
public static readonly UpdateOption<int?> DownloadTimeOut = ValueOf<int?>("DOWNLOADTIMEOUT");

/// <summary>
/// Whether to enable the driver upgrade function.
/// </summary>
public static readonly UpdateOption<bool?> Drive = ValueOf<bool?>("DRIVE");

/// <summary>
/// Whether to enable the patch function.
/// </summary>
public static readonly UpdateOption<bool?> Patch = ValueOf<bool?>("PATCH");

/// <summary>
/// Whether to enable the backup function.
/// </summary>
public static readonly UpdateOption<bool?> BackUp = ValueOf<bool?>("BACKUP");

/// <summary>
/// Specifies the update execution mode.
/// </summary>
public static readonly UpdateOption<UpdateMode?> Mode = ValueOf<UpdateMode?>("MODE");

/// <summary>
/// Whether to enable silent update mode.
/// </summary>
public static readonly UpdateOption<bool> EnableSilentUpdate = ValueOf<bool>("ENABLESILENTUPDATE");

internal UpdateOption(int id, string name)
: base(id, name) { }

public abstract bool Set(IUpdateConfiguration configuration, object value);
}

public sealed class UpdateOption<T> : UpdateOption
{
internal UpdateOption(int id, string name)
: base(id, name)
{
}

public void Validate(T value) => Contract.Requires(value != null);

public override bool Set(IUpdateConfiguration configuration, object value) => configuration.SetOption(this, (T)value);
}

public abstract class ConstantPool
{
private readonly Dictionary<string, IConstant> constants = new Dictionary<string, IConstant>();
private int nextId = 1;

/// <summary>Shortcut of <c>this.ValueOf(firstNameComponent.Name + "#" + secondNameComponent)</c>.</summary>
public IConstant ValueOf<T>(Type firstNameComponent, string secondNameComponent)
{
Contract.Requires(firstNameComponent != null);
Contract.Requires(secondNameComponent != null);
return this.ValueOf<T>(firstNameComponent.Name + '#' + secondNameComponent);
}

/// <summary>
/// Returns the <see cref="IConstant" /> which is assigned to the specified <c>name</c>.
/// If there's no such <see cref="IConstant" />, a new one will be created and returned.
/// Once created, the subsequent calls with the same <c>name</c> will always return the previously created one
/// (i.e. singleton.)
/// </summary>
/// <param name="name">the name of the <see cref="IConstant" /></param>
public IConstant ValueOf<T>(string name)
{
IConstant constant;
lock (this.constants)
{
if (this.constants.TryGetValue(name, out constant))
{
return constant;
}
else
{
constant = this.NewInstance0<T>(name);
}
}
return constant;
}
/// <summary>Unique option name.</summary>
public string Name { get; }

/// <summary>Returns <c>true</c> if a <see cref="AttributeKey{T}" /> exists for the given <c>name</c>.</summary>
public bool Exists(string name)
/// <summary>Protected constructor — use <see cref="ValueOf{T}(string, T)"/> to create instances.</summary>
protected UpdateOption(string name)
{
CheckNotNullAndNotEmpty(name);
lock (this.constants)
return this.constants.ContainsKey(name);
Name = name ?? throw new ArgumentNullException(nameof(name));
}

/// <summary>
/// Creates a new <see cref="IConstant" /> for the given <c>name</c> or fail with an
/// <see cref="ArgumentException" /> if a <see cref="IConstant" /> for the given <c>name</c> exists.
/// Returns the <see cref="UpdateOption{T}"/> for the given name, creating one with the
/// provided default value if it does not exist. Subsequent calls with the same name
/// return the same instance (singleton).
/// </summary>
public IConstant NewInstance<T>(string name)
public static UpdateOption<T> ValueOf<T>(string name, T defaultValue = default!)
{
if (this.Exists(name)) throw new ArgumentException($"'{name}' is already in use");
IConstant constant = this.NewInstance0<T>(name);
return constant;
}

// Be careful that this dose not check whether the argument is null or empty.
private IConstant NewInstance0<T>(string name)
{
lock (this.constants)
lock (_lock)
{
IConstant constant = this.NewConstant<T>(this.nextId, name);
this.constants[name] = constant;
this.nextId++;
return constant;
}
}

private static void CheckNotNullAndNotEmpty(string name) => Contract.Requires(!string.IsNullOrEmpty(name));

protected abstract IConstant NewConstant<T>(int id, string name);
if (_registry.TryGetValue(name, out var existing) && existing is UpdateOption<T> typed)
return typed;

[Obsolete]
public int NextId()
{
lock (this.constants)
{
int id = this.nextId;
this.nextId++;
return id;
var option = new UpdateOption<T>(name, defaultValue);
_registry[name] = option;
return option;
}
}
}

public interface IConstant
{
/// <summary>Returns the unique number assigned to this <see cref="IConstant" />.</summary>
int Id { get; }

/// <summary>Returns the name of this <see cref="IConstant" />.</summary>
string Name { get; }
}

public interface IUpdateConfiguration
{
T GetOption<T>(UpdateOption<T> option);

bool SetOption(UpdateOption option, object value);

bool SetOption<T>(UpdateOption<T> option, T value);
}

public abstract class AbstractConstant : IConstant
{
private static long nextUniquifier;
private long volatileUniquifier;

protected AbstractConstant(int id, string name)
{
this.Id = id;
this.Name = name;
}

public int Id { get; }
/// <summary>Returns the option name.</summary>
public override string ToString() => Name;

public string Name { get; }

public override sealed string ToString() => this.Name;
/// <summary>Hash code based on name.</summary>
public override int GetHashCode() => Name.GetHashCode();

protected long Uniquifier
{
get
{
long result;
if ((result = Volatile.Read(ref this.volatileUniquifier)) == 0)
{
result = Interlocked.Increment(ref nextUniquifier);
long previousUniquifier = Interlocked.CompareExchange(ref this.volatileUniquifier, result, 0);
if (previousUniquifier != 0) result = previousUniquifier;
}
return result;
}
}
/// <summary>Equality based on name.</summary>
public override bool Equals(object? obj)
=> obj is UpdateOption other && Name == other.Name;
}

public abstract class AbstractConstant<T> : AbstractConstant, IComparable<T>, IEquatable<T>
where T : AbstractConstant<T>
/// <summary>
/// Strongly-typed update option key with a default value.
/// Instances are obtained via <see cref="UpdateOption.ValueOf{T}(string, T)"/>.
/// </summary>
public sealed class UpdateOption<T> : UpdateOption
{
/// <summary>Creates a new instance.</summary>
protected AbstractConstant(int id, string name)
: base(id, name) { }

public override sealed int GetHashCode() => base.GetHashCode();

public override sealed bool Equals(object obj) => base.Equals(obj);

public bool Equals(T other) => ReferenceEquals(this, other);
/// <summary>Default value used when the option is not explicitly set.</summary>
public T DefaultValue { get; }

public int CompareTo(T o)
internal UpdateOption(string name, T defaultValue) : base(name)
{
if (ReferenceEquals(this, o)) return 0;
AbstractConstant<T> other = o;
int returnCode = this.GetHashCode() - other.GetHashCode();
if (returnCode != 0) return returnCode;
long thisUV = this.Uniquifier;
long otherUV = other.Uniquifier;
if (thisUV < otherUV) return -1;
if (thisUV > otherUV) return 1;
throw new System.Exception("failed to compare two different constants");
DefaultValue = defaultValue;
}
}
}
24 changes: 14 additions & 10 deletions src/c#/GeneralUpdate.Core/Configuration/UpdateOptionValue.cs
Original file line number Diff line number Diff line change
@@ -1,29 +1,33 @@
namespace GeneralUpdate.Core.Configuration
{
/// <summary>
/// Wraps a concrete option value for storage in the options dictionary.
/// </summary>
public abstract class UpdateOptionValue
{
/// <summary>The option key this value belongs to.</summary>
public abstract UpdateOption Option { get; }

public abstract bool Set(IUpdateConfiguration config);

/// <summary>Returns the stored value as an object.</summary>
public abstract object GetValue();
}

/// <summary>
/// Strongly-typed option value wrapper.
/// </summary>
public sealed class UpdateOptionValue<T> : UpdateOptionValue
{
public override UpdateOption Option { get; }
private readonly T value;
private readonly T _value;

public UpdateOptionValue(UpdateOption<T> option, T value)
{
this.Option = option;
this.value = value;
Option = option;
_value = value;
}

public override object GetValue() => this.value;

public override bool Set(IUpdateConfiguration config) => config.SetOption(this.Option, this.value);
public override object GetValue() => _value!;

public override string ToString() => this.value.ToString();
public override string ToString() => _value?.ToString() ?? string.Empty;
}
}
}
Loading
Loading