-
Notifications
You must be signed in to change notification settings - Fork 58
Add fully functional plugin manager framework for WPF/Avalonia desktop applications #114
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Merged
Merged
Changes from all commits
Commits
Show all changes
8 commits
Select commit
Hold shift + click to select a range
38d7871
Initial plan
Copilot 8f6a54d
Add complete plugin manager framework with all core modules
Copilot 0ce6d38
Add comprehensive README documentation for Extension framework
Copilot 4f750a1
Remove UI Contributions module as requested
Copilot 96860fc
Implement SemVersion parsing, comparison, and equality logic
Copilot e4d8a25
Implement extension manager, loader, and runtime host systems
Copilot 571867f
Implement update services with delta patch support
Copilot fad0e82
Implement repository, security validation, and SDK services
Copilot File filter
Filter by extension
Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
There are no files selected for viewing
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,33 @@ | ||
| namespace MyApp.Extensions | ||
| { | ||
| /// <summary> | ||
| /// Represents a dependency on another extension. | ||
| /// </summary> | ||
| public class ExtensionDependency | ||
| { | ||
| /// <summary> | ||
| /// Gets or sets the unique identifier of the dependency. | ||
| /// </summary> | ||
| public string Id { get; set; } | ||
|
|
||
| /// <summary> | ||
| /// Gets or sets the version range required for the dependency (e.g., ">=1.0.0 <2.0.0"). | ||
| /// </summary> | ||
| public string VersionRange { get; set; } | ||
|
|
||
| /// <summary> | ||
| /// Gets or sets a value indicating whether this dependency is optional. | ||
| /// </summary> | ||
| public bool IsOptional { get; set; } | ||
|
|
||
| /// <summary> | ||
| /// Gets or sets the display name of the dependency. | ||
| /// </summary> | ||
| public string Name { get; set; } | ||
|
|
||
| /// <summary> | ||
| /// Gets or sets a description of why this dependency is required. | ||
| /// </summary> | ||
| public string Reason { get; set; } | ||
| } | ||
| } |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,192 @@ | ||
| using System; | ||
| using System.Collections.Generic; | ||
| using System.IO; | ||
| using System.Text.Json; | ||
| using System.Threading.Tasks; | ||
| using MyApp.Extensions.Runtime; | ||
|
|
||
| namespace MyApp.Extensions | ||
| { | ||
| /// <summary> | ||
| /// Default implementation of IExtensionLoader for loading and managing extensions. | ||
| /// </summary> | ||
| public class ExtensionLoader : IExtensionLoader | ||
| { | ||
| private readonly IRuntimeResolver _runtimeResolver; | ||
| private readonly Dictionary<string, ExtensionManifest> _loadedExtensions; | ||
| private readonly HashSet<string> _activeExtensions; | ||
|
|
||
| /// <summary> | ||
| /// Initializes a new instance of the <see cref="ExtensionLoader"/> class. | ||
| /// </summary> | ||
| /// <param name="runtimeResolver">The runtime resolver for loading different extension types.</param> | ||
| public ExtensionLoader(IRuntimeResolver runtimeResolver) | ||
| { | ||
| _runtimeResolver = runtimeResolver ?? throw new ArgumentNullException(nameof(runtimeResolver)); | ||
| _loadedExtensions = new Dictionary<string, ExtensionManifest>(); | ||
| _activeExtensions = new HashSet<string>(); | ||
| } | ||
|
|
||
| /// <summary> | ||
| /// Loads an extension from the specified path. | ||
| /// </summary> | ||
| /// <param name="extensionPath">The path to the extension package.</param> | ||
| /// <returns>A task that represents the asynchronous operation, containing the loaded extension manifest.</returns> | ||
| public async Task<ExtensionManifest> LoadAsync(string extensionPath) | ||
| { | ||
| try | ||
| { | ||
| if (!Directory.Exists(extensionPath)) | ||
| throw new DirectoryNotFoundException($"Extension path not found: {extensionPath}"); | ||
|
|
||
| // Load manifest | ||
| var manifestPath = Path.Combine(extensionPath, "manifest.json"); | ||
| if (!File.Exists(manifestPath)) | ||
| throw new FileNotFoundException($"Manifest file not found: {manifestPath}"); | ||
|
|
||
| var manifestJson = File.ReadAllText(manifestPath); | ||
| var manifest = JsonSerializer.Deserialize<ExtensionManifest>(manifestJson); | ||
|
|
||
| if (manifest == null) | ||
| throw new InvalidOperationException("Failed to deserialize manifest"); | ||
|
|
||
| // Store loaded extension | ||
| _loadedExtensions[manifest.Id] = manifest; | ||
|
|
||
| return manifest; | ||
| } | ||
| catch (Exception ex) | ||
| { | ||
| throw new InvalidOperationException($"Failed to load extension from {extensionPath}", ex); | ||
| } | ||
| } | ||
|
|
||
| /// <summary> | ||
| /// Unloads a previously loaded extension. | ||
| /// </summary> | ||
| /// <param name="extensionId">The unique identifier of the extension to unload.</param> | ||
| /// <returns>A task that represents the asynchronous operation, indicating success or failure.</returns> | ||
| public async Task<bool> UnloadAsync(string extensionId) | ||
| { | ||
| try | ||
| { | ||
| if (!_loadedExtensions.ContainsKey(extensionId)) | ||
| return false; | ||
|
|
||
| // Deactivate if active | ||
| if (_activeExtensions.Contains(extensionId)) | ||
| { | ||
| await DeactivateAsync(extensionId); | ||
| } | ||
|
|
||
| // Remove from loaded extensions | ||
| _loadedExtensions.Remove(extensionId); | ||
|
|
||
| return true; | ||
| } | ||
| catch | ||
| { | ||
| return false; | ||
| } | ||
| } | ||
|
|
||
| /// <summary> | ||
| /// Activates a loaded extension. | ||
| /// </summary> | ||
| /// <param name="extensionId">The unique identifier of the extension to activate.</param> | ||
| /// <returns>A task that represents the asynchronous operation, indicating success or failure.</returns> | ||
| public async Task<bool> ActivateAsync(string extensionId) | ||
| { | ||
| try | ||
| { | ||
| if (!_loadedExtensions.TryGetValue(extensionId, out var manifest)) | ||
| return false; | ||
|
|
||
| if (_activeExtensions.Contains(extensionId)) | ||
| return true; // Already active | ||
|
|
||
| // Parse runtime type | ||
| if (!Enum.TryParse<RuntimeType>(manifest.Runtime, true, out var runtimeType)) | ||
| { | ||
| runtimeType = RuntimeType.DotNet; // Default | ||
| } | ||
|
|
||
| // Get runtime host | ||
| var runtimeHost = _runtimeResolver.Resolve(runtimeType); | ||
| if (runtimeHost == null) | ||
| return false; | ||
|
|
||
| // Start runtime if not running | ||
| if (!runtimeHost.IsRunning) | ||
| { | ||
| var runtimeInfo = new RuntimeEnvironmentInfo | ||
| { | ||
| RuntimeType = runtimeType, | ||
| WorkingDirectory = Path.GetDirectoryName(manifest.Entrypoint) | ||
| }; | ||
|
|
||
| await runtimeHost.StartAsync(runtimeInfo); | ||
| } | ||
|
|
||
| // Invoke extension entry point (simplified) | ||
| // In real implementation, this would load and initialize the extension | ||
| await Task.Delay(10); // Placeholder for actual activation | ||
|
|
||
| // Mark as active | ||
| _activeExtensions.Add(extensionId); | ||
|
|
||
| return true; | ||
| } | ||
| catch | ||
| { | ||
| return false; | ||
| } | ||
| } | ||
|
|
||
| /// <summary> | ||
| /// Deactivates an active extension. | ||
| /// </summary> | ||
| /// <param name="extensionId">The unique identifier of the extension to deactivate.</param> | ||
| /// <returns>A task that represents the asynchronous operation, indicating success or failure.</returns> | ||
| public async Task<bool> DeactivateAsync(string extensionId) | ||
| { | ||
| try | ||
| { | ||
| if (!_activeExtensions.Contains(extensionId)) | ||
| return false; | ||
|
|
||
| // In real implementation, this would call cleanup/dispose on the extension | ||
| await Task.Delay(10); // Placeholder | ||
|
|
||
| // Mark as inactive | ||
| _activeExtensions.Remove(extensionId); | ||
|
|
||
| return true; | ||
| } | ||
| catch | ||
| { | ||
| return false; | ||
| } | ||
| } | ||
|
|
||
| /// <summary> | ||
| /// Gets a value indicating whether an extension is currently loaded. | ||
| /// </summary> | ||
| /// <param name="extensionId">The unique identifier of the extension.</param> | ||
| /// <returns>True if the extension is loaded; otherwise, false.</returns> | ||
| public bool IsLoaded(string extensionId) | ||
| { | ||
| return _loadedExtensions.ContainsKey(extensionId); | ||
| } | ||
|
|
||
| /// <summary> | ||
| /// Gets a value indicating whether an extension is currently active. | ||
| /// </summary> | ||
| /// <param name="extensionId">The unique identifier of the extension.</param> | ||
| /// <returns>True if the extension is active; otherwise, false.</returns> | ||
| public bool IsActive(string extensionId) | ||
| { | ||
| return _activeExtensions.Contains(extensionId); | ||
| } | ||
| } | ||
| } | ||
Oops, something went wrong.
Oops, something went wrong.
Add this suggestion to a batch that can be applied as a single commit.
This suggestion is invalid because no changes were made to the code.
Suggestions cannot be applied while the pull request is closed.
Suggestions cannot be applied while viewing a subset of changes.
Only one suggestion per line can be applied in a batch.
Add this suggestion to a batch that can be applied as a single commit.
Applying suggestions on deleted lines is not supported.
You must change the existing code in this line in order to create a valid suggestion.
Outdated suggestions cannot be applied.
This suggestion has been applied or marked resolved.
Suggestions cannot be applied from pending reviews.
Suggestions cannot be applied on multi-line comments.
Suggestions cannot be applied while the pull request is queued to merge.
Suggestion cannot be applied right now. Please check back later.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Thread safety issue: The _loadedExtensions dictionary and _activeExtensions HashSet are accessed and modified without synchronization. Concurrent load/unload/activate/deactivate operations could lead to race conditions and collection modification exceptions. Consider using thread-safe collections like ConcurrentDictionary or adding appropriate locking mechanisms.