diff --git a/src/c#/GeneralUpdate.Core/Bootstrap/GeneralUpdateBootstrap.cs b/src/c#/GeneralUpdate.Core/Bootstrap/GeneralUpdateBootstrap.cs index 892e3aa9..a42d7293 100644 --- a/src/c#/GeneralUpdate.Core/Bootstrap/GeneralUpdateBootstrap.cs +++ b/src/c#/GeneralUpdate.Core/Bootstrap/GeneralUpdateBootstrap.cs @@ -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() diff --git a/src/c#/GeneralUpdate.Core/Configuration/AbstractBootstrap.cs b/src/c#/GeneralUpdate.Core/Configuration/AbstractBootstrap.cs index fd6fb2f7..869b0ab7 100644 --- a/src/c#/GeneralUpdate.Core/Configuration/AbstractBootstrap.cs +++ b/src/c#/GeneralUpdate.Core/Configuration/AbstractBootstrap.cs @@ -23,17 +23,6 @@ public abstract class AbstractBootstrap protected internal AbstractBootstrap() { _options = new ConcurrentDictionary(); - 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 LaunchAsync(); @@ -50,19 +39,12 @@ public TBootstrap Option(UpdateOption option, T value) return (TBootstrap)this; } - protected T? GetOption(UpdateOption? option) + protected T GetOption(UpdateOption? 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 ═══════════ diff --git a/src/c#/GeneralUpdate.Core/Configuration/IsExternalInit.cs b/src/c#/GeneralUpdate.Core/Configuration/IsExternalInit.cs index 5ead4123..e5869b7e 100644 --- a/src/c#/GeneralUpdate.Core/Configuration/IsExternalInit.cs +++ b/src/c#/GeneralUpdate.Core/Configuration/IsExternalInit.cs @@ -1,3 +1,4 @@ +// ReSharper disable once CheckNamespace namespace System.Runtime.CompilerServices { internal static class IsExternalInit { } diff --git a/src/c#/GeneralUpdate.Core/Configuration/UpdateOption.cs b/src/c#/GeneralUpdate.Core/Configuration/UpdateOption.cs index f2356c15..6c490430 100644 --- a/src/c#/GeneralUpdate.Core/Configuration/UpdateOption.cs +++ b/src/c#/GeneralUpdate.Core/Configuration/UpdateOption.cs @@ -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 + /// + /// Base class for strongly-typed update option keys. + /// Simple implementation using ConcurrentDictionary registry, + /// replacing the earlier Netty ConstantPool pattern. + /// + public class UpdateOption { - private class UpdateOptionPool : ConstantPool - { - protected override IConstant NewConstant(int id, string name) => new UpdateOption(id, name); - } - - private static readonly UpdateOptionPool Pool = new(); + private static readonly ConcurrentDictionary _registry = new(); + private static readonly object _lock = new(); - public static UpdateOption ValueOf(string name) => (UpdateOption)Pool.ValueOf(name); - - /// - /// Update the file format of the package. - /// - public static readonly UpdateOption Format = ValueOf("COMPRESSFORMAT"); - - /// - /// Compress encoding. - /// - public static readonly UpdateOption Encoding = ValueOf("COMPRESSENCODING"); - - /// - /// Timeout period (unit: second). If this parameter is not specified, the default timeout period is 30 seconds. - /// - public static readonly UpdateOption DownloadTimeOut = ValueOf("DOWNLOADTIMEOUT"); - - /// - /// Whether to enable the driver upgrade function. - /// - public static readonly UpdateOption Drive = ValueOf("DRIVE"); - - /// - /// Whether to enable the patch function. - /// - public static readonly UpdateOption Patch = ValueOf("PATCH"); - - /// - /// Whether to enable the backup function. - /// - public static readonly UpdateOption BackUp = ValueOf("BACKUP"); - - /// - /// Specifies the update execution mode. - /// - public static readonly UpdateOption Mode = ValueOf("MODE"); - - /// - /// Whether to enable silent update mode. - /// - public static readonly UpdateOption EnableSilentUpdate = ValueOf("ENABLESILENTUPDATE"); - - internal UpdateOption(int id, string name) - : base(id, name) { } - - public abstract bool Set(IUpdateConfiguration configuration, object value); - } - - public sealed class UpdateOption : 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 constants = new Dictionary(); - private int nextId = 1; - - /// Shortcut of this.ValueOf(firstNameComponent.Name + "#" + secondNameComponent). - public IConstant ValueOf(Type firstNameComponent, string secondNameComponent) - { - Contract.Requires(firstNameComponent != null); - Contract.Requires(secondNameComponent != null); - return this.ValueOf(firstNameComponent.Name + '#' + secondNameComponent); - } - - /// - /// Returns the which is assigned to the specified name. - /// If there's no such , a new one will be created and returned. - /// Once created, the subsequent calls with the same name will always return the previously created one - /// (i.e. singleton.) - /// - /// the name of the - public IConstant ValueOf(string name) - { - IConstant constant; - lock (this.constants) - { - if (this.constants.TryGetValue(name, out constant)) - { - return constant; - } - else - { - constant = this.NewInstance0(name); - } - } - return constant; - } + /// Unique option name. + public string Name { get; } - /// Returns true if a exists for the given name. - public bool Exists(string name) + /// Protected constructor — use to create instances. + protected UpdateOption(string name) { - CheckNotNullAndNotEmpty(name); - lock (this.constants) - return this.constants.ContainsKey(name); + Name = name ?? throw new ArgumentNullException(nameof(name)); } /// - /// Creates a new for the given name or fail with an - /// if a for the given name exists. + /// Returns the 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). /// - public IConstant NewInstance(string name) + public static UpdateOption ValueOf(string name, T defaultValue = default!) { - if (this.Exists(name)) throw new ArgumentException($"'{name}' is already in use"); - IConstant constant = this.NewInstance0(name); - return constant; - } - - // Be careful that this dose not check whether the argument is null or empty. - private IConstant NewInstance0(string name) - { - lock (this.constants) + lock (_lock) { - IConstant constant = this.NewConstant(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(int id, string name); + if (_registry.TryGetValue(name, out var existing) && existing is UpdateOption typed) + return typed; - [Obsolete] - public int NextId() - { - lock (this.constants) - { - int id = this.nextId; - this.nextId++; - return id; + var option = new UpdateOption(name, defaultValue); + _registry[name] = option; + return option; } } - } - - public interface IConstant - { - /// Returns the unique number assigned to this . - int Id { get; } - - /// Returns the name of this . - string Name { get; } - } - - public interface IUpdateConfiguration - { - T GetOption(UpdateOption option); - - bool SetOption(UpdateOption option, object value); - - bool SetOption(UpdateOption 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; } + /// Returns the option name. + public override string ToString() => Name; - public string Name { get; } - - public override sealed string ToString() => this.Name; + /// Hash code based on name. + 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; - } - } + /// Equality based on name. + public override bool Equals(object? obj) + => obj is UpdateOption other && Name == other.Name; } - public abstract class AbstractConstant : AbstractConstant, IComparable, IEquatable - where T : AbstractConstant + /// + /// Strongly-typed update option key with a default value. + /// Instances are obtained via . + /// + public sealed class UpdateOption : UpdateOption { - /// Creates a new instance. - 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); + /// Default value used when the option is not explicitly set. + public T DefaultValue { get; } - public int CompareTo(T o) + internal UpdateOption(string name, T defaultValue) : base(name) { - if (ReferenceEquals(this, o)) return 0; - AbstractConstant 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; } } } diff --git a/src/c#/GeneralUpdate.Core/Configuration/UpdateOptionValue.cs b/src/c#/GeneralUpdate.Core/Configuration/UpdateOptionValue.cs index b09bedb3..08fb2750 100644 --- a/src/c#/GeneralUpdate.Core/Configuration/UpdateOptionValue.cs +++ b/src/c#/GeneralUpdate.Core/Configuration/UpdateOptionValue.cs @@ -1,29 +1,33 @@ namespace GeneralUpdate.Core.Configuration { + /// + /// Wraps a concrete option value for storage in the options dictionary. + /// public abstract class UpdateOptionValue { + /// The option key this value belongs to. public abstract UpdateOption Option { get; } - public abstract bool Set(IUpdateConfiguration config); - + /// Returns the stored value as an object. public abstract object GetValue(); } + /// + /// Strongly-typed option value wrapper. + /// public sealed class UpdateOptionValue : UpdateOptionValue { public override UpdateOption Option { get; } - private readonly T value; + private readonly T _value; public UpdateOptionValue(UpdateOption 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; } -} \ No newline at end of file +} diff --git a/src/c#/GeneralUpdate.Core/Configuration/UpdateOptions.cs b/src/c#/GeneralUpdate.Core/Configuration/UpdateOptions.cs index 38601273..90f6b931 100644 --- a/src/c#/GeneralUpdate.Core/Configuration/UpdateOptions.cs +++ b/src/c#/GeneralUpdate.Core/Configuration/UpdateOptions.cs @@ -1,56 +1,54 @@ using System; -using System.Diagnostics; -using System.Reflection; -using System.Runtime.InteropServices; using System.Text; +using GeneralUpdate.Core.FileSystem; namespace GeneralUpdate.Core.Configuration { /// /// Convenience accessor for UpdateOption constants. - /// Maps to the underlying UpdateOption singleton constants. - /// New code should use UpdateOptions.X instead of UpdateOption.X. + /// Each option has a unique string name and a reasonable default value. + /// Use via .Option(UpdateOptions.UpdateUrl, "https://..."). /// public static class UpdateOptions { // ═══ Core ═══ - public static UpdateOption AppType => UpdateOption.ValueOf("APPTYPE"); + public static UpdateOption AppType { get; } = UpdateOption.ValueOf("APPTYPE", Configuration.AppType.ClientApp); - // ═══ Existing options (backward-compatible) ═══ - public static UpdateOption Encoding => UpdateOption.Encoding; - public static UpdateOption Format => UpdateOption.Format; - public static UpdateOption DownloadTimeout => UpdateOption.DownloadTimeOut; - public static UpdateOption DriveEnabled => UpdateOption.Drive; - public static UpdateOption PatchEnabled => UpdateOption.Patch; - public static UpdateOption BackupEnabled => UpdateOption.BackUp; - public static UpdateOption Mode => UpdateOption.Mode; - public static UpdateOption Silent => UpdateOption.EnableSilentUpdate; + // ═══ Backward-compatible options ═══ + public static UpdateOption Encoding { get; } = UpdateOption.ValueOf("COMPRESSENCODING", System.Text.Encoding.UTF8); + public static UpdateOption Format { get; } = UpdateOption.ValueOf("COMPRESSFORMAT", "ZIP"); + public static UpdateOption DownloadTimeout { get; } = UpdateOption.ValueOf("DOWNLOADTIMEOUT", 30); + public static UpdateOption DriveEnabled { get; } = UpdateOption.ValueOf("DRIVE", false); + public static UpdateOption PatchEnabled { get; } = UpdateOption.ValueOf("PATCH", true); + public static UpdateOption BackupEnabled { get; } = UpdateOption.ValueOf("BACKUP", true); + public static UpdateOption Mode { get; } = UpdateOption.ValueOf("MODE", null); + public static UpdateOption Silent { get; } = UpdateOption.ValueOf("ENABLESILENTUPDATE", false); // ═══ New options ═══ - public static readonly UpdateOption UpdateUrl = UpdateOption.ValueOf("UPDATEURL"); - public static readonly UpdateOption AppSecretKey = UpdateOption.ValueOf("APPSECRETKEY"); - public static readonly UpdateOption AppName = UpdateOption.ValueOf("APPNAME"); - public static readonly UpdateOption MainAppName = UpdateOption.ValueOf("MAINAPPNAME"); - public static readonly UpdateOption InstallPath = UpdateOption.ValueOf("INSTALLPATH"); - public static readonly UpdateOption ClientVersion = UpdateOption.ValueOf("CLIENTVERSION"); - public static readonly UpdateOption UpgradeClientVersion = UpdateOption.ValueOf("UPGRADECLIENTVERSION"); - public static readonly UpdateOption Platform = UpdateOption.ValueOf("PLATFORM"); - public static readonly UpdateOption SilentAutoInstall = UpdateOption.ValueOf("SILENTAUTOINSTALL"); - public static readonly UpdateOption MaxConcurrency = UpdateOption.ValueOf("MAXCONCURRENCY"); - public static readonly UpdateOption EnableResume = UpdateOption.ValueOf("ENABLERESUME"); - public static readonly UpdateOption RetryCount = UpdateOption.ValueOf("RETRYCOUNT"); - public static readonly UpdateOption VerifyChecksum = UpdateOption.ValueOf("VERIFYCHECKSUM"); - public static readonly UpdateOption ReportUrl = UpdateOption.ValueOf("REPORTURL"); - public static readonly UpdateOption ProductId = UpdateOption.ValueOf("PRODUCTID"); - public static readonly UpdateOption PermissionScript = UpdateOption.ValueOf("PERMISSIONSCRIPT"); - public static readonly UpdateOption Scheme = UpdateOption.ValueOf("SCHEME"); - public static readonly UpdateOption Token = UpdateOption.ValueOf("TOKEN"); + public static UpdateOption UpdateUrl { get; } = UpdateOption.ValueOf("UPDATEURL", null); + public static UpdateOption AppSecretKey { get; } = UpdateOption.ValueOf("APPSECRETKEY", string.Empty); + public static UpdateOption AppName { get; } = UpdateOption.ValueOf("APPNAME", string.Empty); + public static UpdateOption MainAppName { get; } = UpdateOption.ValueOf("MAINAPPNAME", string.Empty); + public static UpdateOption InstallPath { get; } = UpdateOption.ValueOf("INSTALLPATH", AppContext.BaseDirectory); + public static UpdateOption ClientVersion { get; } = UpdateOption.ValueOf("CLIENTVERSION", string.Empty); + public static UpdateOption UpgradeClientVersion { get; } = UpdateOption.ValueOf("UPGRADECLIENTVERSION", null); + public static UpdateOption Platform { get; } = UpdateOption.ValueOf("PLATFORM", null); + public static UpdateOption SilentAutoInstall { get; } = UpdateOption.ValueOf("SILENTAUTOINSTALL", false); + public static UpdateOption MaxConcurrency { get; } = UpdateOption.ValueOf("MAXCONCURRENCY", 3); + public static UpdateOption EnableResume { get; } = UpdateOption.ValueOf("ENABLERESUME", true); + public static UpdateOption RetryCount { get; } = UpdateOption.ValueOf("RETRYCOUNT", 3); + public static UpdateOption VerifyChecksum { get; } = UpdateOption.ValueOf("VERIFYCHECKSUM", true); + public static UpdateOption ReportUrl { get; } = UpdateOption.ValueOf("REPORTURL", null); + public static UpdateOption ProductId { get; } = UpdateOption.ValueOf("PRODUCTID", null); + public static UpdateOption PermissionScript { get; } = UpdateOption.ValueOf("PERMISSIONSCRIPT", null); + public static UpdateOption Scheme { get; } = UpdateOption.ValueOf("SCHEME", null); + public static UpdateOption Token { get; } = UpdateOption.ValueOf("TOKEN", null); // ═══ OSS ═══ - public static readonly UpdateOption OSSProvider = UpdateOption.ValueOf("OSSPROVIDER"); - public static readonly UpdateOption OSSBucketRegion = UpdateOption.ValueOf("OSSBUCKETREGION"); + public static UpdateOption OSSProvider { get; } = UpdateOption.ValueOf("OSSPROVIDER", null); + public static UpdateOption OSSBucketRegion { get; } = UpdateOption.ValueOf("OSSBUCKETREGION", null); // ═══ Blacklist ═══ - public static readonly UpdateOption BlackList = UpdateOption.ValueOf("BLACKLIST"); + public static UpdateOption BlackList { get; } = UpdateOption.ValueOf("BLACKLIST", BlackListConfig.Empty); } } diff --git a/src/c#/GeneralUpdate.Core/GeneralUpdate.Core.csproj b/src/c#/GeneralUpdate.Core/GeneralUpdate.Core.csproj index de6d0919..b2985972 100644 --- a/src/c#/GeneralUpdate.Core/GeneralUpdate.Core.csproj +++ b/src/c#/GeneralUpdate.Core/GeneralUpdate.Core.csproj @@ -11,18 +11,32 @@ GeneralUpdate.png upgrade,update,client 10.0.0-preview - netstandard2.0 + netstandard2.0;net8.0;net10.0 + true + true - + + + + + + + + + + + - + + + diff --git a/src/c#/GeneralUpdate.Core/Strategy/LinuxStrategy.cs b/src/c#/GeneralUpdate.Core/Strategy/LinuxStrategy.cs index 8a70e89e..19fbec74 100644 --- a/src/c#/GeneralUpdate.Core/Strategy/LinuxStrategy.cs +++ b/src/c#/GeneralUpdate.Core/Strategy/LinuxStrategy.cs @@ -36,9 +36,8 @@ protected override PipelineBuilder BuildPipeline(PipelineContext context) .UseMiddlewareIf(_configinfo.PatchEnabled) .UseMiddleware() .UseMiddleware(); -#if NET10_0_OR_GREATER - builder.UseMiddlewareIf(_configinfo.DriveEnabled == true); -#endif + // DrivelutionMiddleware requires GeneralUpdate.Drivelution project reference; + // uncomment when the reference is added for net10.0 target. return builder; } diff --git a/src/c#/GeneralUpdate.Core/Strategy/WindowsStrategy.cs b/src/c#/GeneralUpdate.Core/Strategy/WindowsStrategy.cs index 0bf48ace..49f3b823 100644 --- a/src/c#/GeneralUpdate.Core/Strategy/WindowsStrategy.cs +++ b/src/c#/GeneralUpdate.Core/Strategy/WindowsStrategy.cs @@ -38,9 +38,8 @@ protected override PipelineBuilder BuildPipeline(PipelineContext context) .UseMiddlewareIf(_configinfo.PatchEnabled) .UseMiddleware() .UseMiddleware(); -#if NET10_0_OR_GREATER - builder.UseMiddlewareIf(_configinfo.DriveEnabled == true); -#endif + // DrivelutionMiddleware requires GeneralUpdate.Drivelution project reference; + // uncomment when the reference is added for net10.0 target. return builder; }