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
33 changes: 33 additions & 0 deletions src/c#/GeneralUpdate.Extension/ExtensionDependency.cs
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; }
}
}
192 changes: 192 additions & 0 deletions src/c#/GeneralUpdate.Extension/ExtensionLoader.cs
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>();
}
Comment on lines +16 to +28
Copy link

Copilot AI Jan 23, 2026

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.

Copilot uses AI. Check for mistakes.

/// <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);
}
}
}
Loading
Loading