-
Notifications
You must be signed in to change notification settings - Fork 74
Expand file tree
/
Copy pathAbstractBootstrap.cs
More file actions
351 lines (331 loc) · 19.1 KB
/
Copy pathAbstractBootstrap.cs
File metadata and controls
351 lines (331 loc) · 19.1 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
using System;
using System.Collections.Concurrent;
using System.Collections.Generic;
using System.Threading.Tasks;
using GeneralUpdate.Core.Strategy;
namespace GeneralUpdate.Core.Configuration
{
/// <summary>
/// Provides the bootstrap base class supporting a generic self-referencing (CRTP) pattern
/// for configuring and launching the update workflow.
/// </summary>
/// <typeparam name="TBootstrap">The derived bootstrap type; must inherit from <see cref="AbstractBootstrap{TBootstrap, TStrategy}"/>.</typeparam>
/// <typeparam name="TStrategy">The update strategy type; must implement <see cref="IStrategy"/>.</typeparam>
/// <remarks>
/// <para>This class uses an extension-point registration/resolution pattern to manage
/// replaceable components in the update workflow. The core mechanisms are:</para>
/// <para>
/// - The <c>_extensions</c> dictionary stores Type→Type mappings (interface type → implementation type).
/// Extensions are registered via fluent methods such as <c>.Strategy<T>()</c> or
/// <c>.DownloadSource<T>()</c> and are lazily instantiated on demand through
/// <see cref="ResolveExtension{TExtension}"/>.<br/>
/// - The <c>_instances</c> dictionary stores already-instantiated singleton objects
/// (e.g., <c>BlackListConfig</c>). These take precedence over lazy registrations
/// in <c>_extensions</c>.<br/>
/// - The <see cref="Option{T}(UpdateOption{T}, T)"/> method provides fluent configuration
/// options, read via <see cref="GetOption{T}(UpdateOption{T}?)"/>, with a default-value
/// fallback mechanism.
/// </para>
/// <para>Typical usage: chain extension registrations together, then call
/// <see cref="LaunchAsync"/> to start the update workflow.</para>
/// </remarks>
public abstract class AbstractBootstrap<TBootstrap, TStrategy>
where TBootstrap : AbstractBootstrap<TBootstrap, TStrategy>
where TStrategy : IStrategy
{
private readonly ConcurrentDictionary<UpdateOption, UpdateOptionValue> _options;
/// <summary>User-registered extension type mappings (interface type → implementation type), used for lazy instantiation.</summary>
private readonly Dictionary<Type, Type> _extensions = new();
/// <summary>Registered singleton instances (e.g., <c>BlackListConfig</c>).</summary>
private readonly Dictionary<Type, object> _instances = new();
protected internal AbstractBootstrap()
{
_options = new ConcurrentDictionary<UpdateOption, UpdateOptionValue>();
}
public abstract Task<TBootstrap> LaunchAsync();
/// <summary>
/// Sets an update option value, supporting fluent chaining.
/// </summary>
/// <typeparam name="T">The type of the option value.</typeparam>
/// <param name="option">The option key to set.</param>
/// <param name="value">The value to set. If <c>null</c>, the option entry is removed
/// from the dictionary so that subsequent reads fall back to the default value.</param>
/// <returns>The current <typeparamref name="TBootstrap"/> instance for chaining.</returns>
/// <remarks>
/// Options are stored in a <c>ConcurrentDictionary</c> to guarantee thread safety.
/// When <paramref name="value"/> is <c>null</c>, the entry is removed, causing
/// <see cref="GetOption{T}(UpdateOption{T}?)"/> to return
/// <see cref="UpdateOption{T}.DefaultValue"/>.
/// </remarks>
public TBootstrap Option<T>(UpdateOption<T> option, T value)
{
if (value == null)
_options.TryRemove(option, out _);
else
_options[option] = new UpdateOptionValue<T>(option, value);
return (TBootstrap)this;
}
/// <summary>
/// Gets the value of the specified option. Returns the default value when the option
/// is <c>null</c> or has not been registered.
/// </summary>
/// <typeparam name="T">The type of the option value.</typeparam>
/// <param name="option">The option key to retrieve; can be <c>null</c>.</param>
/// <returns>
/// The registered value if found; otherwise, <see cref="UpdateOption{T}.DefaultValue"/>.
/// </returns>
/// <remarks>
/// First attempts to look up the option in the <c>_options</c> dictionary.
/// If not found, falls back to <see cref="UpdateOption{T}.DefaultValue"/>.
/// This is the companion read method for <see cref="Option{T}(UpdateOption{T}, T)"/>.
/// </remarks>
protected T GetOption<T>(UpdateOption<T>? option)
{
if (option == null) return default!;
if (_options.TryGetValue(option, out var val) && val != null)
return (T)val.GetValue();
return option.DefaultValue;
}
// ═══════════ Extension point registration ═══════════
/// <summary>
/// Registers an update status reporter for reporting update progress and results
/// to a server (e.g., GeneralSpacestation).
/// </summary>
/// <typeparam name="T">The reporter implementation type; must implement
/// <c>IUpdateReporter</c> and have a parameterless constructor.</typeparam>
/// <returns>The current <typeparamref name="TBootstrap"/> instance for chaining.</returns>
/// <remarks>
/// <para>The reporter is invoked at key points in the update workflow:</para>
/// <para>- Update starts: <c>ReportAsync(Updating)</c>.</para>
/// <para>- Download completes: <c>ReportAsync(Updating)</c>.</para>
/// <para>- Update applied successfully: <c>ReportAsync(Success)</c>.</para>
/// <para>- Update failed: <c>ReportAsync(Failure)</c>.</para>
/// <para>The default implementation is <c>HttpUpdateReporter</c>. All reporter
/// invocations are wrapped in try-catch; a single failure does not block the workflow.</para>
/// </remarks>
public TBootstrap UpdateReporter<T>() where T : Download.Reporting.IUpdateReporter, new()
{
_extensions[typeof(Download.Reporting.IUpdateReporter)] = typeof(T);
return (TBootstrap)this;
}
/// <summary>
/// Registers a custom OS-level strategy implementation (e.g.,
/// <see cref="Strategy.WindowsStrategy"/>, <see cref="Strategy.LinuxStrategy"/>,
/// <see cref="Strategy.MacStrategy"/>).
/// </summary>
/// <typeparam name="T">The strategy implementation type; must implement <see cref="IStrategy"/>
/// and have a parameterless constructor.</typeparam>
/// <returns>The current <typeparamref name="TBootstrap"/> instance for chaining.</returns>
/// <remarks>
/// Once registered, when <c>ClientStrategy.ResolveOsStrategy()</c> is called, the
/// type registered here is used instead of auto-detecting the current operating system.
/// This extension point takes effect when <see cref="LaunchAsync"/> executes.
/// </remarks>
public TBootstrap Strategy<T>() where T : IStrategy, new()
{
_extensions[typeof(IStrategy)] = typeof(T);
return (TBootstrap)this;
}
/// <summary>
/// Registers update lifecycle hook implementations for injecting custom logic at key
/// points in the update workflow.
/// </summary>
/// <typeparam name="T">The hook implementation type; must implement <c>IUpdateHooks</c>
/// and have a parameterless constructor.</typeparam>
/// <returns>The current <typeparamref name="TBootstrap"/> instance for chaining.</returns>
/// <remarks>
/// <para>Hook callbacks are invoked at the following points in the workflow:</para>
/// <para>- <c>OnBeforeUpdateAsync</c>: Called before the download starts; can cancel the update.</para>
/// <para>- <c>OnDownloadCompletedAsync</c>: Called after all files have been downloaded.</para>
/// <para>- <c>OnAfterUpdateAsync</c>: Called after the upgrade packages have been applied.</para>
/// <para>- <c>OnBeforeStartAppAsync</c>: Called before launching the upgrade process.</para>
/// <para>- <c>OnUpdateErrorAsync</c>: Called when an exception occurs during the update.</para>
/// <para>The default implementation is <c>NoOpUpdateHooks</c> (no-op). All hook invocations
/// are wrapped in try-catch; a single hook failure does not block the workflow.</para>
/// </remarks>
public TBootstrap Hooks<T>() where T : Hooks.IUpdateHooks, new()
{
_extensions[typeof(Hooks.IUpdateHooks)] = typeof(T);
return (TBootstrap)this;
}
/// <summary>
/// Registers an SSL validation policy implementation for customising HTTPS certificate
/// validation behaviour.
/// </summary>
/// <typeparam name="T">The SSL policy implementation type; must implement
/// <c>ISslValidationPolicy</c> and have a parameterless constructor.</typeparam>
/// <returns>The current <typeparamref name="TBootstrap"/> instance for chaining.</returns>
/// <remarks>
/// Can be used to bypass self-signed certificate validation, use custom root certificate
/// authorities, or implement certificate pinning. This extension point is read by
/// <c>HttpClientProvider</c> and applied to the <c>HttpClientHandler</c> when initiating
/// HTTP download requests.
/// </remarks>
public TBootstrap SslPolicy<T>() where T : Security.ISslValidationPolicy, new()
{
_extensions[typeof(Security.ISslValidationPolicy)] = typeof(T);
return (TBootstrap)this;
}
/// <summary>
/// Registers a custom download retry/timeout policy for controlling how the system
/// behaves when downloads fail.
/// </summary>
/// <typeparam name="T">The download policy implementation type; must implement
/// <c>IDownloadPolicy</c> and have a parameterless constructor.</typeparam>
/// <returns>The current <typeparamref name="TBootstrap"/> instance for chaining.</returns>
/// <remarks>
/// Only takes effect when using the default download orchestrator
/// (<c>DefaultDownloadOrchestrator</c>). If a custom orchestrator is also registered
/// via <see cref="DownloadOrchestrator{T}"/>, this setting is ignored.
/// The download policy determines the wait interval after each failure and the
/// maximum retry count.
/// </remarks>
public TBootstrap DownloadPolicy<T>() where T : Download.Abstractions.IDownloadPolicy, new()
{
_extensions[typeof(Download.Abstractions.IDownloadPolicy)] = typeof(T);
return (TBootstrap)this;
}
/// <summary>
/// Registers a custom single-file download executor for supporting protocols other
/// than HTTP/HTTPS.
/// </summary>
/// <typeparam name="T">The download executor implementation type; must implement
/// <c>IDownloadExecutor</c> and have a parameterless constructor.</typeparam>
/// <returns>The current <typeparamref name="TBootstrap"/> instance for chaining.</returns>
/// <remarks>
/// Only takes effect when using the default download orchestrator
/// (<c>DefaultDownloadOrchestrator</c>). If a custom orchestrator is also registered
/// via <see cref="DownloadOrchestrator{T}"/>, this setting is ignored.
/// Can be used to implement FTP, SFTP, or private-protocol file downloads.
/// </remarks>
public TBootstrap DownloadExecutor<T>() where T : Download.Abstractions.IDownloadExecutor, new()
{
_extensions[typeof(Download.Abstractions.IDownloadExecutor)] = typeof(T);
return (TBootstrap)this;
}
/// <summary>
/// Registers a download data source implementation for retrieving version information
/// and update file manifests from the server.
/// </summary>
/// <typeparam name="T">The download source implementation type; must implement
/// <c>IDownloadSource</c> and have a parameterless constructor.</typeparam>
/// <returns>The current <typeparamref name="TBootstrap"/> instance for chaining.</returns>
/// <remarks>
/// <para>Built-in implementations include <c>HttpDownloadSource</c> (HTTP/HTTPS) and
/// SignalR Hub download sources.</para>
/// <para>Use this method to register custom data sources, such as a local file system,
/// FTP server, or private cloud storage for fetching update manifests.</para>
/// <para>This extension point takes effect when
/// <c>ClientStrategy.ExecuteStandardWorkflowAsync()</c> calls
/// <c>downloadSource.ListAsync()</c>.</para>
/// </remarks>
public TBootstrap DownloadSource<T>() where T : Download.Abstractions.IDownloadSource, new()
{
_extensions[typeof(Download.Abstractions.IDownloadSource)] = typeof(T);
return (TBootstrap)this;
}
/// <summary>
/// Registers a custom post-download processing pipeline for performing hash verification,
/// decryption, virus scanning, or other post-processing on downloaded files.
/// </summary>
/// <typeparam name="T">The download pipeline implementation type; must implement
/// <c>IDownloadPipeline</c> and have a parameterless constructor.</typeparam>
/// <returns>The current <typeparamref name="TBootstrap"/> instance for chaining.</returns>
/// <remarks>
/// Only takes effect when using the default download orchestrator
/// (<c>DefaultDownloadOrchestrator</c>). If a custom orchestrator is also registered
/// via <see cref="DownloadOrchestrator{T}"/>, this setting is ignored.
/// The download pipeline receives the path of each completed download and can perform
/// integrity checks, decrypt encrypted files, or scan for malware.
/// </remarks>
public TBootstrap DownloadPipeline<T>() where T : Download.Abstractions.IDownloadPipeline, new()
{
_extensions[typeof(Download.Abstractions.IDownloadPipeline)] = typeof(T);
return (TBootstrap)this;
}
/// <summary>
/// <summary>
/// Registers an HTTP authentication provider for attaching authentication credentials
/// to download requests.
/// </summary>
/// <typeparam name="T">The authentication provider implementation type; must implement
/// <c>IHttpAuthProvider</c> and have a parameterless constructor.</typeparam>
/// <returns>The current <typeparamref name="TBootstrap"/> instance for chaining.</returns>
/// <remarks>
/// Can be used to support servers that require token authentication (Bearer Token),
/// basic authentication (Basic Auth), or custom authentication headers. This extension
/// point is read by <c>HttpClientProvider</c> and injected into request headers when
/// creating HTTP download requests.
/// </remarks>
public TBootstrap UpdateAuth<T>() where T : Security.IHttpAuthProvider, new()
{
_extensions[typeof(Security.IHttpAuthProvider)] = typeof(T);
return (TBootstrap)this;
}
/// <summary>
/// Registers a custom download orchestrator for end-to-end handling of batch
/// download tasks.
/// </summary>
/// <typeparam name="T">The orchestrator implementation type; must implement
/// <c>IDownloadOrchestrator</c> and have a parameterless constructor.</typeparam>
/// <returns>The current <typeparamref name="TBootstrap"/> instance for chaining.</returns>
/// <remarks>
/// <para>This is the highest-level download abstraction, owning full control of the
/// download pipeline.</para>
/// <para>When a custom orchestrator is set, registrations via
/// <see cref="DownloadPolicy{T}"/>, <see cref="DownloadExecutor{T}"/>, and
/// <see cref="DownloadPipeline{T}"/> are ignored, because the custom orchestrator
/// assumes full control of the download workflow.</para>
/// <para>Suitable for advanced scenarios requiring fully customised download behaviour
/// (e.g., third-party download libraries, chunked download, multi-threaded download).</para>
/// </remarks>
public TBootstrap DownloadOrchestrator<T>() where T : Download.Abstractions.IDownloadOrchestrator, new()
{
_extensions[typeof(Download.Abstractions.IDownloadOrchestrator)] = typeof(T);
return (TBootstrap)this;
}
/// <summary>
/// Resolves and instantiates an extension of the specified interface type using a
/// two-phase lookup strategy.
/// </summary>
/// <typeparam name="TExtension">The extension interface type to resolve.</typeparam>
/// <returns>The extension instance; <c>null</c> if no corresponding implementation
/// type has been registered.</returns>
/// <remarks>
/// <para><b>Two-phase lookup:</b></para>
/// <para>
/// <b>Phase 1</b> — Looks up the registered implementation <c>Type</c> in the
/// <c>_extensions</c> dictionary by interface type. If found, a new instance is
/// created via <c>Activator.CreateInstance</c>.<br/>
/// <b>Phase 2</b> — If not found in <c>_extensions</c>, looks up an existing
/// singleton instance in the <c>_instances</c> dictionary.
/// </para>
/// <para>Singleton instances in <c>_instances</c> take precedence over lazy
/// registrations in <c>_extensions</c>.</para>
/// </remarks>
protected TExtension? ResolveExtension<TExtension>() where TExtension : class
{
if (_extensions.TryGetValue(typeof(TExtension), out var t))
return Activator.CreateInstance((Type)t) as TExtension;
if (_instances.TryGetValue(typeof(TExtension), out var instance))
return instance as TExtension;
return null;
}
/// <summary>
/// Resolves the registered implementation type for the specified extension interface
/// without instantiating it.
/// </summary>
/// <typeparam name="TExtension">The extension interface type to resolve.</typeparam>
/// <returns>The registered implementation type; <c>null</c> if not registered.</returns>
/// <remarks>
/// Unlike <see cref="ResolveExtension{TExtension}"/>, this method only returns
/// type information. It is suitable for scenarios where reflection-based type
/// metadata is needed without creating an instance — for example, checking
/// whether an extension is registered in order to decide a workflow branch.
/// </remarks>
protected Type? ResolveExtensionType<TExtension>() where TExtension : class
{
return _extensions.TryGetValue(typeof(TExtension), out var t) ? t : null;
}
}
}