-
Notifications
You must be signed in to change notification settings - Fork 74
Expand file tree
/
Copy pathGeneralExtensionHost.cs
More file actions
461 lines (398 loc) · 19.3 KB
/
Copy pathGeneralExtensionHost.cs
File metadata and controls
461 lines (398 loc) · 19.3 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
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
using System;
using System.Collections.Generic;
using System.Linq;
using System.Threading.Tasks;
namespace GeneralUpdate.Extension
{
/// <summary>
/// Main orchestrator for the extension system.
/// Coordinates extension discovery, compatibility validation, updates, and lifecycle management.
/// </summary>
public class GeneralExtensionHost : IExtensionHost
{
private readonly Version _hostVersion;
private readonly Metadata.TargetPlatform _targetPlatform;
private readonly Core.IExtensionCatalog _catalog;
private readonly Compatibility.ICompatibilityValidator _validator;
private readonly Download.IUpdateQueue _updateQueue;
private readonly Installation.ExtensionInstallService _installService;
private readonly Services.IExtensionService _extensionService;
private bool _globalAutoUpdateEnabled = true;
#region Properties
/// <summary>
/// Gets the current host application version used for compatibility checking.
/// </summary>
public Version HostVersion => _hostVersion;
/// <summary>
/// Gets the target platform for extension filtering.
/// </summary>
public Metadata.TargetPlatform TargetPlatform => _targetPlatform;
/// <summary>
/// Gets or sets a value indicating whether automatic updates are globally enabled.
/// When disabled, no extensions will be automatically updated.
/// </summary>
public bool GlobalAutoUpdateEnabled
{
get => _globalAutoUpdateEnabled;
set => _globalAutoUpdateEnabled = value;
}
/// <summary>
/// Gets the extension service for query and download operations.
/// </summary>
public Services.IExtensionService ExtensionService => _extensionService;
#endregion
#region Events
/// <summary>
/// Occurs when an update operation changes state.
/// </summary>
public event EventHandler<EventHandlers.UpdateStateChangedEventArgs>? UpdateStateChanged;
/// <summary>
/// Occurs when download progress updates.
/// </summary>
public event EventHandler<EventHandlers.DownloadProgressEventArgs>? DownloadProgress;
/// <summary>
/// Occurs when a download completes successfully.
/// </summary>
public event EventHandler<EventHandlers.ExtensionEventArgs>? DownloadCompleted;
/// <summary>
/// Occurs when a download fails.
/// </summary>
public event EventHandler<EventHandlers.ExtensionEventArgs>? DownloadFailed;
/// <summary>
/// Occurs when an installation completes.
/// </summary>
public event EventHandler<EventHandlers.InstallationCompletedEventArgs>? InstallationCompleted;
/// <summary>
/// Occurs when a rollback completes.
/// </summary>
public event EventHandler<EventHandlers.RollbackCompletedEventArgs>? RollbackCompleted;
#endregion
/// <summary>
/// Initializes a new instance of the <see cref="GeneralExtensionHost"/> class using a configuration object.
/// </summary>
/// <param name="config">Configuration settings for the extension host.</param>
/// <exception cref="ArgumentNullException">Thrown when config is null or required properties are missing.</exception>
public GeneralExtensionHost(ExtensionHostConfig config)
{
if (config == null)
throw new ArgumentNullException(nameof(config));
config.Validate();
_hostVersion = config.HostVersion;
_targetPlatform = config.TargetPlatform;
// Initialize core services
_catalog = new Core.ExtensionCatalog(config.InstallBasePath);
_validator = new Compatibility.CompatibilityValidator(config.HostVersion);
_updateQueue = new Download.UpdateQueue();
_installService = new Installation.ExtensionInstallService(config.InstallBasePath);
// Initialize extension service with empty list (will be updated via ParseAvailableExtensions)
_extensionService = new Services.ExtensionService(
new List<Metadata.AvailableExtension>(),
config.DownloadPath,
_updateQueue,
config.ServerUrl,
config.HostVersion,
_validator,
config.DownloadTimeout,
config.AuthScheme,
config.AuthToken);
// Wire up event handlers
_updateQueue.StateChanged += (sender, args) => UpdateStateChanged?.Invoke(sender, args);
_extensionService.ProgressUpdated += (sender, args) => DownloadProgress?.Invoke(sender, args);
_extensionService.DownloadCompleted += (sender, args) => DownloadCompleted?.Invoke(sender, args);
_extensionService.DownloadFailed += (sender, args) => DownloadFailed?.Invoke(sender, args);
_installService.InstallationCompleted += (sender, args) => InstallationCompleted?.Invoke(sender, args);
_installService.RollbackCompleted += (sender, args) => RollbackCompleted?.Invoke(sender, args);
}
/// <summary>
/// Initializes a new instance of the <see cref="GeneralExtensionHost"/> class.
/// </summary>
/// <param name="hostVersion">The current host application version.</param>
/// <param name="installBasePath">Base directory for extension installations.</param>
/// <param name="downloadPath">Directory for downloading extension packages.</param>
/// <param name="serverUrl">Server base URL for extension queries and downloads.</param>
/// <param name="targetPlatform">The current platform (Windows/Linux/macOS).</param>
/// <param name="downloadTimeout">Download timeout in seconds (default: 300).</param>
/// <param name="authScheme">Optional HTTP authentication scheme (e.g., "Bearer", "Basic").</param>
/// <param name="authToken">Optional HTTP authentication token.</param>
/// <exception cref="ArgumentNullException">Thrown when required parameters are null.</exception>
[Obsolete("Use the constructor that accepts ExtensionHostConfig for better maintainability.")]
public GeneralExtensionHost(
Version hostVersion,
string installBasePath,
string downloadPath,
string serverUrl,
Metadata.TargetPlatform targetPlatform = Metadata.TargetPlatform.Windows,
int downloadTimeout = 300,
string? authScheme = null,
string? authToken = null)
: this(new ExtensionHostConfig
{
HostVersion = hostVersion,
InstallBasePath = installBasePath,
DownloadPath = downloadPath,
ServerUrl = serverUrl,
TargetPlatform = targetPlatform,
DownloadTimeout = downloadTimeout,
AuthScheme = authScheme,
AuthToken = authToken
})
{
}
#region Extension Catalog
/// <summary>
/// Loads all locally installed extensions from the file system.
/// This should be called during application startup to populate the catalog.
/// </summary>
public void LoadInstalledExtensions()
{
_catalog.LoadInstalledExtensions();
}
/// <summary>
/// Gets all locally installed extensions currently in the catalog.
/// </summary>
/// <returns>A list of installed extensions.</returns>
public List<Installation.InstalledExtension> GetInstalledExtensions()
{
return _catalog.GetInstalledExtensions();
}
/// <summary>
/// Gets installed extensions compatible with the current target platform.
/// </summary>
/// <returns>A filtered list of platform-compatible extensions.</returns>
public List<Installation.InstalledExtension> GetInstalledExtensionsForCurrentPlatform()
{
return _catalog.GetInstalledExtensionsByPlatform(_targetPlatform);
}
/// <summary>
/// Retrieves a specific installed extension by its unique identifier.
/// </summary>
/// <param name="extensionName">The extension identifier to search for.</param>
/// <returns>The matching extension if found; otherwise, null.</returns>
public Installation.InstalledExtension? GetInstalledExtensionById(string extensionName)
{
return _catalog.GetInstalledExtensionById(extensionName);
}
/// <summary>
/// Parses available extensions from JSON data received from the server.
/// </summary>
/// <param name="json">JSON string containing extension metadata.</param>
/// <returns>A list of parsed available extensions.</returns>
public List<Metadata.AvailableExtension> ParseAvailableExtensions(string json)
{
var extensions = _catalog.ParseAvailableExtensions(json);
_extensionService.UpdateAvailableExtensions(extensions);
return extensions;
}
/// <summary>
/// Gets available extensions that are compatible with the current host version and platform.
/// Applies both platform and version compatibility filters.
/// </summary>
/// <param name="availableExtensions">List of available extensions from the server.</param>
/// <returns>A filtered list of compatible extensions.</returns>
public List<Metadata.AvailableExtension> GetCompatibleExtensions(List<Metadata.AvailableExtension> availableExtensions)
{
// First filter by platform
var platformFiltered = _catalog.FilterByPlatform(availableExtensions, _targetPlatform);
// Then filter by version compatibility
return _validator.FilterCompatible(platformFiltered);
}
#endregion
#region Update Configuration
/// <summary>
/// Sets the auto-update preference for a specific extension.
/// Changes are persisted in the extension's manifest file.
/// </summary>
/// <param name="extensionName">The extension identifier.</param>
/// <param name="enabled">True to enable auto-updates; false to disable.</param>
public void SetAutoUpdate(string extensionName, bool enabled)
{
var extension = _catalog.GetInstalledExtensionById(extensionName);
if (extension != null)
{
extension.AutoUpdateEnabled = enabled;
_catalog.AddOrUpdateInstalledExtension(extension);
}
}
/// <summary>
/// Gets the auto-update preference for a specific extension.
/// </summary>
/// <param name="extensionName">The extension identifier.</param>
/// <returns>True if auto-updates are enabled; otherwise, false.</returns>
public bool GetAutoUpdate(string extensionName)
{
var extension = _catalog.GetInstalledExtensionById(extensionName);
return extension?.AutoUpdateEnabled ?? false;
}
#endregion
#region Update Operations
/// <summary>
/// Queues an extension for update after validating compatibility and platform support.
/// </summary>
/// <param name="extension">The extension to update.</param>
/// <param name="enableRollback">Whether to enable automatic rollback on installation failure.</param>
/// <returns>The created update operation.</returns>
/// <exception cref="ArgumentNullException">Thrown when <paramref name="extension"/> is null.</exception>
/// <exception cref="InvalidOperationException">Thrown when the extension is incompatible.</exception>
public Download.UpdateOperation QueueUpdate(Metadata.AvailableExtension extension, bool enableRollback = true)
{
if (extension == null)
throw new ArgumentNullException(nameof(extension));
// Verify compatibility
if (!_validator.IsCompatible(extension.Descriptor))
{
throw new InvalidOperationException(
$"Extension '{extension.Descriptor.DisplayName}' is not compatible with host version {_hostVersion}");
}
// Verify platform support
if ((extension.Descriptor.SupportedPlatforms & _targetPlatform) == 0)
{
throw new InvalidOperationException(
$"Extension '{extension.Descriptor.DisplayName}' does not support the current platform");
}
return _updateQueue.Enqueue(extension, enableRollback);
}
/// <summary>
/// Automatically discovers and queues updates for all installed extensions with auto-update enabled.
/// Only considers extensions that have compatible updates available.
/// </summary>
/// <param name="availableExtensions">List of available extensions to check for updates.</param>
/// <returns>A list of update operations that were queued.</returns>
public List<Download.UpdateOperation> QueueAutoUpdates(List<Metadata.AvailableExtension> availableExtensions)
{
if (!_globalAutoUpdateEnabled)
return new List<Download.UpdateOperation>();
var queuedOperations = new List<Download.UpdateOperation>();
var installedExtensions = _catalog.GetInstalledExtensions();
foreach (var installed in installedExtensions)
{
if (!installed.AutoUpdateEnabled)
continue;
// Find available versions for this extension
var versions = availableExtensions
.Where(ext => ext.Descriptor.Name == installed.Descriptor.Name)
.ToList();
if (!versions.Any())
continue;
// Get the latest compatible update
var update = _validator.GetCompatibleUpdate(installed, versions);
if (update != null)
{
try
{
var operation = QueueUpdate(update, true);
queuedOperations.Add(operation);
}
catch (Exception ex)
{
// Log error but continue processing other extensions
GeneralUpdate.Common.Shared.GeneralTracer.Error(
$"Failed to queue auto-update for extension {installed.Descriptor.Name}", ex);
}
}
}
return queuedOperations;
}
/// <summary>
/// Finds the best upgrade version for a specific extension.
/// Selects the minimum supported version among the latest compatible versions.
/// </summary>
/// <param name="extensionName">The extension identifier.</param>
/// <param name="availableExtensions">Available versions of the extension.</param>
/// <returns>The best compatible version if found; otherwise, null.</returns>
public Metadata.AvailableExtension? FindBestUpgrade(string extensionName, List<Metadata.AvailableExtension> availableExtensions)
{
var versions = availableExtensions
.Where(ext => ext.Descriptor.Name == extensionName)
.ToList();
return _validator.FindMinimumSupportedLatest(versions);
}
/// <summary>
/// Processes the next queued update operation by downloading and installing the extension.
/// </summary>
/// <returns>A task that represents the asynchronous operation. The task result indicates success.</returns>
public async Task<bool> ProcessNextUpdateAsync()
{
var operation = _updateQueue.GetNextQueued();
if (operation == null)
return false;
try
{
// Download the extension package
var downloadedPath = await _extensionService.DownloadAsync(operation);
if (downloadedPath == null)
return false;
// Install the extension
var installed = await _installService.InstallAsync(
downloadedPath,
operation.Extension.Descriptor,
operation.EnableRollback);
if (installed != null)
{
_catalog.AddOrUpdateInstalledExtension(installed);
_updateQueue.ChangeState(operation.OperationId, Download.UpdateState.UpdateSuccessful);
return true;
}
else
{
_updateQueue.ChangeState(operation.OperationId, Download.UpdateState.UpdateFailed, "Installation failed");
return false;
}
}
catch (Exception ex)
{
_updateQueue.ChangeState(operation.OperationId, Download.UpdateState.UpdateFailed, ex.Message);
GeneralUpdate.Common.Shared.GeneralTracer.Error($"Failed to process update for operation {operation.OperationId}", ex);
return false;
}
}
/// <summary>
/// Processes all queued update operations sequentially.
/// Continues processing until the queue is empty.
/// </summary>
/// <returns>A task that represents the asynchronous operation.</returns>
public async Task ProcessAllUpdatesAsync()
{
while (await ProcessNextUpdateAsync())
{
// Continue processing until queue is empty
}
}
/// <summary>
/// Gets all update operations currently in the queue.
/// </summary>
/// <returns>A list of all update operations.</returns>
public List<Download.UpdateOperation> GetUpdateQueue()
{
return _updateQueue.GetAllOperations();
}
/// <summary>
/// Gets update operations filtered by their current state.
/// </summary>
/// <param name="state">The state to filter by.</param>
/// <returns>A list of operations with the specified state.</returns>
public List<Download.UpdateOperation> GetUpdatesByState(Download.UpdateState state)
{
return _updateQueue.GetOperationsByState(state);
}
/// <summary>
/// Clears completed update operations from the queue to prevent memory accumulation.
/// Removes operations that are successful, failed, or cancelled.
/// </summary>
public void ClearCompletedUpdates()
{
_updateQueue.ClearCompleted();
}
#endregion
#region Compatibility
/// <summary>
/// Checks if an extension descriptor is compatible with the current host version.
/// </summary>
/// <param name="descriptor">The extension descriptor to validate.</param>
/// <returns>True if compatible; otherwise, false.</returns>
public bool IsCompatible(Metadata.ExtensionDescriptor descriptor)
{
return _validator.IsCompatible(descriptor);
}
#endregion
}
}