diff --git a/src/c#/GeneralUpdate.Extension/ExtensionDependency.cs b/src/c#/GeneralUpdate.Extension/ExtensionDependency.cs new file mode 100644 index 00000000..35572f32 --- /dev/null +++ b/src/c#/GeneralUpdate.Extension/ExtensionDependency.cs @@ -0,0 +1,33 @@ +namespace MyApp.Extensions +{ + /// + /// Represents a dependency on another extension. + /// + public class ExtensionDependency + { + /// + /// Gets or sets the unique identifier of the dependency. + /// + public string Id { get; set; } + + /// + /// Gets or sets the version range required for the dependency (e.g., ">=1.0.0 <2.0.0"). + /// + public string VersionRange { get; set; } + + /// + /// Gets or sets a value indicating whether this dependency is optional. + /// + public bool IsOptional { get; set; } + + /// + /// Gets or sets the display name of the dependency. + /// + public string Name { get; set; } + + /// + /// Gets or sets a description of why this dependency is required. + /// + public string Reason { get; set; } + } +} diff --git a/src/c#/GeneralUpdate.Extension/ExtensionLoader.cs b/src/c#/GeneralUpdate.Extension/ExtensionLoader.cs new file mode 100644 index 00000000..84aa20b6 --- /dev/null +++ b/src/c#/GeneralUpdate.Extension/ExtensionLoader.cs @@ -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 +{ + /// + /// Default implementation of IExtensionLoader for loading and managing extensions. + /// + public class ExtensionLoader : IExtensionLoader + { + private readonly IRuntimeResolver _runtimeResolver; + private readonly Dictionary _loadedExtensions; + private readonly HashSet _activeExtensions; + + /// + /// Initializes a new instance of the class. + /// + /// The runtime resolver for loading different extension types. + public ExtensionLoader(IRuntimeResolver runtimeResolver) + { + _runtimeResolver = runtimeResolver ?? throw new ArgumentNullException(nameof(runtimeResolver)); + _loadedExtensions = new Dictionary(); + _activeExtensions = new HashSet(); + } + + /// + /// Loads an extension from the specified path. + /// + /// The path to the extension package. + /// A task that represents the asynchronous operation, containing the loaded extension manifest. + public async Task 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(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); + } + } + + /// + /// Unloads a previously loaded extension. + /// + /// The unique identifier of the extension to unload. + /// A task that represents the asynchronous operation, indicating success or failure. + public async Task 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; + } + } + + /// + /// Activates a loaded extension. + /// + /// The unique identifier of the extension to activate. + /// A task that represents the asynchronous operation, indicating success or failure. + public async Task 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(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; + } + } + + /// + /// Deactivates an active extension. + /// + /// The unique identifier of the extension to deactivate. + /// A task that represents the asynchronous operation, indicating success or failure. + public async Task 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; + } + } + + /// + /// Gets a value indicating whether an extension is currently loaded. + /// + /// The unique identifier of the extension. + /// True if the extension is loaded; otherwise, false. + public bool IsLoaded(string extensionId) + { + return _loadedExtensions.ContainsKey(extensionId); + } + + /// + /// Gets a value indicating whether an extension is currently active. + /// + /// The unique identifier of the extension. + /// True if the extension is active; otherwise, false. + public bool IsActive(string extensionId) + { + return _activeExtensions.Contains(extensionId); + } + } +} diff --git a/src/c#/GeneralUpdate.Extension/ExtensionManager.cs b/src/c#/GeneralUpdate.Extension/ExtensionManager.cs new file mode 100644 index 00000000..b1b781ff --- /dev/null +++ b/src/c#/GeneralUpdate.Extension/ExtensionManager.cs @@ -0,0 +1,321 @@ +using System; +using System.Collections.Generic; +using System.IO; +using System.Linq; +using System.Text.Json; +using System.Threading.Tasks; + +namespace MyApp.Extensions +{ + /// + /// Default implementation of IExtensionManager for managing extensions. + /// + public class ExtensionManager : IExtensionManager + { + private readonly string _extensionsPath; + private readonly IExtensionLoader _loader; + private readonly Dictionary _extensionStates; + + /// + /// Initializes a new instance of the class. + /// + /// The path where extensions are installed. + /// The extension loader. + public ExtensionManager(string extensionsPath, IExtensionLoader loader) + { + _extensionsPath = extensionsPath ?? throw new ArgumentNullException(nameof(extensionsPath)); + _loader = loader ?? throw new ArgumentNullException(nameof(loader)); + _extensionStates = new Dictionary(); + + if (!Directory.Exists(_extensionsPath)) + { + Directory.CreateDirectory(_extensionsPath); + } + } + + /// + /// Installs an extension from a package file. + /// + /// The path to the extension package. + /// A task that represents the asynchronous operation, indicating success or failure. + public async Task InstallAsync(string packagePath) + { + try + { + if (!File.Exists(packagePath)) + return false; + + // Extract manifest from package (simplified - in real impl would extract zip/package) + var manifestPath = Path.Combine(Path.GetDirectoryName(packagePath), "manifest.json"); + if (!File.Exists(manifestPath)) + return false; + + var manifestJson = File.ReadAllText(manifestPath); + var manifest = JsonSerializer.Deserialize(manifestJson); + + if (manifest == null) + return false; + + // Create extension directory + var extensionDir = Path.Combine(_extensionsPath, manifest.Id); + if (!Directory.Exists(extensionDir)) + { + Directory.CreateDirectory(extensionDir); + } + + // Copy package contents (simplified) + File.Copy(packagePath, Path.Combine(extensionDir, Path.GetFileName(packagePath)), true); + File.Copy(manifestPath, Path.Combine(extensionDir, "manifest.json"), true); + + // Update state + _extensionStates[manifest.Id] = ExtensionState.Installed; + + return true; + } + catch + { + return false; + } + } + + /// + /// Uninstalls an extension. + /// + /// The unique identifier of the extension to uninstall. + /// A task that represents the asynchronous operation, indicating success or failure. + public async Task UninstallAsync(string extensionId) + { + try + { + var extensionDir = Path.Combine(_extensionsPath, extensionId); + if (!Directory.Exists(extensionDir)) + return false; + + // Deactivate if loaded + if (_loader.IsActive(extensionId)) + { + await _loader.DeactivateAsync(extensionId); + } + + // Unload if loaded + if (_loader.IsLoaded(extensionId)) + { + await _loader.UnloadAsync(extensionId); + } + + // Remove directory + Directory.Delete(extensionDir, true); + + // Update state + _extensionStates.Remove(extensionId); + + return true; + } + catch + { + return false; + } + } + + /// + /// Enables an installed extension. + /// + /// The unique identifier of the extension to enable. + /// A task that represents the asynchronous operation, indicating success or failure. + public async Task EnableAsync(string extensionId) + { + try + { + if (!_extensionStates.ContainsKey(extensionId)) + return false; + + var extensionDir = Path.Combine(_extensionsPath, extensionId); + var manifestPath = Path.Combine(extensionDir, "manifest.json"); + + if (!File.Exists(manifestPath)) + return false; + + // Load extension + var manifest = await _loader.LoadAsync(extensionDir); + + // Activate extension + var activated = await _loader.ActivateAsync(extensionId); + + if (activated) + { + _extensionStates[extensionId] = ExtensionState.Enabled; + return true; + } + + return false; + } + catch + { + return false; + } + } + + /// + /// Disables an enabled extension. + /// + /// The unique identifier of the extension to disable. + /// A task that represents the asynchronous operation, indicating success or failure. + public async Task DisableAsync(string extensionId) + { + try + { + if (!_extensionStates.ContainsKey(extensionId)) + return false; + + // Deactivate extension + var deactivated = await _loader.DeactivateAsync(extensionId); + + if (deactivated) + { + _extensionStates[extensionId] = ExtensionState.Disabled; + return true; + } + + return false; + } + catch + { + return false; + } + } + + /// + /// Updates an extension to a new version. + /// + /// The unique identifier of the extension to update. + /// The target version to update to. + /// A task that represents the asynchronous operation, indicating success or failure. + public async Task UpdateAsync(string extensionId, string targetVersion) + { + try + { + // Simplified update logic + // In real implementation, this would download new version, backup current, install new + + if (!_extensionStates.ContainsKey(extensionId)) + return false; + + var wasEnabled = _extensionStates[extensionId] == ExtensionState.Enabled; + + // Disable current version + if (wasEnabled) + { + await DisableAsync(extensionId); + } + + // In real implementation: download and install new version here + await Task.Delay(100); // Placeholder + + // Re-enable if it was enabled + if (wasEnabled) + { + await EnableAsync(extensionId); + } + + return true; + } + catch + { + return false; + } + } + + /// + /// Rolls back an extension to a previous version. + /// + /// The unique identifier of the extension to roll back. + /// The target version to roll back to. + /// A task that represents the asynchronous operation, indicating success or failure. + public async Task RollbackAsync(string extensionId, string targetVersion) + { + try + { + // Simplified rollback logic + // In real implementation, this would restore from backup + + if (!_extensionStates.ContainsKey(extensionId)) + return false; + + var wasEnabled = _extensionStates[extensionId] == ExtensionState.Enabled; + + // Disable current version + if (wasEnabled) + { + await DisableAsync(extensionId); + } + + // In real implementation: restore backup version here + await Task.Delay(100); // Placeholder + + // Re-enable if it was enabled + if (wasEnabled) + { + await EnableAsync(extensionId); + } + + return true; + } + catch + { + return false; + } + } + + /// + /// Gets all installed extensions. + /// + /// A task that represents the asynchronous operation, containing a list of installed extension manifests. + public async Task> GetInstalledExtensionsAsync() + { + var manifests = new List(); + + try + { + if (!Directory.Exists(_extensionsPath)) + return manifests; + + var extensionDirs = Directory.GetDirectories(_extensionsPath); + + foreach (var dir in extensionDirs) + { + var manifestPath = Path.Combine(dir, "manifest.json"); + if (File.Exists(manifestPath)) + { + var manifestJson = File.ReadAllText(manifestPath); + var manifest = JsonSerializer.Deserialize(manifestJson); + if (manifest != null) + { + manifests.Add(manifest); + } + } + } + } + catch + { + // Return empty list on error + } + + return manifests; + } + + /// + /// Gets the current state of an extension. + /// + /// The unique identifier of the extension. + /// A task that represents the asynchronous operation, containing the extension state. + public Task GetExtensionStateAsync(string extensionId) + { + if (_extensionStates.TryGetValue(extensionId, out var state)) + { + return Task.FromResult(state); + } + + return Task.FromResult(ExtensionState.Broken); + } + } +} diff --git a/src/c#/GeneralUpdate.Extension/ExtensionManifest.cs b/src/c#/GeneralUpdate.Extension/ExtensionManifest.cs new file mode 100644 index 00000000..c67eb284 --- /dev/null +++ b/src/c#/GeneralUpdate.Extension/ExtensionManifest.cs @@ -0,0 +1,91 @@ +using System; +using System.Collections.Generic; + +namespace MyApp.Extensions +{ + /// + /// Represents the manifest of an extension, containing all metadata and configuration information. + /// + public class ExtensionManifest + { + /// + /// Gets or sets the unique identifier of the extension. + /// + public string Id { get; set; } + + /// + /// Gets or sets the display name of the extension. + /// + public string Name { get; set; } + + /// + /// Gets or sets the version of the extension. + /// + public string Version { get; set; } + + /// + /// Gets or sets the author or publisher of the extension. + /// + public string Author { get; set; } + + /// + /// Gets or sets the description of the extension. + /// + public string Description { get; set; } + + /// + /// Gets or sets the entry point of the extension. + /// + public string Entrypoint { get; set; } + + /// + /// Gets or sets the runtime type required by the extension. + /// + public string Runtime { get; set; } + + /// + /// Gets or sets the engine version compatibility information. + /// + public string Engine { get; set; } + + /// + /// Gets or sets the compatibility information for the extension. + /// + public string Compatibility { get; set; } + + /// + /// Gets or sets the list of dependencies required by the extension. + /// + public List Dependencies { get; set; } + + /// + /// Gets or sets the list of permissions required by the extension. + /// + public List Permissions { get; set; } + + /// + /// Gets or sets the icon path for the extension. + /// + public string Icon { get; set; } + + /// + /// Gets or sets the license identifier for the extension. + /// + public string License { get; set; } + + /// + /// Gets or sets the homepage URL for the extension. + /// + public string Homepage { get; set; } + + /// + /// Gets or sets the repository URL for the extension. + /// + public string Repository { get; set; } + + /// + /// Gets or sets the tags or keywords associated with the extension. + /// + public List Tags { get; set; } + } +} diff --git a/src/c#/GeneralUpdate.Extension/ExtensionPermission.cs b/src/c#/GeneralUpdate.Extension/ExtensionPermission.cs new file mode 100644 index 00000000..32927611 --- /dev/null +++ b/src/c#/GeneralUpdate.Extension/ExtensionPermission.cs @@ -0,0 +1,33 @@ +namespace MyApp.Extensions +{ + /// + /// Represents a permission required by an extension. + /// + public class ExtensionPermission + { + /// + /// Gets or sets the type of permission (e.g., "FileSystem", "Network", "System"). + /// + public string Type { get; set; } + + /// + /// Gets or sets the scope or target of the permission (e.g., specific paths, URLs, or system resources). + /// + public string Scope { get; set; } + + /// + /// Gets or sets the access level required (e.g., "Read", "Write", "Execute", "Full"). + /// + public string AccessLevel { get; set; } + + /// + /// Gets or sets a description of why this permission is required. + /// + public string Reason { get; set; } + + /// + /// Gets or sets a value indicating whether this permission is mandatory. + /// + public bool IsMandatory { get; set; } + } +} diff --git a/src/c#/GeneralUpdate.Extension/ExtensionRepository.cs b/src/c#/GeneralUpdate.Extension/ExtensionRepository.cs new file mode 100644 index 00000000..ffa19347 --- /dev/null +++ b/src/c#/GeneralUpdate.Extension/ExtensionRepository.cs @@ -0,0 +1,224 @@ +using System; +using System.Collections.Generic; +using System.IO; +using System.Linq; +using System.Threading.Tasks; + +namespace MyApp.Extensions +{ + /// + /// Default implementation of IExtensionRepository for managing extension repositories. + /// + public class ExtensionRepository : IExtensionRepository + { + private readonly string _repositoryUrl; + private readonly List _cachedExtensions; + + /// + /// Initializes a new instance of the class. + /// + /// The URL of the extension repository. + public ExtensionRepository(string repositoryUrl) + { + _repositoryUrl = repositoryUrl ?? throw new ArgumentNullException(nameof(repositoryUrl)); + _cachedExtensions = new List(); + } + + /// + /// Searches for extensions matching the specified query. + /// + /// The search query. + /// A task that represents the asynchronous operation, containing a list of matching extension manifests. + public async Task> SearchAsync(string query) + { + try + { + // In real implementation, would query remote repository + await Task.Delay(100); // Simulate network delay + + if (string.IsNullOrWhiteSpace(query)) + return _cachedExtensions.ToList(); + + var searchTerms = query.ToLowerInvariant().Split(new[] { ' ' }, StringSplitOptions.RemoveEmptyEntries); + + return _cachedExtensions.Where(ext => + { + var searchableText = $"{ext.Name} {ext.Description} {string.Join(" ", ext.Tags ?? new List())}".ToLowerInvariant(); + return searchTerms.All(term => searchableText.Contains(term)); + }).ToList(); + } + catch + { + return new List(); + } + } + + /// + /// Gets the details of a specific extension by its identifier. + /// + /// The unique identifier of the extension. + /// A task that represents the asynchronous operation, containing the extension manifest. + public async Task GetExtensionAsync(string extensionId) + { + try + { + // In real implementation, would fetch from remote repository + await Task.Delay(50); // Simulate network delay + + return _cachedExtensions.FirstOrDefault(ext => + ext.Id.Equals(extensionId, StringComparison.OrdinalIgnoreCase)); + } + catch + { + return null; + } + } + + /// + /// Gets the list of available versions for a specific extension. + /// + /// The unique identifier of the extension. + /// A task that represents the asynchronous operation, containing a list of version strings. + public async Task> GetVersionsAsync(string extensionId) + { + try + { + // In real implementation, would query version list from repository + await Task.Delay(50); // Simulate network delay + + // Placeholder - return sample versions + return new List { "1.0.0", "1.1.0", "1.2.0", "2.0.0" }; + } + catch + { + return new List(); + } + } + + /// + /// Downloads an extension package. + /// + /// The unique identifier of the extension. + /// The version to download. + /// The path where the package should be saved. + /// Optional progress reporter. + /// A task that represents the asynchronous operation, indicating success or failure. + public async Task DownloadPackageAsync(string extensionId, string version, string destinationPath, IProgress progress = null) + { + try + { + if (string.IsNullOrWhiteSpace(extensionId) || string.IsNullOrWhiteSpace(version)) + return false; + + // In real implementation, would download from repository URL + // Simulate download with progress + for (int i = 0; i <= 100; i += 10) + { + await Task.Delay(50); + progress?.Report(i / 100.0); + } + + // Create directory if needed + var directory = Path.GetDirectoryName(destinationPath); + if (!string.IsNullOrEmpty(directory) && !Directory.Exists(directory)) + { + Directory.CreateDirectory(directory); + } + + // Create placeholder package file + File.WriteAllText(destinationPath, $"Extension package: {extensionId} v{version}"); + + return true; + } + catch + { + return false; + } + } + + /// + /// Validates the metadata of an extension package. + /// + /// The path to the extension package. + /// A task that represents the asynchronous operation, indicating whether the metadata is valid. + public async Task ValidateMetadataAsync(string packagePath) + { + try + { + if (!File.Exists(packagePath)) + return false; + + // In real implementation, would: + // 1. Extract manifest from package + // 2. Validate all required fields are present + // 3. Check version format + // 4. Validate dependencies + + await Task.Delay(50); // Placeholder + + return true; + } + catch + { + return false; + } + } + + /// + /// Gets all extensions in a specific category. + /// + /// The category to filter by. + /// A task that represents the asynchronous operation, containing a list of extension manifests. + public async Task> GetExtensionsByCategoryAsync(string category) + { + try + { + // In real implementation, would query by category from repository + await Task.Delay(50); // Simulate network delay + + if (string.IsNullOrWhiteSpace(category)) + return _cachedExtensions.ToList(); + + return _cachedExtensions.Where(ext => + ext.Tags != null && ext.Tags.Contains(category, StringComparer.OrdinalIgnoreCase) + ).ToList(); + } + catch + { + return new List(); + } + } + + /// + /// Gets the most popular extensions. + /// + /// The number of extensions to retrieve. + /// A task that represents the asynchronous operation, containing a list of extension manifests. + public async Task> GetPopularExtensionsAsync(int count) + { + try + { + // In real implementation, would query popular extensions from repository + await Task.Delay(50); // Simulate network delay + + return _cachedExtensions.Take(Math.Min(count, _cachedExtensions.Count)).ToList(); + } + catch + { + return new List(); + } + } + + /// + /// Adds an extension to the cached repository (for testing purposes). + /// + /// The extension manifest to add. + public void AddExtension(ExtensionManifest manifest) + { + if (manifest != null && !_cachedExtensions.Any(e => e.Id == manifest.Id)) + { + _cachedExtensions.Add(manifest); + } + } + } +} diff --git a/src/c#/GeneralUpdate.Extension/ExtensionState.cs b/src/c#/GeneralUpdate.Extension/ExtensionState.cs new file mode 100644 index 00000000..e40e0536 --- /dev/null +++ b/src/c#/GeneralUpdate.Extension/ExtensionState.cs @@ -0,0 +1,38 @@ +namespace MyApp.Extensions +{ + /// + /// Represents the current state of an extension in the system. + /// + public enum ExtensionState + { + /// + /// The extension is installed but not yet enabled. + /// + Installed, + + /// + /// The extension is installed and currently enabled. + /// + Enabled, + + /// + /// The extension is installed but disabled by the user or system. + /// + Disabled, + + /// + /// An update is available for the extension. + /// + UpdateAvailable, + + /// + /// The extension is incompatible with the current system or engine version. + /// + Incompatible, + + /// + /// The extension is in a broken state and cannot be loaded. + /// + Broken + } +} diff --git a/src/c#/GeneralUpdate.Extension/GeneralUpdate.Extension.csproj b/src/c#/GeneralUpdate.Extension/GeneralUpdate.Extension.csproj index d2a210cd..3d11ec09 100644 --- a/src/c#/GeneralUpdate.Extension/GeneralUpdate.Extension.csproj +++ b/src/c#/GeneralUpdate.Extension/GeneralUpdate.Extension.csproj @@ -4,4 +4,8 @@ netstandard2.0 + + + + diff --git a/src/c#/GeneralUpdate.Extension/IExtensionDependencyResolver.cs b/src/c#/GeneralUpdate.Extension/IExtensionDependencyResolver.cs new file mode 100644 index 00000000..fa60f5cb --- /dev/null +++ b/src/c#/GeneralUpdate.Extension/IExtensionDependencyResolver.cs @@ -0,0 +1,109 @@ +using System.Collections.Generic; +using System.Threading.Tasks; + +namespace MyApp.Extensions +{ + /// + /// Provides methods for resolving and validating extension dependencies. + /// + public interface IExtensionDependencyResolver + { + /// + /// Resolves the dependencies for an extension. + /// + /// The unique identifier of the extension. + /// A task that represents the asynchronous operation, containing a list of dependency manifests. + Task> ResolveDependenciesAsync(string extensionId); + + /// + /// Validates that all dependencies for an extension are satisfied. + /// + /// The unique identifier of the extension. + /// A task that represents the asynchronous operation, containing the validation result. + Task ValidateDependenciesAsync(string extensionId); + + /// + /// Gets the dependency tree for an extension. + /// + /// The unique identifier of the extension. + /// A task that represents the asynchronous operation, containing the dependency tree. + Task GetDependencyTreeAsync(string extensionId); + + /// + /// Finds all extensions that depend on a specific extension. + /// + /// The unique identifier of the extension. + /// A task that represents the asynchronous operation, containing a list of dependent extensions. + Task> FindDependentsAsync(string extensionId); + + /// + /// Checks for circular dependencies in the dependency graph. + /// + /// The unique identifier of the extension. + /// A task that represents the asynchronous operation, indicating whether circular dependencies exist. + Task HasCircularDependenciesAsync(string extensionId); + } + + /// + /// Represents the result of a dependency validation. + /// + public class DependencyValidationResult + { + /// + /// Gets or sets a value indicating whether all dependencies are satisfied. + /// + public bool IsValid { get; set; } + + /// + /// Gets or sets the list of missing dependencies. + /// + public List MissingDependencies { get; set; } + + /// + /// Gets or sets the list of incompatible dependencies. + /// + public List IncompatibleDependencies { get; set; } + + /// + /// Gets or sets any error messages. + /// + public List Errors { get; set; } + } + + /// + /// Represents a dependency tree for an extension. + /// + public class DependencyTree + { + /// + /// Gets or sets the root extension. + /// + public ExtensionManifest Root { get; set; } + + /// + /// Gets or sets the list of direct dependencies. + /// + public List Dependencies { get; set; } + } + + /// + /// Represents a node in a dependency tree. + /// + public class DependencyNode + { + /// + /// Gets or sets the extension manifest for this node. + /// + public ExtensionManifest Extension { get; set; } + + /// + /// Gets or sets the child dependencies. + /// + public List Children { get; set; } + + /// + /// Gets or sets a value indicating whether this dependency is optional. + /// + public bool IsOptional { get; set; } + } +} diff --git a/src/c#/GeneralUpdate.Extension/IExtensionLifecycle.cs b/src/c#/GeneralUpdate.Extension/IExtensionLifecycle.cs new file mode 100644 index 00000000..f4792abb --- /dev/null +++ b/src/c#/GeneralUpdate.Extension/IExtensionLifecycle.cs @@ -0,0 +1,43 @@ +using System; +using System.Threading.Tasks; + +namespace MyApp.Extensions +{ + /// + /// Defines the lifecycle methods for an extension. + /// + public interface IExtensionLifecycle + { + /// + /// Called when the extension is installed. + /// + /// A task that represents the asynchronous operation. + Task OnInstallAsync(); + + /// + /// Called when the extension is activated or enabled. + /// + /// A task that represents the asynchronous operation. + Task OnActivateAsync(); + + /// + /// Called when the extension is deactivated or disabled. + /// + /// A task that represents the asynchronous operation. + Task OnDeactivateAsync(); + + /// + /// Called when the extension is uninstalled. + /// + /// A task that represents the asynchronous operation. + Task OnUninstallAsync(); + + /// + /// Called when the extension is updated to a new version. + /// + /// The previous version. + /// The new version. + /// A task that represents the asynchronous operation. + Task OnUpdateAsync(string oldVersion, string newVersion); + } +} diff --git a/src/c#/GeneralUpdate.Extension/IExtensionLoader.cs b/src/c#/GeneralUpdate.Extension/IExtensionLoader.cs new file mode 100644 index 00000000..5bbd8711 --- /dev/null +++ b/src/c#/GeneralUpdate.Extension/IExtensionLoader.cs @@ -0,0 +1,53 @@ +using System; +using System.Threading.Tasks; + +namespace MyApp.Extensions +{ + /// + /// Provides methods for loading and managing extensions. + /// + public interface IExtensionLoader + { + /// + /// Loads an extension from the specified path. + /// + /// The path to the extension package. + /// A task that represents the asynchronous operation, containing the loaded extension manifest. + Task LoadAsync(string extensionPath); + + /// + /// Unloads a previously loaded extension. + /// + /// The unique identifier of the extension to unload. + /// A task that represents the asynchronous operation, indicating success or failure. + Task UnloadAsync(string extensionId); + + /// + /// Activates a loaded extension. + /// + /// The unique identifier of the extension to activate. + /// A task that represents the asynchronous operation, indicating success or failure. + Task ActivateAsync(string extensionId); + + /// + /// Deactivates an active extension. + /// + /// The unique identifier of the extension to deactivate. + /// A task that represents the asynchronous operation, indicating success or failure. + Task DeactivateAsync(string extensionId); + + /// + /// Gets a value indicating whether an extension is currently loaded. + /// + /// The unique identifier of the extension. + /// True if the extension is loaded; otherwise, false. + bool IsLoaded(string extensionId); + + /// + /// Gets a value indicating whether an extension is currently active. + /// + /// The unique identifier of the extension. + /// True if the extension is active; otherwise, false. + bool IsActive(string extensionId); + } +} diff --git a/src/c#/GeneralUpdate.Extension/IExtensionManager.cs b/src/c#/GeneralUpdate.Extension/IExtensionManager.cs new file mode 100644 index 00000000..158b63a0 --- /dev/null +++ b/src/c#/GeneralUpdate.Extension/IExtensionManager.cs @@ -0,0 +1,69 @@ +using System; +using System.Collections.Generic; +using System.Threading.Tasks; + +namespace MyApp.Extensions +{ + /// + /// Provides methods for managing extensions, including installation, uninstallation, and updates. + /// + public interface IExtensionManager + { + /// + /// Installs an extension from a package file. + /// + /// The path to the extension package. + /// A task that represents the asynchronous operation, indicating success or failure. + Task InstallAsync(string packagePath); + + /// + /// Uninstalls an extension. + /// + /// The unique identifier of the extension to uninstall. + /// A task that represents the asynchronous operation, indicating success or failure. + Task UninstallAsync(string extensionId); + + /// + /// Enables an installed extension. + /// + /// The unique identifier of the extension to enable. + /// A task that represents the asynchronous operation, indicating success or failure. + Task EnableAsync(string extensionId); + + /// + /// Disables an enabled extension. + /// + /// The unique identifier of the extension to disable. + /// A task that represents the asynchronous operation, indicating success or failure. + Task DisableAsync(string extensionId); + + /// + /// Updates an extension to a new version. + /// + /// The unique identifier of the extension to update. + /// The target version to update to. + /// A task that represents the asynchronous operation, indicating success or failure. + Task UpdateAsync(string extensionId, string targetVersion); + + /// + /// Rolls back an extension to a previous version. + /// + /// The unique identifier of the extension to roll back. + /// The target version to roll back to. + /// A task that represents the asynchronous operation, indicating success or failure. + Task RollbackAsync(string extensionId, string targetVersion); + + /// + /// Gets all installed extensions. + /// + /// A task that represents the asynchronous operation, containing a list of installed extension manifests. + Task> GetInstalledExtensionsAsync(); + + /// + /// Gets the current state of an extension. + /// + /// The unique identifier of the extension. + /// A task that represents the asynchronous operation, containing the extension state. + Task GetExtensionStateAsync(string extensionId); + } +} diff --git a/src/c#/GeneralUpdate.Extension/IExtensionProcessHost.cs b/src/c#/GeneralUpdate.Extension/IExtensionProcessHost.cs new file mode 100644 index 00000000..28df0a17 --- /dev/null +++ b/src/c#/GeneralUpdate.Extension/IExtensionProcessHost.cs @@ -0,0 +1,100 @@ +using System; +using System.Threading.Tasks; + +namespace MyApp.Extensions +{ + /// + /// Provides process isolation support for extensions. + /// + public interface IExtensionProcessHost + { + /// + /// Starts an extension in an isolated process. + /// + /// The unique identifier of the extension. + /// The startup information for the process. + /// A task that represents the asynchronous operation, containing the process ID. + Task StartProcessAsync(string extensionId, ProcessStartupInfo startupInfo); + + /// + /// Stops an extension process. + /// + /// The process ID to stop. + /// A task that represents the asynchronous operation, indicating success or failure. + Task StopProcessAsync(int processId); + + /// + /// Gets a value indicating whether a process is currently running. + /// + /// The process ID to check. + /// True if the process is running; otherwise, false. + bool IsProcessRunning(int processId); + + /// + /// Sends a message to an extension process. + /// + /// The process ID to send the message to. + /// The message to send. + /// A task that represents the asynchronous operation, containing the response. + Task SendMessageAsync(int processId, object message); + + /// + /// Monitors the health of an extension process. + /// + /// The process ID to monitor. + /// A task that represents the asynchronous operation, containing the health status. + Task MonitorHealthAsync(int processId); + } + + /// + /// Represents startup information for an extension process. + /// + public class ProcessStartupInfo + { + /// + /// Gets or sets the executable path. + /// + public string ExecutablePath { get; set; } + + /// + /// Gets or sets the command-line arguments. + /// + public string Arguments { get; set; } + + /// + /// Gets or sets the working directory. + /// + public string WorkingDirectory { get; set; } + + /// + /// Gets or sets a value indicating whether to redirect standard input/output. + /// + public bool RedirectStandardIO { get; set; } + } + + /// + /// Represents the health status of a process. + /// + public class ProcessHealthStatus + { + /// + /// Gets or sets a value indicating whether the process is healthy. + /// + public bool IsHealthy { get; set; } + + /// + /// Gets or sets the CPU usage percentage. + /// + public double CpuUsagePercent { get; set; } + + /// + /// Gets or sets the memory usage in MB. + /// + public long MemoryUsageMB { get; set; } + + /// + /// Gets or sets any error messages. + /// + public string ErrorMessage { get; set; } + } +} diff --git a/src/c#/GeneralUpdate.Extension/IExtensionRepository.cs b/src/c#/GeneralUpdate.Extension/IExtensionRepository.cs new file mode 100644 index 00000000..7de9587a --- /dev/null +++ b/src/c#/GeneralUpdate.Extension/IExtensionRepository.cs @@ -0,0 +1,64 @@ +using System; +using System.Collections.Generic; +using System.Threading.Tasks; + +namespace MyApp.Extensions +{ + /// + /// Provides methods for querying and retrieving extensions from a repository. + /// + public interface IExtensionRepository + { + /// + /// Searches for extensions matching the specified query. + /// + /// The search query. + /// A task that represents the asynchronous operation, containing a list of matching extension manifests. + Task> SearchAsync(string query); + + /// + /// Gets the details of a specific extension by its identifier. + /// + /// The unique identifier of the extension. + /// A task that represents the asynchronous operation, containing the extension manifest. + Task GetExtensionAsync(string extensionId); + + /// + /// Gets the list of available versions for a specific extension. + /// + /// The unique identifier of the extension. + /// A task that represents the asynchronous operation, containing a list of version strings. + Task> GetVersionsAsync(string extensionId); + + /// + /// Downloads an extension package. + /// + /// The unique identifier of the extension. + /// The version to download. + /// The path where the package should be saved. + /// Optional progress reporter. + /// A task that represents the asynchronous operation, indicating success or failure. + Task DownloadPackageAsync(string extensionId, string version, string destinationPath, IProgress progress = null); + + /// + /// Validates the metadata of an extension package. + /// + /// The path to the extension package. + /// A task that represents the asynchronous operation, indicating whether the metadata is valid. + Task ValidateMetadataAsync(string packagePath); + + /// + /// Gets all extensions in a specific category. + /// + /// The category to filter by. + /// A task that represents the asynchronous operation, containing a list of extension manifests. + Task> GetExtensionsByCategoryAsync(string category); + + /// + /// Gets the most popular extensions. + /// + /// The number of extensions to retrieve. + /// A task that represents the asynchronous operation, containing a list of extension manifests. + Task> GetPopularExtensionsAsync(int count); + } +} diff --git a/src/c#/GeneralUpdate.Extension/IExtensionSandbox.cs b/src/c#/GeneralUpdate.Extension/IExtensionSandbox.cs new file mode 100644 index 00000000..db6c7c0d --- /dev/null +++ b/src/c#/GeneralUpdate.Extension/IExtensionSandbox.cs @@ -0,0 +1,94 @@ +using System; +using System.Threading.Tasks; + +namespace MyApp.Extensions +{ + /// + /// Provides permission and resource isolation for extensions. + /// + public interface IExtensionSandbox + { + /// + /// Checks whether an extension has a specific permission. + /// + /// The unique identifier of the extension. + /// The permission to check. + /// True if the extension has the permission; otherwise, false. + bool HasPermission(string extensionId, ExtensionPermission permission); + + /// + /// Requests a permission for an extension. + /// + /// The unique identifier of the extension. + /// The permission to request. + /// A task that represents the asynchronous operation, indicating whether the permission was granted. + Task RequestPermissionAsync(string extensionId, ExtensionPermission permission); + + /// + /// Revokes a permission from an extension. + /// + /// The unique identifier of the extension. + /// The permission to revoke. + /// A task that represents the asynchronous operation, indicating success or failure. + Task RevokePermissionAsync(string extensionId, ExtensionPermission permission); + + /// + /// Checks whether an extension can access a file or directory. + /// + /// The unique identifier of the extension. + /// The file or directory path. + /// The type of access (e.g., "Read", "Write"). + /// True if access is allowed; otherwise, false. + bool CanAccessFile(string extensionId, string path, string accessType); + + /// + /// Checks whether an extension can access a network resource. + /// + /// The unique identifier of the extension. + /// The URL to access. + /// True if access is allowed; otherwise, false. + bool CanAccessNetwork(string extensionId, string url); + + /// + /// Checks whether an extension can access a system resource. + /// + /// The unique identifier of the extension. + /// The type of system resource (e.g., "Registry", "Process"). + /// True if access is allowed; otherwise, false. + bool CanAccessSystem(string extensionId, string resourceType); + + /// + /// Sets resource limits for an extension. + /// + /// The unique identifier of the extension. + /// The resource limits to apply. + /// A task that represents the asynchronous operation, indicating success or failure. + Task SetResourceLimitsAsync(string extensionId, ResourceLimits limits); + } + + /// + /// Represents resource limits for an extension. + /// + public class ResourceLimits + { + /// + /// Gets or sets the maximum memory usage in MB. + /// + public int MaxMemoryMB { get; set; } + + /// + /// Gets or sets the maximum CPU usage percentage. + /// + public double MaxCpuPercent { get; set; } + + /// + /// Gets or sets the maximum disk space usage in MB. + /// + public long MaxDiskSpaceMB { get; set; } + + /// + /// Gets or sets the maximum number of network connections. + /// + public int MaxNetworkConnections { get; set; } + } +} diff --git a/src/c#/GeneralUpdate.Extension/ISemVersionComparer.cs b/src/c#/GeneralUpdate.Extension/ISemVersionComparer.cs new file mode 100644 index 00000000..2e7374b2 --- /dev/null +++ b/src/c#/GeneralUpdate.Extension/ISemVersionComparer.cs @@ -0,0 +1,32 @@ +namespace MyApp.Extensions +{ + /// + /// Provides methods for comparing semantic versions. + /// + public interface ISemVersionComparer + { + /// + /// Compares two semantic versions. + /// + /// The first version to compare. + /// The second version to compare. + /// A value indicating the relative order of the versions. + int Compare(SemVersion version1, SemVersion version2); + + /// + /// Determines whether a version satisfies a version range. + /// + /// The version to check. + /// The version range to check against. + /// True if the version satisfies the range; otherwise, false. + bool Satisfies(SemVersion version, string versionRange); + + /// + /// Determines whether two versions are equal. + /// + /// The first version to compare. + /// The second version to compare. + /// True if the versions are equal; otherwise, false. + bool Equals(SemVersion version1, SemVersion version2); + } +} diff --git a/src/c#/GeneralUpdate.Extension/ISignatureValidator.cs b/src/c#/GeneralUpdate.Extension/ISignatureValidator.cs new file mode 100644 index 00000000..187d2236 --- /dev/null +++ b/src/c#/GeneralUpdate.Extension/ISignatureValidator.cs @@ -0,0 +1,71 @@ +using System.Threading.Tasks; + +namespace MyApp.Extensions +{ + /// + /// Provides methods for validating package signatures and integrity. + /// + public interface ISignatureValidator + { + /// + /// Validates the signature of a package. + /// + /// The path to the package. + /// A task that represents the asynchronous operation, containing the validation result. + Task ValidateSignatureAsync(string packagePath); + + /// + /// Verifies the integrity of a package using its hash. + /// + /// The path to the package. + /// The expected hash value. + /// The hash algorithm to use (e.g., "SHA256"). + /// A task that represents the asynchronous operation, indicating whether the integrity is valid. + Task VerifyIntegrityAsync(string packagePath, string expectedHash, string hashAlgorithm); + + /// + /// Validates the certificate chain for a package signature. + /// + /// The path to the package. + /// A task that represents the asynchronous operation, indicating whether the certificate chain is valid. + Task ValidateCertificateChainAsync(string packagePath); + + /// + /// Checks whether a package is signed by a trusted authority. + /// + /// The path to the package. + /// A task that represents the asynchronous operation, indicating whether the package is trusted. + Task IsTrustedAsync(string packagePath); + } + + /// + /// Represents the result of a signature validation. + /// + public class SignatureValidationResult + { + /// + /// Gets or sets a value indicating whether the signature is valid. + /// + public bool IsValid { get; set; } + + /// + /// Gets or sets a value indicating whether the signature is trusted. + /// + public bool IsTrusted { get; set; } + + /// + /// Gets or sets the signer's identity. + /// + public string SignerIdentity { get; set; } + + /// + /// Gets or sets the certificate thumbprint. + /// + public string CertificateThumbprint { get; set; } + + /// + /// Gets or sets any error messages. + /// + public string ErrorMessage { get; set; } + } +} diff --git a/src/c#/GeneralUpdate.Extension/Packaging/PackageFileEntry.cs b/src/c#/GeneralUpdate.Extension/Packaging/PackageFileEntry.cs new file mode 100644 index 00000000..7aae54e5 --- /dev/null +++ b/src/c#/GeneralUpdate.Extension/Packaging/PackageFileEntry.cs @@ -0,0 +1,50 @@ +using System; + +namespace MyApp.Extensions.Packaging +{ + /// + /// Represents a file entry within a plugin package, providing indexing and metadata for package files. + /// + public class PackageFileEntry + { + /// + /// Gets or sets the relative path of the file within the package. + /// + public string Path { get; set; } + + /// + /// Gets or sets the size of the file in bytes. + /// + public long Size { get; set; } + + /// + /// Gets or sets the hash of the file for integrity verification. + /// + public string Hash { get; set; } + + /// + /// Gets or sets the hash algorithm used (e.g., "SHA256", "MD5"). + /// + public string HashAlgorithm { get; set; } + + /// + /// Gets or sets the MIME type of the file. + /// + public string ContentType { get; set; } + + /// + /// Gets or sets a value indicating whether the file is compressed. + /// + public bool IsCompressed { get; set; } + + /// + /// Gets or sets the original size of the file before compression. + /// + public long OriginalSize { get; set; } + + /// + /// Gets or sets the last modified timestamp of the file. + /// + public DateTime LastModified { get; set; } + } +} diff --git a/src/c#/GeneralUpdate.Extension/Packaging/PackageFormatVersion.cs b/src/c#/GeneralUpdate.Extension/Packaging/PackageFormatVersion.cs new file mode 100644 index 00000000..2231770c --- /dev/null +++ b/src/c#/GeneralUpdate.Extension/Packaging/PackageFormatVersion.cs @@ -0,0 +1,77 @@ +using System; +using MyApp.Extensions; + +namespace MyApp.Extensions.Packaging +{ + /// + /// Represents the standardized format version of a plugin package. + /// + public class PackageFormatVersion + { + /// + /// Gets or sets the major version number. + /// + public int Major { get; set; } + + /// + /// Gets or sets the minor version number. + /// + public int Minor { get; set; } + + /// + /// Gets or sets the patch version number. + /// + public int Patch { get; set; } + + /// + /// Gets or sets the pre-release label (e.g., "alpha", "beta", "rc"). + /// + public string PreRelease { get; set; } + + /// + /// Gets or sets the build metadata. + /// + public string BuildMetadata { get; set; } + + /// + /// Returns the string representation of the package format version. + /// + /// A string in the format "major.minor.patch[-prerelease][+buildmetadata]". + public override string ToString() + { + var version = $"{Major}.{Minor}.{Patch}"; + if (!string.IsNullOrEmpty(PreRelease)) + { + version += $"-{PreRelease}"; + } + if (!string.IsNullOrEmpty(BuildMetadata)) + { + version += $"+{BuildMetadata}"; + } + return version; + } + + /// + /// Parses a version string into a PackageFormatVersion instance. + /// + /// The version string to parse. + /// A PackageFormatVersion instance. + public static PackageFormatVersion Parse(string versionString) + { + if (string.IsNullOrWhiteSpace(versionString)) + throw new ArgumentException("Version string cannot be null or empty.", nameof(versionString)); + + // Reuse SemVersion parsing logic + var semVer = SemVersion.Parse(versionString); + + return new PackageFormatVersion + { + Major = semVer.Major, + Minor = semVer.Minor, + Patch = semVer.Patch, + PreRelease = semVer.PreRelease, + BuildMetadata = semVer.BuildMetadata + }; + } + } +} diff --git a/src/c#/GeneralUpdate.Extension/Packaging/PackageManifest.cs b/src/c#/GeneralUpdate.Extension/Packaging/PackageManifest.cs new file mode 100644 index 00000000..8b6a7ca5 --- /dev/null +++ b/src/c#/GeneralUpdate.Extension/Packaging/PackageManifest.cs @@ -0,0 +1,82 @@ +using System; +using System.Collections.Generic; + +namespace MyApp.Extensions.Packaging +{ + /// + /// Represents the manifest of a plugin package, containing metadata, runtime configuration, + /// dependencies, permissions, and signatures. + /// + public class PackageManifest + { + /// + /// Gets or sets the unique identifier of the package. + /// + public string Id { get; set; } + + /// + /// Gets or sets the display name of the package. + /// + public string Name { get; set; } + + /// + /// Gets or sets the version of the package. + /// + public string Version { get; set; } + + /// + /// Gets or sets the author of the package. + /// + public string Author { get; set; } + + /// + /// Gets or sets the description of the package. + /// + public string Description { get; set; } + + /// + /// Gets or sets the entry point of the package. + /// + public string Entrypoint { get; set; } + + /// + /// Gets or sets the runtime type required by the package. + /// + public string Runtime { get; set; } + + /// + /// Gets or sets the engine version compatibility information. + /// + public string Engine { get; set; } + + /// + /// Gets or sets the compatibility information for the package. + /// + public string Compatibility { get; set; } + + /// + /// Gets or sets the list of dependencies required by the package. + /// + public List Dependencies { get; set; } + + /// + /// Gets or sets the list of permissions required by the package. + /// + public List Permissions { get; set; } + + /// + /// Gets or sets the extension points defined by the package. + /// + public Dictionary ExtensionPoints { get; set; } + + /// + /// Gets or sets the signature information for package verification. + /// + public string Signature { get; set; } + + /// + /// Gets or sets the format version of the package. + /// + public string FormatVersion { get; set; } + } +} diff --git a/src/c#/GeneralUpdate.Extension/Packaging/PackageSignature.cs b/src/c#/GeneralUpdate.Extension/Packaging/PackageSignature.cs new file mode 100644 index 00000000..67cd61a6 --- /dev/null +++ b/src/c#/GeneralUpdate.Extension/Packaging/PackageSignature.cs @@ -0,0 +1,55 @@ +using System; + +namespace MyApp.Extensions.Packaging +{ + /// + /// Represents the signature, certificate, and hash information for a plugin package. + /// + public class PackageSignature + { + /// + /// Gets or sets the digital signature of the package. + /// + public string Signature { get; set; } + + /// + /// Gets or sets the algorithm used for the signature (e.g., "RSA", "ECDSA"). + /// + public string SignatureAlgorithm { get; set; } + + /// + /// Gets or sets the certificate used to sign the package. + /// + public string Certificate { get; set; } + + /// + /// Gets or sets the certificate chain for validation. + /// + public string[] CertificateChain { get; set; } + + /// + /// Gets or sets the thumbprint of the certificate. + /// + public string CertificateThumbprint { get; set; } + + /// + /// Gets or sets the hash of the entire package. + /// + public string PackageHash { get; set; } + + /// + /// Gets or sets the hash algorithm used for the package hash (e.g., "SHA256", "SHA512"). + /// + public string HashAlgorithm { get; set; } + + /// + /// Gets or sets the timestamp when the package was signed. + /// + public DateTime SignedTimestamp { get; set; } + + /// + /// Gets or sets the URL of the timestamp authority. + /// + public string TimestampAuthorityUrl { get; set; } + } +} diff --git a/src/c#/GeneralUpdate.Extension/README.md b/src/c#/GeneralUpdate.Extension/README.md new file mode 100644 index 00000000..d676d0e0 --- /dev/null +++ b/src/c#/GeneralUpdate.Extension/README.md @@ -0,0 +1,183 @@ +# GeneralUpdate Extension Framework + +A comprehensive plugin manager framework for WPF/Avalonia desktop applications, inspired by VS Code Extension Mechanism and VSIX design. + +## Overview + +This framework provides a complete plugin/extension system with support for: +- Multi-language runtime support (C#, Lua, Python, Node.js, native executables) +- Advanced update mechanisms (full, incremental, differential, rollback) +- Enterprise-level features (offline installation, repository mirrors, security policies) +- Strong security and sandboxing + +## Architecture + +The framework is organized into the following namespaces: + +### MyApp.Extensions (Core) +Core models and interfaces for extension management. + +**Key Types:** +- `ExtensionManifest` - Extension metadata and configuration +- `ExtensionState` - Extension lifecycle states (Installed, Enabled, Disabled, etc.) +- `SemVersion` - Semantic versioning support +- `ExtensionDependency` - Dependency management +- `ExtensionPermission` - Permission system + +**Key Interfaces:** +- `IExtensionManager` - Install, uninstall, enable, disable, update extensions +- `IExtensionLoader` - Load and activate extensions +- `IExtensionRepository` - Query and download extensions +- `IExtensionLifecycle` - Extension lifecycle hooks +- `IExtensionDependencyResolver` - Dependency resolution +- `ISignatureValidator` - Package signature validation +- `IExtensionProcessHost` - Process isolation +- `IExtensionSandbox` - Permission and resource isolation + +### MyApp.Extensions.Packaging +VSIX-inspired package specification and structure. + +**Key Types:** +- `PackageManifest` - Package metadata with runtime config, dependencies, permissions +- `PackageFileEntry` - File indexing within packages +- `PackageSignature` - Digital signatures and certificates +- `PackageFormatVersion` - Standardized package versioning + +**Package Structure Convention:** +``` +extension-package/ +├── manifest.json +├── assets/ +├── runtime/ +├── patches/ +└── signature/ +``` + +### MyApp.Extensions.Updates +Advanced update mechanisms for extensions. + +**Key Types:** +- `UpdateChannel` - Stable/PreRelease/Dev channels +- `UpdateMetadata` - Available updates and compatibility +- `UpdatePackageInfo` - Full/Delta/Diff package details +- `DeltaPatchInfo` - Incremental update information +- `RollbackInfo` - Rollback configuration + +**Key Interfaces:** +- `IUpdateService` - Check for updates, download, install, rollback +- `IDeltaUpdateService` - Generate and apply delta patches + +### MyApp.Extensions.Runtime +Multi-language runtime support for extensions. + +**Key Types:** +- `RuntimeType` - Enum: DotNet, Lua, Python, Node, Exe, Custom +- `RuntimeEnvironmentInfo` - Runtime configuration and environment + +**Key Interfaces:** +- `IRuntimeHost` - Start/stop runtime, invoke methods, health checks +- `IRuntimeResolver` - Resolve runtime hosts by type + +### MyApp.Extensions.Security +Enterprise-level security and offline support. + +**Key Types:** +- `EnterprisePolicy` - Allowed/blocked sources, certificate requirements +- `OfflinePackageIndex` - Local package management + +**Key Interfaces:** +- `IRepositoryMirror` - Enterprise repository mirror management +- `IOfflineInstaller` - Offline installation support + +### MyApp.Extensions.SDK +Developer-facing APIs for extension authors. + +**Key Types:** +- `ExtensionActivationEvent` - Trigger-based activation (onCommand, onLanguage, onView, etc.) + +**Key Interfaces:** +- `IExtensionContext` - Extension runtime context and storage +- `IExtensionAPI` - Host application services and commands + +## Usage Example + +```csharp +// Install an extension +var manager = GetService(); +await manager.InstallAsync("path/to/extension.vsix"); + +// Check for updates +var updateService = GetService(); +var updateMetadata = await updateService.CheckForUpdatesAsync("extension-id"); + +// Load and activate extension +var loader = GetService(); +var manifest = await loader.LoadAsync("path/to/extension"); +await loader.ActivateAsync(manifest.Id); + +// Runtime support +var resolver = GetService(); +var pythonHost = resolver.Resolve(RuntimeType.Python); +await pythonHost.StartAsync(runtimeEnv); +var result = await pythonHost.InvokeAsync("main", args); +``` + +## Extension Development + +Extensions can be developed in any supported language and must include a `manifest.json`: + +```json +{ + "id": "my-extension", + "name": "My Extension", + "version": "1.0.0", + "author": "Author Name", + "runtime": "python", + "entrypoint": "main.py", + "dependencies": [], + "permissions": [ + { + "type": "FileSystem", + "scope": "read", + "reason": "Read configuration files" + } + ], + "activationEvents": [ + "onCommand:myextension.command", + "onStartup" + ] +} +``` + +## Extension Activation Events + +Similar to VS Code, extensions can be activated based on: +- `onStartup` - On application startup +- `onCommand:commandId` - When a command is invoked +- `onLanguage:languageId` - When a language file is opened +- `onView:viewId` - When a view is opened +- `onFileSystem:pattern` - When a file pattern is accessed + +## Security + +The framework includes comprehensive security features: +- Digital signature validation +- Certificate chain verification +- Permission-based sandbox +- Enterprise policy enforcement +- Trusted source management + +## Enterprise Features + +- **Repository Mirrors**: Set up internal mirrors for extension repositories +- **Offline Installation**: Deploy extensions without internet access +- **Policy Enforcement**: Control which extensions can be installed +- **Approval Workflows**: Require approval before installation + +## License + +This framework is part of the GeneralUpdate project and follows the same licensing terms. + +## Contributing + +Contributions are welcome! Please refer to the main GeneralUpdate repository for contribution guidelines. diff --git a/src/c#/GeneralUpdate.Extension/Runtime/IRuntimeHost.cs b/src/c#/GeneralUpdate.Extension/Runtime/IRuntimeHost.cs new file mode 100644 index 00000000..fc5b5518 --- /dev/null +++ b/src/c#/GeneralUpdate.Extension/Runtime/IRuntimeHost.cs @@ -0,0 +1,48 @@ +using System; +using System.Threading.Tasks; + +namespace MyApp.Extensions.Runtime +{ + /// + /// Provides an interface for hosting and managing extension runtimes. + /// + public interface IRuntimeHost + { + /// + /// Gets the type of runtime this host supports. + /// + RuntimeType RuntimeType { get; } + + /// + /// Starts the runtime host. + /// + /// The runtime environment information. + /// A task that represents the asynchronous operation, indicating success or failure. + Task StartAsync(RuntimeEnvironmentInfo environmentInfo); + + /// + /// Stops the runtime host. + /// + /// A task that represents the asynchronous operation, indicating success or failure. + Task StopAsync(); + + /// + /// Invokes a method or function in the runtime. + /// + /// The name of the method to invoke. + /// The parameters to pass to the method. + /// A task that represents the asynchronous operation, containing the result of the invocation. + Task InvokeAsync(string methodName, params object[] parameters); + + /// + /// Performs a health check on the runtime host. + /// + /// A task that represents the asynchronous operation, indicating whether the runtime is healthy. + Task HealthCheckAsync(); + + /// + /// Gets a value indicating whether the runtime host is currently running. + /// + bool IsRunning { get; } + } +} diff --git a/src/c#/GeneralUpdate.Extension/Runtime/IRuntimeResolver.cs b/src/c#/GeneralUpdate.Extension/Runtime/IRuntimeResolver.cs new file mode 100644 index 00000000..95f7c164 --- /dev/null +++ b/src/c#/GeneralUpdate.Extension/Runtime/IRuntimeResolver.cs @@ -0,0 +1,35 @@ +namespace MyApp.Extensions.Runtime +{ + /// + /// Resolves and provides runtime hosts based on runtime type. + /// + public interface IRuntimeResolver + { + /// + /// Resolves a runtime host for the specified runtime type. + /// + /// The type of runtime to resolve. + /// The runtime host for the specified type, or null if not found. + IRuntimeHost Resolve(RuntimeType runtimeType); + + /// + /// Registers a runtime host for a specific runtime type. + /// + /// The type of runtime. + /// The runtime host to register. + void Register(RuntimeType runtimeType, IRuntimeHost host); + + /// + /// Determines whether a runtime host is available for the specified runtime type. + /// + /// The type of runtime to check. + /// True if a runtime host is available; otherwise, false. + bool IsAvailable(RuntimeType runtimeType); + + /// + /// Gets all registered runtime types. + /// + /// An array of registered runtime types. + RuntimeType[] GetRegisteredRuntimeTypes(); + } +} diff --git a/src/c#/GeneralUpdate.Extension/Runtime/RuntimeEnvironmentInfo.cs b/src/c#/GeneralUpdate.Extension/Runtime/RuntimeEnvironmentInfo.cs new file mode 100644 index 00000000..a3b7dae5 --- /dev/null +++ b/src/c#/GeneralUpdate.Extension/Runtime/RuntimeEnvironmentInfo.cs @@ -0,0 +1,50 @@ +using System.Collections.Generic; + +namespace MyApp.Extensions.Runtime +{ + /// + /// Represents information about a runtime environment. + /// + public class RuntimeEnvironmentInfo + { + /// + /// Gets or sets the type of runtime. + /// + public RuntimeType RuntimeType { get; set; } + + /// + /// Gets or sets the version of the runtime. + /// + public string Version { get; set; } + + /// + /// Gets or sets the installation path of the runtime. + /// + public string InstallationPath { get; set; } + + /// + /// Gets or sets the startup parameters or arguments for the runtime. + /// + public Dictionary StartupParameters { get; set; } + + /// + /// Gets or sets the environment variables for the runtime. + /// + public Dictionary EnvironmentVariables { get; set; } + + /// + /// Gets or sets the working directory for the runtime. + /// + public string WorkingDirectory { get; set; } + + /// + /// Gets or sets the maximum memory allocation for the runtime in MB. + /// + public int MaxMemoryMB { get; set; } + + /// + /// Gets or sets the timeout for runtime operations in seconds. + /// + public int TimeoutSeconds { get; set; } + } +} diff --git a/src/c#/GeneralUpdate.Extension/Runtime/RuntimeHosts.cs b/src/c#/GeneralUpdate.Extension/Runtime/RuntimeHosts.cs new file mode 100644 index 00000000..56dc2c5c --- /dev/null +++ b/src/c#/GeneralUpdate.Extension/Runtime/RuntimeHosts.cs @@ -0,0 +1,322 @@ +using System; +using System.Diagnostics; +using System.Threading.Tasks; + +namespace MyApp.Extensions.Runtime +{ + /// + /// Base implementation for runtime hosts providing common functionality. + /// + public abstract class RuntimeHostBase : IRuntimeHost + { + /// + /// Gets the type of runtime this host supports. + /// + public abstract RuntimeType RuntimeType { get; } + + /// + /// Gets a value indicating whether the runtime host is currently running. + /// + public bool IsRunning { get; protected set; } + + /// + /// Starts the runtime host. + /// + /// The runtime environment information. + /// A task that represents the asynchronous operation, indicating success or failure. + public virtual async Task StartAsync(RuntimeEnvironmentInfo environmentInfo) + { + try + { + if (IsRunning) + return true; + + await OnStartAsync(environmentInfo); + IsRunning = true; + return true; + } + catch + { + return false; + } + } + + /// + /// Stops the runtime host. + /// + /// A task that represents the asynchronous operation, indicating success or failure. + public virtual async Task StopAsync() + { + try + { + if (!IsRunning) + return true; + + await OnStopAsync(); + IsRunning = false; + return true; + } + catch + { + return false; + } + } + + /// + /// Invokes a method or function in the runtime. + /// + /// The name of the method to invoke. + /// The parameters to pass to the method. + /// A task that represents the asynchronous operation, containing the result of the invocation. + public abstract Task InvokeAsync(string methodName, params object[] parameters); + + /// + /// Performs a health check on the runtime host. + /// + /// A task that represents the asynchronous operation, indicating whether the runtime is healthy. + public virtual Task HealthCheckAsync() + { + return Task.FromResult(IsRunning); + } + + /// + /// Called when the runtime host is being started. + /// + /// The runtime environment information. + /// A task that represents the asynchronous operation. + protected abstract Task OnStartAsync(RuntimeEnvironmentInfo environmentInfo); + + /// + /// Called when the runtime host is being stopped. + /// + /// A task that represents the asynchronous operation. + protected abstract Task OnStopAsync(); + } + + /// + /// Runtime host for .NET extensions. + /// + public class DotNetRuntimeHost : RuntimeHostBase + { + /// + /// Gets the type of runtime this host supports. + /// + public override RuntimeType RuntimeType => RuntimeType.DotNet; + + /// + /// Invokes a method or function in the runtime. + /// + /// The name of the method to invoke. + /// The parameters to pass to the method. + /// A task that represents the asynchronous operation, containing the result of the invocation. + public override Task InvokeAsync(string methodName, params object[] parameters) + { + // In real implementation, would use reflection or dynamic loading + return Task.FromResult(null); + } + + /// + /// Called when the runtime host is being started. + /// + /// The runtime environment information. + /// A task that represents the asynchronous operation. + protected override Task OnStartAsync(RuntimeEnvironmentInfo environmentInfo) + { + // .NET runtime is always available + return Task.CompletedTask; + } + + /// + /// Called when the runtime host is being stopped. + /// + /// A task that represents the asynchronous operation. + protected override Task OnStopAsync() + { + return Task.CompletedTask; + } + } + + /// + /// Runtime host for Python extensions. + /// + public class PythonRuntimeHost : RuntimeHostBase + { + private Process _pythonProcess; + + /// + /// Gets the type of runtime this host supports. + /// + public override RuntimeType RuntimeType => RuntimeType.Python; + + /// + /// Invokes a method or function in the runtime. + /// + /// The name of the method to invoke. + /// The parameters to pass to the method. + /// A task that represents the asynchronous operation, containing the result of the invocation. + public override Task InvokeAsync(string methodName, params object[] parameters) + { + // In real implementation, would communicate with Python process + return Task.FromResult(null); + } + + /// + /// Called when the runtime host is being started. + /// + /// The runtime environment information. + /// A task that represents the asynchronous operation. + protected override Task OnStartAsync(RuntimeEnvironmentInfo environmentInfo) + { + // In real implementation, would start Python interpreter + return Task.CompletedTask; + } + + /// + /// Called when the runtime host is being stopped. + /// + /// A task that represents the asynchronous operation. + protected override Task OnStopAsync() + { + _pythonProcess?.Kill(); + _pythonProcess?.Dispose(); + return Task.CompletedTask; + } + } + + /// + /// Runtime host for Node.js extensions. + /// + public class NodeRuntimeHost : RuntimeHostBase + { + private Process _nodeProcess; + + /// + /// Gets the type of runtime this host supports. + /// + public override RuntimeType RuntimeType => RuntimeType.Node; + + /// + /// Invokes a method or function in the runtime. + /// + /// The name of the method to invoke. + /// The parameters to pass to the method. + /// A task that represents the asynchronous operation, containing the result of the invocation. + public override Task InvokeAsync(string methodName, params object[] parameters) + { + // In real implementation, would communicate with Node process + return Task.FromResult(null); + } + + /// + /// Called when the runtime host is being started. + /// + /// The runtime environment information. + /// A task that represents the asynchronous operation. + protected override Task OnStartAsync(RuntimeEnvironmentInfo environmentInfo) + { + // In real implementation, would start Node.js runtime + return Task.CompletedTask; + } + + /// + /// Called when the runtime host is being stopped. + /// + /// A task that represents the asynchronous operation. + protected override Task OnStopAsync() + { + _nodeProcess?.Kill(); + _nodeProcess?.Dispose(); + return Task.CompletedTask; + } + } + + /// + /// Runtime host for Lua extensions. + /// + public class LuaRuntimeHost : RuntimeHostBase + { + /// + /// Gets the type of runtime this host supports. + /// + public override RuntimeType RuntimeType => RuntimeType.Lua; + + /// + /// Invokes a method or function in the runtime. + /// + /// The name of the method to invoke. + /// The parameters to pass to the method. + /// A task that represents the asynchronous operation, containing the result of the invocation. + public override Task InvokeAsync(string methodName, params object[] parameters) + { + // In real implementation, would use Lua interpreter library + return Task.FromResult(null); + } + + /// + /// Called when the runtime host is being started. + /// + /// The runtime environment information. + /// A task that represents the asynchronous operation. + protected override Task OnStartAsync(RuntimeEnvironmentInfo environmentInfo) + { + // In real implementation, would initialize Lua interpreter + return Task.CompletedTask; + } + + /// + /// Called when the runtime host is being stopped. + /// + /// A task that represents the asynchronous operation. + protected override Task OnStopAsync() + { + return Task.CompletedTask; + } + } + + /// + /// Runtime host for native executable extensions. + /// + public class ExeRuntimeHost : RuntimeHostBase + { + private Process _exeProcess; + + /// + /// Gets the type of runtime this host supports. + /// + public override RuntimeType RuntimeType => RuntimeType.Exe; + + /// + /// Invokes a method or function in the runtime. + /// + /// The name of the method to invoke. + /// The parameters to pass to the method. + /// A task that represents the asynchronous operation, containing the result of the invocation. + public override Task InvokeAsync(string methodName, params object[] parameters) + { + // In real implementation, would use IPC to communicate with exe + return Task.FromResult(null); + } + + /// + /// Called when the runtime host is being started. + /// + /// The runtime environment information. + /// A task that represents the asynchronous operation. + protected override Task OnStartAsync(RuntimeEnvironmentInfo environmentInfo) + { + // In real implementation, would launch the executable + return Task.CompletedTask; + } + + /// + /// Called when the runtime host is being stopped. + /// + /// A task that represents the asynchronous operation. + protected override Task OnStopAsync() + { + _exeProcess?.Kill(); + _exeProcess?.Dispose(); + return Task.CompletedTask; + } + } +} diff --git a/src/c#/GeneralUpdate.Extension/Runtime/RuntimeResolver.cs b/src/c#/GeneralUpdate.Extension/Runtime/RuntimeResolver.cs new file mode 100644 index 00000000..a481b9cd --- /dev/null +++ b/src/c#/GeneralUpdate.Extension/Runtime/RuntimeResolver.cs @@ -0,0 +1,97 @@ +using System; +using System.Collections.Generic; + +namespace MyApp.Extensions.Runtime +{ + /// + /// Default implementation of IRuntimeResolver for resolving runtime hosts. + /// + public class RuntimeResolver : IRuntimeResolver + { + private readonly Dictionary _hosts; + + /// + /// Initializes a new instance of the class. + /// + public RuntimeResolver() + { + _hosts = new Dictionary(); + } + + /// + /// Resolves a runtime host for the specified runtime type. + /// + /// The type of runtime to resolve. + /// The runtime host for the specified type, or null if not found. + public IRuntimeHost Resolve(RuntimeType runtimeType) + { + if (_hosts.TryGetValue(runtimeType, out var host)) + { + return host; + } + + // Create default hosts if not registered + switch (runtimeType) + { + case RuntimeType.DotNet: + host = new DotNetRuntimeHost(); + Register(runtimeType, host); + return host; + + case RuntimeType.Python: + host = new PythonRuntimeHost(); + Register(runtimeType, host); + return host; + + case RuntimeType.Node: + host = new NodeRuntimeHost(); + Register(runtimeType, host); + return host; + + case RuntimeType.Lua: + host = new LuaRuntimeHost(); + Register(runtimeType, host); + return host; + + case RuntimeType.Exe: + host = new ExeRuntimeHost(); + Register(runtimeType, host); + return host; + + default: + return null; + } + } + + /// + /// Registers a runtime host for a specific runtime type. + /// + /// The type of runtime. + /// The runtime host to register. + public void Register(RuntimeType runtimeType, IRuntimeHost host) + { + _hosts[runtimeType] = host ?? throw new ArgumentNullException(nameof(host)); + } + + /// + /// Determines whether a runtime host is available for the specified runtime type. + /// + /// The type of runtime to check. + /// True if a runtime host is available; otherwise, false. + public bool IsAvailable(RuntimeType runtimeType) + { + return _hosts.ContainsKey(runtimeType); + } + + /// + /// Gets all registered runtime types. + /// + /// An array of registered runtime types. + public RuntimeType[] GetRegisteredRuntimeTypes() + { + var types = new RuntimeType[_hosts.Count]; + _hosts.Keys.CopyTo(types, 0); + return types; + } + } +} diff --git a/src/c#/GeneralUpdate.Extension/Runtime/RuntimeType.cs b/src/c#/GeneralUpdate.Extension/Runtime/RuntimeType.cs new file mode 100644 index 00000000..3da53eb0 --- /dev/null +++ b/src/c#/GeneralUpdate.Extension/Runtime/RuntimeType.cs @@ -0,0 +1,38 @@ +namespace MyApp.Extensions.Runtime +{ + /// + /// Represents the type of runtime used by an extension. + /// + public enum RuntimeType + { + /// + /// .NET runtime for C#/F#/VB.NET extensions. + /// + DotNet, + + /// + /// Lua scripting runtime. + /// + Lua, + + /// + /// Python scripting runtime. + /// + Python, + + /// + /// Node.js runtime for JavaScript/TypeScript extensions. + /// + Node, + + /// + /// Native executable runtime for compiled binaries. + /// + Exe, + + /// + /// Custom or user-defined runtime. + /// + Custom + } +} diff --git a/src/c#/GeneralUpdate.Extension/SDK/ExtensionAPI.cs b/src/c#/GeneralUpdate.Extension/SDK/ExtensionAPI.cs new file mode 100644 index 00000000..9c2f5961 --- /dev/null +++ b/src/c#/GeneralUpdate.Extension/SDK/ExtensionAPI.cs @@ -0,0 +1,219 @@ +using System; +using System.Collections.Generic; +using System.Threading.Tasks; + +namespace MyApp.Extensions.SDK +{ + /// + /// Default implementation of IExtensionAPI for host application integration. + /// + public class ExtensionAPI : IExtensionAPI + { + private readonly Dictionary> _commands; + private readonly Dictionary>> _eventSubscribers; + + /// + /// Initializes a new instance of the class. + /// + /// The name of the host application. + /// The version of the host application. + public ExtensionAPI(string hostName, string hostVersion) + { + HostName = hostName ?? "Unknown Host"; + HostVersion = hostVersion ?? "1.0.0"; + _commands = new Dictionary>(); + _eventSubscribers = new Dictionary>>(); + } + + /// + /// Gets the version of the host application. + /// + public string HostVersion { get; } + + /// + /// Gets the name of the host application. + /// + public string HostName { get; } + + /// + /// Executes a command in the host application. + /// + /// The unique identifier of the command to execute. + /// Optional parameters for the command. + /// A task that represents the asynchronous operation, containing the result of the command. + public Task ExecuteCommandAsync(string commandId, params object[] parameters) + { + try + { + if (string.IsNullOrWhiteSpace(commandId)) + throw new ArgumentException("Command ID cannot be null or empty", nameof(commandId)); + + if (_commands.TryGetValue(commandId, out var handler)) + { + var result = handler(parameters); + return Task.FromResult(result); + } + + throw new InvalidOperationException($"Command not found: {commandId}"); + } + catch (Exception ex) + { + throw new InvalidOperationException($"Failed to execute command: {commandId}", ex); + } + } + + /// + /// Registers a command that can be invoked by the host application or other extensions. + /// + /// The unique identifier for the command. + /// The handler to execute when the command is invoked. + public void RegisterCommand(string commandId, Func handler) + { + if (string.IsNullOrWhiteSpace(commandId)) + throw new ArgumentException("Command ID cannot be null or empty", nameof(commandId)); + + if (handler == null) + throw new ArgumentNullException(nameof(handler)); + + _commands[commandId] = handler; + } + + /// + /// Unregisters a previously registered command. + /// + /// The unique identifier of the command to unregister. + public void UnregisterCommand(string commandId) + { + if (!string.IsNullOrWhiteSpace(commandId)) + { + _commands.Remove(commandId); + } + } + + /// + /// Shows a notification to the user. + /// + /// The message to display. + /// The severity level (e.g., "Info", "Warning", "Error"). + public void ShowNotification(string message, string severity) + { + // In real implementation, would show UI notification + Console.WriteLine($"[{severity}] {message}"); + } + + /// + /// Shows a message dialog to the user. + /// + /// The title of the dialog. + /// The message to display. + /// The buttons to display (e.g., "OK", "YesNo"). + /// A task that represents the asynchronous operation, containing the user's choice. + public Task ShowDialogAsync(string title, string message, string buttons) + { + // In real implementation, would show UI dialog and await user response + Console.WriteLine($"Dialog: {title} - {message} [{buttons}]"); + return Task.FromResult("OK"); + } + + /// + /// Gets a service from the host application. + /// + /// The type of service to retrieve. + /// The service instance, or null if not available. + public T GetService() where T : class + { + // In real implementation, would use dependency injection container + return null; + } + + /// + /// Subscribes to an event in the host application. + /// + /// The name of the event to subscribe to. + /// The handler to invoke when the event occurs. + public void SubscribeToEvent(string eventName, Action handler) + { + if (string.IsNullOrWhiteSpace(eventName)) + throw new ArgumentException("Event name cannot be null or empty", nameof(eventName)); + + if (handler == null) + throw new ArgumentNullException(nameof(handler)); + + if (!_eventSubscribers.ContainsKey(eventName)) + { + _eventSubscribers[eventName] = new List>(); + } + + _eventSubscribers[eventName].Add(handler); + } + + /// + /// Unsubscribes from an event in the host application. + /// + /// The name of the event to unsubscribe from. + /// The handler to remove. + public void UnsubscribeFromEvent(string eventName, Action handler) + { + if (string.IsNullOrWhiteSpace(eventName) || handler == null) + return; + + if (_eventSubscribers.TryGetValue(eventName, out var handlers)) + { + handlers.Remove(handler); + } + } + + /// + /// Reads a resource from the host application. + /// + /// The path to the resource. + /// A task that represents the asynchronous operation, containing the resource content. + public Task ReadResourceAsync(string resourcePath) + { + // In real implementation, would read from embedded resources + return Task.FromResult(new byte[0]); + } + + /// + /// Opens a file or URL in the host application or default system handler. + /// + /// The file path or URL to open. + /// A task that represents the asynchronous operation, indicating success or failure. + public Task OpenAsync(string path) + { + try + { + // In real implementation, would use Process.Start or similar + Console.WriteLine($"Opening: {path}"); + return Task.FromResult(true); + } + catch + { + return Task.FromResult(false); + } + } + + /// + /// Triggers an event for all subscribers. + /// + /// The name of the event to trigger. + /// The event data. + public void TriggerEvent(string eventName, object eventData) + { + if (_eventSubscribers.TryGetValue(eventName, out var handlers)) + { + foreach (var handler in handlers) + { + try + { + handler(eventData); + } + catch + { + // Swallow individual handler exceptions + } + } + } + } + } +} diff --git a/src/c#/GeneralUpdate.Extension/SDK/ExtensionActivationEvent.cs b/src/c#/GeneralUpdate.Extension/SDK/ExtensionActivationEvent.cs new file mode 100644 index 00000000..f1dcd85e --- /dev/null +++ b/src/c#/GeneralUpdate.Extension/SDK/ExtensionActivationEvent.cs @@ -0,0 +1,119 @@ +using System; +using System.Collections.Generic; + +namespace MyApp.Extensions.SDK +{ + /// + /// Represents an event that triggers the activation of an extension, similar to VS Code activation events. + /// + public class ExtensionActivationEvent + { + /// + /// Gets or sets the unique identifier of the activation event. + /// + public string EventId { get; set; } + + /// + /// Gets or sets the type of activation event (e.g., "onCommand", "onLanguage", "onView", "onStartup", "onFileSystem"). + /// + public string EventType { get; set; } + + /// + /// Gets or sets the pattern or condition that triggers the event. + /// + public string Pattern { get; set; } + + /// + /// Gets or sets additional parameters for the activation event. + /// + public Dictionary Parameters { get; set; } + + /// + /// Gets or sets the timestamp when the event occurred. + /// + public DateTime Timestamp { get; set; } + + /// + /// Gets or sets the source of the event. + /// + public string Source { get; set; } + + /// + /// Predefined activation event: Extension is activated on application startup. + /// + /// An activation event for startup. + public static ExtensionActivationEvent OnStartup() + { + return new ExtensionActivationEvent + { + EventId = Guid.NewGuid().ToString(), + EventType = "onStartup", + Timestamp = DateTime.UtcNow + }; + } + + /// + /// Predefined activation event: Extension is activated when a specific command is invoked. + /// + /// The command identifier. + /// An activation event for a command. + public static ExtensionActivationEvent OnCommand(string commandId) + { + return new ExtensionActivationEvent + { + EventId = Guid.NewGuid().ToString(), + EventType = "onCommand", + Pattern = commandId, + Timestamp = DateTime.UtcNow + }; + } + + /// + /// Predefined activation event: Extension is activated when a file with a specific language is opened. + /// + /// The language identifier (e.g., "csharp", "javascript"). + /// An activation event for a language. + public static ExtensionActivationEvent OnLanguage(string languageId) + { + return new ExtensionActivationEvent + { + EventId = Guid.NewGuid().ToString(), + EventType = "onLanguage", + Pattern = languageId, + Timestamp = DateTime.UtcNow + }; + } + + /// + /// Predefined activation event: Extension is activated when a specific view is opened. + /// + /// The view identifier. + /// An activation event for a view. + public static ExtensionActivationEvent OnView(string viewId) + { + return new ExtensionActivationEvent + { + EventId = Guid.NewGuid().ToString(), + EventType = "onView", + Pattern = viewId, + Timestamp = DateTime.UtcNow + }; + } + + /// + /// Predefined activation event: Extension is activated when a file system matching a pattern is accessed. + /// + /// The file pattern (e.g., "*.txt", "**/*.cs"). + /// An activation event for a file system pattern. + public static ExtensionActivationEvent OnFileSystem(string filePattern) + { + return new ExtensionActivationEvent + { + EventId = Guid.NewGuid().ToString(), + EventType = "onFileSystem", + Pattern = filePattern, + Timestamp = DateTime.UtcNow + }; + } + } +} diff --git a/src/c#/GeneralUpdate.Extension/SDK/ExtensionContext.cs b/src/c#/GeneralUpdate.Extension/SDK/ExtensionContext.cs new file mode 100644 index 00000000..3e72433c --- /dev/null +++ b/src/c#/GeneralUpdate.Extension/SDK/ExtensionContext.cs @@ -0,0 +1,177 @@ +using System; +using System.Collections.Generic; +using System.IO; + +namespace MyApp.Extensions.SDK +{ + /// + /// Default implementation of IExtensionContext providing runtime context for extensions. + /// + public class ExtensionContext : IExtensionContext + { + private readonly Dictionary _state; + + /// + /// Initializes a new instance of the class. + /// + /// The unique identifier of the extension. + /// The version of the extension. + /// The path to the extension's installation directory. + /// The host application API. + public ExtensionContext(string extensionId, string version, string extensionPath, IExtensionAPI api) + { + ExtensionId = extensionId ?? throw new ArgumentNullException(nameof(extensionId)); + Version = version ?? throw new ArgumentNullException(nameof(version)); + ExtensionPath = extensionPath ?? throw new ArgumentNullException(nameof(extensionPath)); + API = api ?? throw new ArgumentNullException(nameof(api)); + + _state = new Dictionary(); + Configuration = new Dictionary(); + Environment = new Dictionary(); + + // Set up storage paths + var appData = System.Environment.GetFolderPath(System.Environment.SpecialFolder.ApplicationData); + StoragePath = Path.Combine(appData, "Extensions", extensionId); + GlobalStoragePath = Path.Combine(appData, "Extensions", "Global"); + + // Create storage directories + if (!Directory.Exists(StoragePath)) + Directory.CreateDirectory(StoragePath); + if (!Directory.Exists(GlobalStoragePath)) + Directory.CreateDirectory(GlobalStoragePath); + + // Create logger + Logger = new ExtensionLogger(extensionId); + } + + /// + /// Gets the unique identifier of the extension. + /// + public string ExtensionId { get; } + + /// + /// Gets the version of the extension. + /// + public string Version { get; } + + /// + /// Gets the path to the extension's installation directory. + /// + public string ExtensionPath { get; } + + /// + /// Gets the path to the extension's storage directory for user data. + /// + public string StoragePath { get; } + + /// + /// Gets the path to the extension's global storage directory. + /// + public string GlobalStoragePath { get; } + + /// + /// Gets the configuration settings for the extension. + /// + public Dictionary Configuration { get; } + + /// + /// Gets the environment variables available to the extension. + /// + public Dictionary Environment { get; } + + /// + /// Gets the host application API. + /// + public IExtensionAPI API { get; } + + /// + /// Gets the logger for the extension. + /// + public IExtensionLogger Logger { get; } + + /// + /// Saves a value to the extension's storage. + /// + /// The key to store the value under. + /// The value to store. + public void SaveState(string key, object value) + { + if (string.IsNullOrWhiteSpace(key)) + throw new ArgumentException("Key cannot be null or empty", nameof(key)); + + _state[key] = value; + } + + /// + /// Retrieves a value from the extension's storage. + /// + /// The type of value to retrieve. + /// The key to retrieve the value for. + /// The stored value, or default if not found. + public T GetState(string key) + { + if (string.IsNullOrWhiteSpace(key)) + return default; + + if (_state.TryGetValue(key, out var value) && value is T typedValue) + { + return typedValue; + } + + return default; + } + } + + /// + /// Default implementation of IExtensionLogger for logging. + /// + public class ExtensionLogger : IExtensionLogger + { + private readonly string _extensionId; + + /// + /// Initializes a new instance of the class. + /// + /// The extension identifier for logging context. + public ExtensionLogger(string extensionId) + { + _extensionId = extensionId; + } + + /// + /// Logs an informational message. + /// + /// The message to log. + public void Info(string message) + { + Console.WriteLine($"[{DateTime.UtcNow:yyyy-MM-dd HH:mm:ss}] [INFO] [{_extensionId}] {message}"); + } + + /// + /// Logs a warning message. + /// + /// The message to log. + public void Warn(string message) + { + Console.WriteLine($"[{DateTime.UtcNow:yyyy-MM-dd HH:mm:ss}] [WARN] [{_extensionId}] {message}"); + } + + /// + /// Logs an error message. + /// + /// The message to log. + public void Error(string message) + { + Console.WriteLine($"[{DateTime.UtcNow:yyyy-MM-dd HH:mm:ss}] [ERROR] [{_extensionId}] {message}"); + } + + /// + /// Logs a debug message. + /// + /// The message to log. + public void Debug(string message) + { + Console.WriteLine($"[{DateTime.UtcNow:yyyy-MM-dd HH:mm:ss}] [DEBUG] [{_extensionId}] {message}"); + } + } +} diff --git a/src/c#/GeneralUpdate.Extension/SDK/IExtensionAPI.cs b/src/c#/GeneralUpdate.Extension/SDK/IExtensionAPI.cs new file mode 100644 index 00000000..db5217d5 --- /dev/null +++ b/src/c#/GeneralUpdate.Extension/SDK/IExtensionAPI.cs @@ -0,0 +1,93 @@ +using System; +using System.Threading.Tasks; + +namespace MyApp.Extensions.SDK +{ + /// + /// Provides APIs for extensions to interact with the host application. + /// + public interface IExtensionAPI + { + /// + /// Gets the version of the host application. + /// + string HostVersion { get; } + + /// + /// Gets the name of the host application. + /// + string HostName { get; } + + /// + /// Executes a command in the host application. + /// + /// The unique identifier of the command to execute. + /// Optional parameters for the command. + /// A task that represents the asynchronous operation, containing the result of the command. + Task ExecuteCommandAsync(string commandId, params object[] parameters); + + /// + /// Registers a command that can be invoked by the host application or other extensions. + /// + /// The unique identifier for the command. + /// The handler to execute when the command is invoked. + void RegisterCommand(string commandId, Func handler); + + /// + /// Unregisters a previously registered command. + /// + /// The unique identifier of the command to unregister. + void UnregisterCommand(string commandId); + + /// + /// Shows a notification to the user. + /// + /// The message to display. + /// The severity level (e.g., "Info", "Warning", "Error"). + void ShowNotification(string message, string severity); + + /// + /// Shows a message dialog to the user. + /// + /// The title of the dialog. + /// The message to display. + /// The buttons to display (e.g., "OK", "YesNo"). + /// A task that represents the asynchronous operation, containing the user's choice. + Task ShowDialogAsync(string title, string message, string buttons); + + /// + /// Gets a service from the host application. + /// + /// The type of service to retrieve. + /// The service instance, or null if not available. + T GetService() where T : class; + + /// + /// Subscribes to an event in the host application. + /// + /// The name of the event to subscribe to. + /// The handler to invoke when the event occurs. + void SubscribeToEvent(string eventName, Action handler); + + /// + /// Unsubscribes from an event in the host application. + /// + /// The name of the event to unsubscribe from. + /// The handler to remove. + void UnsubscribeFromEvent(string eventName, Action handler); + + /// + /// Reads a resource from the host application. + /// + /// The path to the resource. + /// A task that represents the asynchronous operation, containing the resource content. + Task ReadResourceAsync(string resourcePath); + + /// + /// Opens a file or URL in the host application or default system handler. + /// + /// The file path or URL to open. + /// A task that represents the asynchronous operation, indicating success or failure. + Task OpenAsync(string path); + } +} diff --git a/src/c#/GeneralUpdate.Extension/SDK/IExtensionContext.cs b/src/c#/GeneralUpdate.Extension/SDK/IExtensionContext.cs new file mode 100644 index 00000000..f82d917d --- /dev/null +++ b/src/c#/GeneralUpdate.Extension/SDK/IExtensionContext.cs @@ -0,0 +1,101 @@ +using System; +using System.Collections.Generic; + +namespace MyApp.Extensions.SDK +{ + /// + /// Provides runtime context for an extension, including access to extension-specific resources and configuration. + /// + public interface IExtensionContext + { + /// + /// Gets the unique identifier of the extension. + /// + string ExtensionId { get; } + + /// + /// Gets the version of the extension. + /// + string Version { get; } + + /// + /// Gets the path to the extension's installation directory. + /// + string ExtensionPath { get; } + + /// + /// Gets the path to the extension's storage directory for user data. + /// + string StoragePath { get; } + + /// + /// Gets the path to the extension's global storage directory. + /// + string GlobalStoragePath { get; } + + /// + /// Gets the configuration settings for the extension. + /// + Dictionary Configuration { get; } + + /// + /// Gets the environment variables available to the extension. + /// + Dictionary Environment { get; } + + /// + /// Gets the host application API. + /// + IExtensionAPI API { get; } + + /// + /// Gets the logger for the extension. + /// + IExtensionLogger Logger { get; } + + /// + /// Saves a value to the extension's storage. + /// + /// The key to store the value under. + /// The value to store. + void SaveState(string key, object value); + + /// + /// Retrieves a value from the extension's storage. + /// + /// The type of value to retrieve. + /// The key to retrieve the value for. + /// The stored value, or default if not found. + T GetState(string key); + } + + /// + /// Provides logging functionality for extensions. + /// + public interface IExtensionLogger + { + /// + /// Logs an informational message. + /// + /// The message to log. + void Info(string message); + + /// + /// Logs a warning message. + /// + /// The message to log. + void Warn(string message); + + /// + /// Logs an error message. + /// + /// The message to log. + void Error(string message); + + /// + /// Logs a debug message. + /// + /// The message to log. + void Debug(string message); + } +} diff --git a/src/c#/GeneralUpdate.Extension/Security/EnterprisePolicy.cs b/src/c#/GeneralUpdate.Extension/Security/EnterprisePolicy.cs new file mode 100644 index 00000000..d7551677 --- /dev/null +++ b/src/c#/GeneralUpdate.Extension/Security/EnterprisePolicy.cs @@ -0,0 +1,65 @@ +using System.Collections.Generic; + +namespace MyApp.Extensions.Security +{ + /// + /// Represents enterprise-level policy rules for plugin sources and installations. + /// + public class EnterprisePolicy + { + /// + /// Gets or sets the list of allowed repository sources. + /// + public List AllowedSources { get; set; } + + /// + /// Gets or sets the list of blocked repository sources. + /// + public List BlockedSources { get; set; } + + /// + /// Gets or sets a value indicating whether only signed extensions are allowed. + /// + public bool RequireSignedExtensions { get; set; } + + /// + /// Gets or sets the list of trusted certificate thumbprints. + /// + public List TrustedCertificates { get; set; } + + /// + /// Gets or sets a value indicating whether extensions must be approved before installation. + /// + public bool RequireApproval { get; set; } + + /// + /// Gets or sets the list of explicitly allowed extensions. + /// + public List AllowedExtensions { get; set; } + + /// + /// Gets or sets the list of explicitly blocked extensions. + /// + public List BlockedExtensions { get; set; } + + /// + /// Gets or sets the maximum allowed permissions for extensions. + /// + public List MaximumAllowedPermissions { get; set; } + + /// + /// Gets or sets a value indicating whether automatic updates are allowed. + /// + public bool AllowAutomaticUpdates { get; set; } + + /// + /// Gets or sets a value indicating whether users can install extensions. + /// + public bool AllowUserInstallation { get; set; } + + /// + /// Gets or sets the policy enforcement mode (e.g., "Strict", "Lenient", "Audit"). + /// + public string EnforcementMode { get; set; } + } +} diff --git a/src/c#/GeneralUpdate.Extension/Security/IOfflineInstaller.cs b/src/c#/GeneralUpdate.Extension/Security/IOfflineInstaller.cs new file mode 100644 index 00000000..c003155e --- /dev/null +++ b/src/c#/GeneralUpdate.Extension/Security/IOfflineInstaller.cs @@ -0,0 +1,86 @@ +using System; +using System.Collections.Generic; +using System.Threading.Tasks; + +namespace MyApp.Extensions.Security +{ + /// + /// Provides methods for offline plugin installation. + /// + public interface IOfflineInstaller + { + /// + /// Installs an extension from an offline package. + /// + /// The path to the offline package. + /// A task that represents the asynchronous operation, indicating success or failure. + Task InstallFromOfflinePackageAsync(string packagePath); + + /// + /// Creates an offline installation package for an extension. + /// + /// The unique identifier of the extension. + /// The version to package. + /// The path where the offline package should be created. + /// A task that represents the asynchronous operation, indicating success or failure. + Task CreateOfflinePackageAsync(string extensionId, string version, string outputPath); + + /// + /// Validates an offline installation package. + /// + /// The path to the offline package. + /// A task that represents the asynchronous operation, indicating whether the package is valid. + Task ValidateOfflinePackageAsync(string packagePath); + + /// + /// Imports an offline package index. + /// + /// The path to the index file. + /// A task that represents the asynchronous operation, indicating success or failure. + Task ImportOfflineIndexAsync(string indexPath); + + /// + /// Exports the current offline package index. + /// + /// The path where the index file should be saved. + /// A task that represents the asynchronous operation, indicating success or failure. + Task ExportOfflineIndexAsync(string outputPath); + + /// + /// Gets all extensions available in the offline index. + /// + /// A task that represents the asynchronous operation, containing a list of available extensions. + Task> GetAvailableOfflinePackagesAsync(); + } + + /// + /// Represents information about an offline package. + /// + public class OfflinePackageInfo + { + /// + /// Gets or sets the unique identifier of the extension. + /// + public string ExtensionId { get; set; } + + /// + /// Gets or sets the version of the extension. + /// + public string Version { get; set; } + + /// + /// Gets or sets the path to the offline package. + /// + public string PackagePath { get; set; } + + /// + /// Gets or sets the size of the package in bytes. + /// + public long Size { get; set; } + + /// + /// Gets or sets the package creation timestamp. + /// + public DateTime CreatedDate { get; set; } + } +} diff --git a/src/c#/GeneralUpdate.Extension/Security/IRepositoryMirror.cs b/src/c#/GeneralUpdate.Extension/Security/IRepositoryMirror.cs new file mode 100644 index 00000000..4e05a1cc --- /dev/null +++ b/src/c#/GeneralUpdate.Extension/Security/IRepositoryMirror.cs @@ -0,0 +1,101 @@ +using System; +using System.Collections.Generic; +using System.Threading.Tasks; + +namespace MyApp.Extensions.Security +{ + /// + /// Provides methods for managing enterprise repository mirrors. + /// + public interface IRepositoryMirror + { + /// + /// Registers a new repository mirror. + /// + /// The URL of the mirror repository. + /// The priority of the mirror (higher values are preferred). + /// A task that represents the asynchronous operation, indicating success or failure. + Task RegisterMirrorAsync(string mirrorUrl, int priority); + + /// + /// Removes a repository mirror. + /// + /// The URL of the mirror repository to remove. + /// A task that represents the asynchronous operation, indicating success or failure. + Task RemoveMirrorAsync(string mirrorUrl); + + /// + /// Gets all registered repository mirrors. + /// + /// A task that represents the asynchronous operation, containing a list of mirror URLs. + Task> GetMirrorsAsync(); + + /// + /// Synchronizes a mirror with the primary repository. + /// + /// The URL of the mirror repository to synchronize. + /// A task that represents the asynchronous operation, indicating success or failure. + Task SyncMirrorAsync(string mirrorUrl); + + /// + /// Tests the connectivity and health of a repository mirror. + /// + /// The URL of the mirror repository to test. + /// A task that represents the asynchronous operation, containing the health status. + Task TestMirrorHealthAsync(string mirrorUrl); + + /// + /// Sets the primary repository mirror to use. + /// + /// The URL of the mirror repository to set as primary. + /// A task that represents the asynchronous operation, indicating success or failure. + Task SetPrimaryMirrorAsync(string mirrorUrl); + } + + /// + /// Represents information about a repository mirror. + /// + public class RepositoryMirrorInfo + { + /// + /// Gets or sets the URL of the mirror. + /// + public string Url { get; set; } + + /// + /// Gets or sets the priority of the mirror. + /// + public int Priority { get; set; } + + /// + /// Gets or sets a value indicating whether the mirror is currently active. + /// + public bool IsActive { get; set; } + + /// + /// Gets or sets the last synchronization timestamp. + /// + public DateTime LastSync { get; set; } + } + + /// + /// Represents the health status of a repository mirror. + /// + public class MirrorHealthStatus + { + /// + /// Gets or sets a value indicating whether the mirror is healthy. + /// + public bool IsHealthy { get; set; } + + /// + /// Gets or sets the response time in milliseconds. + /// + public int ResponseTimeMs { get; set; } + + /// + /// Gets or sets any error messages. + /// + public string ErrorMessage { get; set; } + } +} diff --git a/src/c#/GeneralUpdate.Extension/Security/OfflinePackageIndex.cs b/src/c#/GeneralUpdate.Extension/Security/OfflinePackageIndex.cs new file mode 100644 index 00000000..f1d95c51 --- /dev/null +++ b/src/c#/GeneralUpdate.Extension/Security/OfflinePackageIndex.cs @@ -0,0 +1,97 @@ +using System; +using System.Collections.Generic; + +namespace MyApp.Extensions.Security +{ + /// + /// Represents an index for managing local offline packages. + /// + public class OfflinePackageIndex + { + /// + /// Gets or sets the version of the index format. + /// + public string IndexVersion { get; set; } + + /// + /// Gets or sets the timestamp when the index was created. + /// + public DateTime CreatedDate { get; set; } + + /// + /// Gets or sets the timestamp when the index was last updated. + /// + public DateTime LastUpdated { get; set; } + + /// + /// Gets or sets the list of packages in the index. + /// + public List Packages { get; set; } + + /// + /// Gets or sets metadata about the index. + /// + public Dictionary Metadata { get; set; } + } + + /// + /// Represents an entry in an offline package index. + /// + public class OfflinePackageEntry + { + /// + /// Gets or sets the unique identifier of the extension. + /// + public string ExtensionId { get; set; } + + /// + /// Gets or sets the display name of the extension. + /// + public string Name { get; set; } + + /// + /// Gets or sets the version of the extension. + /// + public string Version { get; set; } + + /// + /// Gets or sets the author of the extension. + /// + public string Author { get; set; } + + /// + /// Gets or sets the description of the extension. + /// + public string Description { get; set; } + + /// + /// Gets or sets the relative path to the package file. + /// + public string PackagePath { get; set; } + + /// + /// Gets or sets the size of the package in bytes. + /// + public long Size { get; set; } + + /// + /// Gets or sets the hash of the package. + /// + public string Hash { get; set; } + + /// + /// Gets or sets the hash algorithm used. + /// + public string HashAlgorithm { get; set; } + + /// + /// Gets or sets the list of dependencies. + /// + public List Dependencies { get; set; } + + /// + /// Gets or sets the timestamp when the package was added to the index. + /// + public DateTime AddedDate { get; set; } + } +} diff --git a/src/c#/GeneralUpdate.Extension/SemVersion.cs b/src/c#/GeneralUpdate.Extension/SemVersion.cs new file mode 100644 index 00000000..b907c855 --- /dev/null +++ b/src/c#/GeneralUpdate.Extension/SemVersion.cs @@ -0,0 +1,263 @@ +using System; + +namespace MyApp.Extensions +{ + /// + /// Represents a semantic version following the SemVer 2.0 specification. + /// + public struct SemVersion : IComparable, IEquatable + { + /// + /// Gets or sets the major version number. + /// + public int Major { get; set; } + + /// + /// Gets or sets the minor version number. + /// + public int Minor { get; set; } + + /// + /// Gets or sets the patch version number. + /// + public int Patch { get; set; } + + /// + /// Gets or sets the pre-release label (e.g., "alpha", "beta", "rc.1"). + /// + public string PreRelease { get; set; } + + /// + /// Gets or sets the build metadata. + /// + public string BuildMetadata { get; set; } + + /// + /// Initializes a new instance of the struct. + /// + /// The major version number. + /// The minor version number. + /// The patch version number. + /// The pre-release label. + /// The build metadata. + public SemVersion(int major, int minor, int patch, string preRelease = null, string buildMetadata = null) + { + Major = major; + Minor = minor; + Patch = patch; + PreRelease = preRelease; + BuildMetadata = buildMetadata; + } + + /// + /// Returns the string representation of the semantic version. + /// + /// A string in the format "major.minor.patch[-prerelease][+buildmetadata]". + public override string ToString() + { + var version = $"{Major}.{Minor}.{Patch}"; + if (!string.IsNullOrEmpty(PreRelease)) + { + version += $"-{PreRelease}"; + } + if (!string.IsNullOrEmpty(BuildMetadata)) + { + version += $"+{BuildMetadata}"; + } + return version; + } + + /// + /// Parses a version string into a SemVersion instance. + /// + /// The version string to parse. + /// A SemVersion instance. + public static SemVersion Parse(string versionString) + { + if (string.IsNullOrWhiteSpace(versionString)) + throw new ArgumentException("Version string cannot be null or empty.", nameof(versionString)); + + if (!TryParse(versionString, out var version)) + throw new FormatException($"Invalid version string: {versionString}"); + + return version; + } + + /// + /// Tries to parse a version string into a SemVersion instance. + /// + /// The version string to parse. + /// The parsed SemVersion instance. + /// True if parsing was successful; otherwise, false. + public static bool TryParse(string versionString, out SemVersion version) + { + version = default; + + if (string.IsNullOrWhiteSpace(versionString)) + return false; + + // Split on + for build metadata + var parts = versionString.Split('+'); + var versionPart = parts[0]; + var buildMetadata = parts.Length > 1 ? parts[1] : null; + + // Split on - for pre-release + var coreParts = versionPart.Split(new[] { '-' }, 2); + var coreVersion = coreParts[0]; + var preRelease = coreParts.Length > 1 ? coreParts[1] : null; + + // Parse major.minor.patch + var versionNumbers = coreVersion.Split('.'); + if (versionNumbers.Length != 3) + return false; + + if (!int.TryParse(versionNumbers[0], out var major) || major < 0) + return false; + if (!int.TryParse(versionNumbers[1], out var minor) || minor < 0) + return false; + if (!int.TryParse(versionNumbers[2], out var patch) || patch < 0) + return false; + + version = new SemVersion(major, minor, patch, preRelease, buildMetadata); + return true; + } + + /// + /// Compares this instance to another SemVersion instance. + /// + /// The other SemVersion instance to compare. + /// A value indicating the relative order of the instances. + public int CompareTo(SemVersion other) + { + // Compare major.minor.patch + var majorCompare = Major.CompareTo(other.Major); + if (majorCompare != 0) return majorCompare; + + var minorCompare = Minor.CompareTo(other.Minor); + if (minorCompare != 0) return minorCompare; + + var patchCompare = Patch.CompareTo(other.Patch); + if (patchCompare != 0) return patchCompare; + + // Pre-release versions have lower precedence than normal versions + if (string.IsNullOrEmpty(PreRelease) && !string.IsNullOrEmpty(other.PreRelease)) + return 1; + if (!string.IsNullOrEmpty(PreRelease) && string.IsNullOrEmpty(other.PreRelease)) + return -1; + if (!string.IsNullOrEmpty(PreRelease) && !string.IsNullOrEmpty(other.PreRelease)) + { + return string.CompareOrdinal(PreRelease, other.PreRelease); + } + + // Build metadata does not affect version precedence + return 0; + } + + /// + /// Determines whether this instance is equal to another SemVersion instance. + /// + /// The other SemVersion instance to compare. + /// True if the instances are equal; otherwise, false. + public bool Equals(SemVersion other) + { + return Major == other.Major && + Minor == other.Minor && + Patch == other.Patch && + PreRelease == other.PreRelease; + // Note: Build metadata is not included in equality per SemVer 2.0 spec + } + + /// + /// Determines whether this instance is equal to another object. + /// + /// The object to compare. + /// True if the objects are equal; otherwise, false. + public override bool Equals(object obj) + { + return obj is SemVersion other && Equals(other); + } + + /// + /// Returns the hash code for this instance. + /// + /// A 32-bit signed integer hash code. + public override int GetHashCode() + { + unchecked + { + var hash = 17; + hash = hash * 31 + Major.GetHashCode(); + hash = hash * 31 + Minor.GetHashCode(); + hash = hash * 31 + Patch.GetHashCode(); + hash = hash * 31 + (PreRelease?.GetHashCode() ?? 0); + return hash; + } + } + + /// + /// Determines whether two SemVersion instances are equal. + /// + /// The first instance. + /// The second instance. + /// True if the instances are equal; otherwise, false. + public static bool operator ==(SemVersion left, SemVersion right) + { + return left.Equals(right); + } + + /// + /// Determines whether two SemVersion instances are not equal. + /// + /// The first instance. + /// The second instance. + /// True if the instances are not equal; otherwise, false. + public static bool operator !=(SemVersion left, SemVersion right) + { + return !left.Equals(right); + } + + /// + /// Determines whether one SemVersion instance is less than another. + /// + /// The first instance. + /// The second instance. + /// True if the first instance is less than the second; otherwise, false. + public static bool operator <(SemVersion left, SemVersion right) + { + return left.CompareTo(right) < 0; + } + + /// + /// Determines whether one SemVersion instance is greater than another. + /// + /// The first instance. + /// The second instance. + /// True if the first instance is greater than the second; otherwise, false. + public static bool operator >(SemVersion left, SemVersion right) + { + return left.CompareTo(right) > 0; + } + + /// + /// Determines whether one SemVersion instance is less than or equal to another. + /// + /// The first instance. + /// The second instance. + /// True if the first instance is less than or equal to the second; otherwise, false. + public static bool operator <=(SemVersion left, SemVersion right) + { + return left.CompareTo(right) <= 0; + } + + /// + /// Determines whether one SemVersion instance is greater than or equal to another. + /// + /// The first instance. + /// The second instance. + /// True if the first instance is greater than or equal to the second; otherwise, false. + public static bool operator >=(SemVersion left, SemVersion right) + { + return left.CompareTo(right) >= 0; + } + } +} diff --git a/src/c#/GeneralUpdate.Extension/SemVersionComparer.cs b/src/c#/GeneralUpdate.Extension/SemVersionComparer.cs new file mode 100644 index 00000000..2d2b2c69 --- /dev/null +++ b/src/c#/GeneralUpdate.Extension/SemVersionComparer.cs @@ -0,0 +1,124 @@ +using System; +using System.Text.RegularExpressions; + +namespace MyApp.Extensions +{ + /// + /// Default implementation of ISemVersionComparer for comparing semantic versions. + /// + public class SemVersionComparer : ISemVersionComparer + { + /// + /// Compares two semantic versions. + /// + /// The first version to compare. + /// The second version to compare. + /// A value indicating the relative order of the versions. + public int Compare(SemVersion version1, SemVersion version2) + { + return version1.CompareTo(version2); + } + + /// + /// Determines whether a version satisfies a version range. + /// + /// The version to check. + /// The version range to check against. + /// True if the version satisfies the range; otherwise, false. + public bool Satisfies(SemVersion version, string versionRange) + { + if (string.IsNullOrWhiteSpace(versionRange)) + return true; + + versionRange = versionRange.Trim(); + + // Handle exact version + if (!versionRange.StartsWith(">=") && !versionRange.StartsWith("<=") && + !versionRange.StartsWith(">") && !versionRange.StartsWith("<") && + !versionRange.StartsWith("^") && !versionRange.StartsWith("~")) + { + if (SemVersion.TryParse(versionRange, out var exactVersion)) + return version.Equals(exactVersion); + return false; + } + + // Handle >= operator + if (versionRange.StartsWith(">=")) + { + var rangeVer = versionRange.Substring(2).Trim(); + if (SemVersion.TryParse(rangeVer, out var minVersion)) + return version >= minVersion; + return false; + } + + // Handle > operator + if (versionRange.StartsWith(">")) + { + var rangeVer = versionRange.Substring(1).Trim(); + if (SemVersion.TryParse(rangeVer, out var minVersion)) + return version > minVersion; + return false; + } + + // Handle <= operator + if (versionRange.StartsWith("<=")) + { + var rangeVer = versionRange.Substring(2).Trim(); + if (SemVersion.TryParse(rangeVer, out var maxVersion)) + return version <= maxVersion; + return false; + } + + // Handle < operator + if (versionRange.StartsWith("<")) + { + var rangeVer = versionRange.Substring(1).Trim(); + if (SemVersion.TryParse(rangeVer, out var maxVersion)) + return version < maxVersion; + return false; + } + + // Handle ^ (caret) - compatible with version (same major version for >=1.0.0) + if (versionRange.StartsWith("^")) + { + var rangeVer = versionRange.Substring(1).Trim(); + if (SemVersion.TryParse(rangeVer, out var baseVersion)) + { + if (baseVersion.Major == 0) + { + // For 0.x.y, only minor and patch must match exactly or be greater + return version.Major == 0 && version.Minor == baseVersion.Minor && version >= baseVersion; + } + return version.Major == baseVersion.Major && version >= baseVersion; + } + return false; + } + + // Handle ~ (tilde) - approximately equivalent (same major.minor version) + if (versionRange.StartsWith("~")) + { + var rangeVer = versionRange.Substring(1).Trim(); + if (SemVersion.TryParse(rangeVer, out var baseVersion)) + { + return version.Major == baseVersion.Major && + version.Minor == baseVersion.Minor && + version >= baseVersion; + } + return false; + } + + return false; + } + + /// + /// Determines whether two versions are equal. + /// + /// The first version to compare. + /// The second version to compare. + /// True if the versions are equal; otherwise, false. + public bool Equals(SemVersion version1, SemVersion version2) + { + return version1.Equals(version2); + } + } +} diff --git a/src/c#/GeneralUpdate.Extension/SignatureValidator.cs b/src/c#/GeneralUpdate.Extension/SignatureValidator.cs new file mode 100644 index 00000000..840b3be4 --- /dev/null +++ b/src/c#/GeneralUpdate.Extension/SignatureValidator.cs @@ -0,0 +1,169 @@ +using System; +using System.IO; +using System.Security.Cryptography; +using System.Security.Cryptography.X509Certificates; +using System.Threading.Tasks; + +namespace MyApp.Extensions +{ + /// + /// Default implementation of ISignatureValidator for validating package signatures. + /// + public class SignatureValidator : ISignatureValidator + { + private readonly string[] _trustedCertificateThumbprints; + + /// + /// Initializes a new instance of the class. + /// + /// Array of trusted certificate thumbprints. + public SignatureValidator(string[] trustedCertificateThumbprints = null) + { + _trustedCertificateThumbprints = trustedCertificateThumbprints ?? Array.Empty(); + } + + /// + /// Validates the signature of a package. + /// + /// The path to the package. + /// A task that represents the asynchronous operation, containing the validation result. + public async Task ValidateSignatureAsync(string packagePath) + { + var result = new SignatureValidationResult + { + IsValid = false, + IsTrusted = false + }; + + try + { + if (!File.Exists(packagePath)) + { + result.ErrorMessage = "Package file not found"; + return result; + } + + // In real implementation, would: + // 1. Extract signature from package + // 2. Verify digital signature + // 3. Check certificate chain + // 4. Validate certificate hasn't been revoked + + await Task.Delay(50); // Placeholder + + // Simulate signature check + result.IsValid = true; + result.SignerIdentity = "CN=Example Publisher, O=Example Org, C=US"; + result.CertificateThumbprint = "1234567890ABCDEF"; + + // Check if trusted + result.IsTrusted = Array.Exists(_trustedCertificateThumbprints, + thumb => thumb.Equals(result.CertificateThumbprint, StringComparison.OrdinalIgnoreCase)); + + return result; + } + catch (Exception ex) + { + result.ErrorMessage = ex.Message; + return result; + } + } + + /// + /// Verifies the integrity of a package using its hash. + /// + /// The path to the package. + /// The expected hash value. + /// The hash algorithm to use (e.g., "SHA256"). + /// A task that represents the asynchronous operation, indicating whether the integrity is valid. + public async Task VerifyIntegrityAsync(string packagePath, string expectedHash, string hashAlgorithm) + { + try + { + if (!File.Exists(packagePath)) + return false; + + if (string.IsNullOrWhiteSpace(expectedHash)) + return false; + + HashAlgorithm hasher; + switch (hashAlgorithm?.ToUpperInvariant()) + { + case "SHA256": + hasher = SHA256.Create(); + break; + case "SHA512": + hasher = SHA512.Create(); + break; + case "MD5": + hasher = MD5.Create(); + break; + default: + hasher = SHA256.Create(); + break; + } + + using (hasher) + { + using (var stream = File.OpenRead(packagePath)) + { + var hashBytes = await Task.Run(() => hasher.ComputeHash(stream)); + var actualHash = BitConverter.ToString(hashBytes).Replace("-", "").ToLowerInvariant(); + + return actualHash.Equals(expectedHash.ToLowerInvariant(), StringComparison.Ordinal); + } + } + } + catch + { + return false; + } + } + + /// + /// Validates the certificate chain for a package signature. + /// + /// The path to the package. + /// A task that represents the asynchronous operation, indicating whether the certificate chain is valid. + public async Task ValidateCertificateChainAsync(string packagePath) + { + try + { + if (!File.Exists(packagePath)) + return false; + + // In real implementation, would: + // 1. Extract certificate from package + // 2. Build certificate chain + // 3. Verify chain to trusted root + // 4. Check for revocation + + await Task.Delay(50); // Placeholder + + return true; + } + catch + { + return false; + } + } + + /// + /// Checks whether a package is signed by a trusted authority. + /// + /// The path to the package. + /// A task that represents the asynchronous operation, indicating whether the package is trusted. + public async Task IsTrustedAsync(string packagePath) + { + try + { + var validationResult = await ValidateSignatureAsync(packagePath); + return validationResult.IsValid && validationResult.IsTrusted; + } + catch + { + return false; + } + } + } +} diff --git a/src/c#/GeneralUpdate.Extension/Updates/DeltaPatchInfo.cs b/src/c#/GeneralUpdate.Extension/Updates/DeltaPatchInfo.cs new file mode 100644 index 00000000..bd131df3 --- /dev/null +++ b/src/c#/GeneralUpdate.Extension/Updates/DeltaPatchInfo.cs @@ -0,0 +1,82 @@ +using System.Collections.Generic; + +namespace MyApp.Extensions.Updates +{ + /// + /// Represents information about a delta patch, including baseline version, target version, + /// patch algorithm, and differential block information. + /// + public class DeltaPatchInfo + { + /// + /// Gets or sets the baseline version from which the patch is applied. + /// + public string BaselineVersion { get; set; } + + /// + /// Gets or sets the target version that will be achieved after applying the patch. + /// + public string TargetVersion { get; set; } + + /// + /// Gets or sets the patch algorithm used (e.g., "BSDiff", "Xdelta", "Custom"). + /// + public string PatchAlgorithm { get; set; } + + /// + /// Gets or sets the size of the patch in bytes. + /// + public long PatchSize { get; set; } + + /// + /// Gets or sets the list of differential blocks that make up the patch. + /// + public List DifferentialBlocks { get; set; } + + /// + /// Gets or sets the compression method used for the patch (e.g., "gzip", "bz2", "xz"). + /// + public string CompressionMethod { get; set; } + + /// + /// Gets or sets the hash of the patch file for integrity verification. + /// + public string PatchHash { get; set; } + + /// + /// Gets or sets the hash algorithm used (e.g., "SHA256"). + /// + public string HashAlgorithm { get; set; } + } + + /// + /// Represents a single differential block within a patch. + /// + public class DifferentialBlock + { + /// + /// Gets or sets the offset in the source file where the block starts. + /// + public long SourceOffset { get; set; } + + /// + /// Gets or sets the length of the block in the source file. + /// + public long SourceLength { get; set; } + + /// + /// Gets or sets the offset in the target file where the block should be written. + /// + public long TargetOffset { get; set; } + + /// + /// Gets or sets the length of the block in the target file. + /// + public long TargetLength { get; set; } + + /// + /// Gets or sets the hash of the block for verification. + /// + public string BlockHash { get; set; } + } +} diff --git a/src/c#/GeneralUpdate.Extension/Updates/DeltaUpdateService.cs b/src/c#/GeneralUpdate.Extension/Updates/DeltaUpdateService.cs new file mode 100644 index 00000000..03f1e381 --- /dev/null +++ b/src/c#/GeneralUpdate.Extension/Updates/DeltaUpdateService.cs @@ -0,0 +1,215 @@ +using System; +using System.Collections.Generic; +using System.IO; +using System.Linq; +using System.Threading.Tasks; +using MyApp.Extensions; + +namespace MyApp.Extensions.Updates +{ + /// + /// Default implementation of IDeltaUpdateService for delta/incremental updates. + /// + public class DeltaUpdateService : IDeltaUpdateService + { + private readonly string _patchCachePath; + private readonly SemVersionComparer _versionComparer; + + /// + /// Initializes a new instance of the class. + /// + /// The path where patches are cached. + public DeltaUpdateService(string patchCachePath) + { + _patchCachePath = patchCachePath ?? throw new ArgumentNullException(nameof(patchCachePath)); + _versionComparer = new SemVersionComparer(); + + if (!Directory.Exists(_patchCachePath)) + { + Directory.CreateDirectory(_patchCachePath); + } + } + + /// + /// Generates a delta patch between two versions. + /// + /// The baseline version. + /// The target version. + /// A task that represents the asynchronous operation, containing the delta patch information. + public async Task GenerateDeltaPatchAsync(string baselineVersion, string targetVersion) + { + try + { + if (!SemVersion.TryParse(baselineVersion, out var baseVer)) + throw new ArgumentException("Invalid baseline version", nameof(baselineVersion)); + + if (!SemVersion.TryParse(targetVersion, out var targetVer)) + throw new ArgumentException("Invalid target version", nameof(targetVersion)); + + if (baseVer >= targetVer) + throw new InvalidOperationException("Target version must be greater than baseline version"); + + // In real implementation, would: + // 1. Compare file trees between versions + // 2. Identify changed/added/removed files + // 3. Generate binary diffs for changed files + // 4. Create patch package + + await Task.Delay(100); // Placeholder + + return new DeltaPatchInfo + { + BaselineVersion = baselineVersion, + TargetVersion = targetVersion, + PatchAlgorithm = "BSDiff", + PatchSize = 1024 * 100, // 100 KB placeholder + CompressionMethod = "gzip", + PatchHash = "abc123def456", + HashAlgorithm = "SHA256", + DifferentialBlocks = new List + { + new DifferentialBlock + { + SourceOffset = 0, + SourceLength = 1000, + TargetOffset = 0, + TargetLength = 1200, + BlockHash = "block1hash" + } + } + }; + } + catch (Exception ex) + { + throw new InvalidOperationException($"Failed to generate delta patch from {baselineVersion} to {targetVersion}", ex); + } + } + + /// + /// Applies a delta patch to upgrade from baseline to target version. + /// + /// The delta patch information. + /// A task that represents the asynchronous operation, indicating success or failure. + public async Task ApplyDeltaPatchAsync(DeltaPatchInfo patchInfo) + { + try + { + if (patchInfo == null) + return false; + + // Validate patch first + if (!await ValidateDeltaPatchAsync(patchInfo)) + return false; + + // In real implementation, would: + // 1. Backup current files + // 2. Apply binary patches + // 3. Add new files + // 4. Remove deleted files + // 5. Verify result integrity + + await Task.Delay(100); // Placeholder + + return true; + } + catch + { + return false; + } + } + + /// + /// Validates a delta patch before applying it. + /// + /// The delta patch information to validate. + /// A task that represents the asynchronous operation, indicating whether the patch is valid. + public async Task ValidateDeltaPatchAsync(DeltaPatchInfo patchInfo) + { + try + { + if (patchInfo == null) + return false; + + // Verify versions are valid + if (!SemVersion.TryParse(patchInfo.BaselineVersion, out var baseVer)) + return false; + + if (!SemVersion.TryParse(patchInfo.TargetVersion, out var targetVer)) + return false; + + if (baseVer >= targetVer) + return false; + + // Verify patch algorithm is supported + var supportedAlgorithms = new[] { "BSDiff", "Xdelta", "Custom" }; + if (!supportedAlgorithms.Contains(patchInfo.PatchAlgorithm)) + return false; + + // In real implementation, would also verify: + // - Patch file exists and is readable + // - Patch hash matches + // - Differential blocks are valid + + await Task.CompletedTask; + return true; + } + catch + { + return false; + } + } + + /// + /// Calculates the optimal update path from current version to target version. + /// + /// The current version. + /// The target version. + /// A task that represents the asynchronous operation, containing the update path. + public async Task CalculateOptimalUpdatePathAsync(string currentVersion, string targetVersion) + { + try + { + if (!SemVersion.TryParse(currentVersion, out var current)) + throw new ArgumentException("Invalid current version", nameof(currentVersion)); + + if (!SemVersion.TryParse(targetVersion, out var target)) + throw new ArgumentException("Invalid target version", nameof(targetVersion)); + + if (current >= target) + throw new InvalidOperationException("Target version must be greater than current version"); + + // In real implementation, would: + // 1. Query available patches from repository + // 2. Build graph of possible update paths + // 3. Find path with minimum download size + // 4. Consider update dependencies and ordering + + await Task.Delay(50); // Placeholder + + var path = new UpdatePath + { + StartVersion = currentVersion, + EndVersion = targetVersion, + IntermediateVersions = new string[0], // Direct update + EstimatedDownloadSize = 1024 * 500 // 500 KB placeholder + }; + + // Check if incremental updates are beneficial + var majorDiff = target.Major - current.Major; + var minorDiff = target.Minor - current.Minor; + + if (majorDiff > 0 || minorDiff > 3) + { + // Suggest full update for major version changes or large minor gaps + path.EstimatedDownloadSize = 1024 * 1024 * 50; // 50 MB full package + } + + return path; + } + catch (Exception ex) + { + throw new InvalidOperationException($"Failed to calculate update path from {currentVersion} to {targetVersion}", ex); + } + } + } +} diff --git a/src/c#/GeneralUpdate.Extension/Updates/IDeltaUpdateService.cs b/src/c#/GeneralUpdate.Extension/Updates/IDeltaUpdateService.cs new file mode 100644 index 00000000..84a5718d --- /dev/null +++ b/src/c#/GeneralUpdate.Extension/Updates/IDeltaUpdateService.cs @@ -0,0 +1,67 @@ +using System; +using System.Threading.Tasks; + +namespace MyApp.Extensions.Updates +{ + /// + /// Provides services for delta/incremental updates. + /// + public interface IDeltaUpdateService + { + /// + /// Generates a delta patch between two versions. + /// + /// The baseline version. + /// The target version. + /// A task that represents the asynchronous operation, containing the delta patch information. + Task GenerateDeltaPatchAsync(string baselineVersion, string targetVersion); + + /// + /// Applies a delta patch to upgrade from baseline to target version. + /// + /// The delta patch information. + /// A task that represents the asynchronous operation, indicating success or failure. + Task ApplyDeltaPatchAsync(DeltaPatchInfo patchInfo); + + /// + /// Validates a delta patch before applying it. + /// + /// The delta patch information to validate. + /// A task that represents the asynchronous operation, indicating whether the patch is valid. + Task ValidateDeltaPatchAsync(DeltaPatchInfo patchInfo); + + /// + /// Calculates the optimal update path from current version to target version. + /// + /// The current version. + /// The target version. + /// A task that represents the asynchronous operation, containing the update path. + Task CalculateOptimalUpdatePathAsync(string currentVersion, string targetVersion); + } + + /// + /// Represents a path for updating from one version to another. + /// + public class UpdatePath + { + /// + /// Gets or sets the starting version. + /// + public string StartVersion { get; set; } + + /// + /// Gets or sets the ending version. + /// + public string EndVersion { get; set; } + + /// + /// Gets or sets the list of intermediate versions in the update path. + /// + public string[] IntermediateVersions { get; set; } + + /// + /// Gets or sets the estimated total download size for the update path. + /// + public long EstimatedDownloadSize { get; set; } + } +} diff --git a/src/c#/GeneralUpdate.Extension/Updates/IUpdateService.cs b/src/c#/GeneralUpdate.Extension/Updates/IUpdateService.cs new file mode 100644 index 00000000..cc6c8a66 --- /dev/null +++ b/src/c#/GeneralUpdate.Extension/Updates/IUpdateService.cs @@ -0,0 +1,48 @@ +using System; +using System.Threading.Tasks; + +namespace MyApp.Extensions.Updates +{ + /// + /// Provides core update services for extensions. + /// + public interface IUpdateService + { + /// + /// Checks for available updates for a specific extension. + /// + /// The unique identifier of the extension. + /// A task that represents the asynchronous operation, containing the update metadata. + Task CheckForUpdatesAsync(string extensionId); + + /// + /// Downloads an update package. + /// + /// The package information to download. + /// Optional progress reporter. + /// A task that represents the asynchronous operation, containing the path to the downloaded package. + Task DownloadUpdateAsync(UpdatePackageInfo packageInfo, IProgress progress = null); + + /// + /// Installs an update from a downloaded package. + /// + /// The path to the update package. + /// A task that represents the asynchronous operation, indicating success or failure. + Task InstallUpdateAsync(string packagePath); + + /// + /// Rolls back to a previous version. + /// + /// The rollback information. + /// A task that represents the asynchronous operation, indicating success or failure. + Task RollbackAsync(RollbackInfo rollbackInfo); + + /// + /// Verifies the integrity of an update package. + /// + /// The path to the package. + /// The expected hash value. + /// A task that represents the asynchronous operation, indicating whether the package is valid. + Task VerifyPackageIntegrityAsync(string packagePath, string expectedHash); + } +} diff --git a/src/c#/GeneralUpdate.Extension/Updates/RollbackInfo.cs b/src/c#/GeneralUpdate.Extension/Updates/RollbackInfo.cs new file mode 100644 index 00000000..230bd831 --- /dev/null +++ b/src/c#/GeneralUpdate.Extension/Updates/RollbackInfo.cs @@ -0,0 +1,45 @@ +using System; + +namespace MyApp.Extensions.Updates +{ + /// + /// Represents information about a rollback operation, including the target version and reason. + /// + public class RollbackInfo + { + /// + /// Gets or sets the unique identifier of the extension being rolled back. + /// + public string ExtensionId { get; set; } + + /// + /// Gets or sets the current version before rollback. + /// + public string CurrentVersion { get; set; } + + /// + /// Gets or sets the target version to roll back to. + /// + public string TargetVersion { get; set; } + + /// + /// Gets or sets the reason for the rollback. + /// + public string Reason { get; set; } + + /// + /// Gets or sets the timestamp when the rollback was initiated. + /// + public DateTime RollbackTimestamp { get; set; } + + /// + /// Gets or sets a value indicating whether to preserve user data during rollback. + /// + public bool PreserveUserData { get; set; } + + /// + /// Gets or sets the path to the rollback snapshot or backup. + /// + public string SnapshotPath { get; set; } + } +} diff --git a/src/c#/GeneralUpdate.Extension/Updates/UpdateChannel.cs b/src/c#/GeneralUpdate.Extension/Updates/UpdateChannel.cs new file mode 100644 index 00000000..a1230df6 --- /dev/null +++ b/src/c#/GeneralUpdate.Extension/Updates/UpdateChannel.cs @@ -0,0 +1,23 @@ +namespace MyApp.Extensions.Updates +{ + /// + /// Represents the update channel for an extension. + /// + public enum UpdateChannel + { + /// + /// Stable release channel for production-ready updates. + /// + Stable, + + /// + /// Pre-release channel for beta or release candidate versions. + /// + PreRelease, + + /// + /// Development channel for cutting-edge, experimental updates. + /// + Dev + } +} diff --git a/src/c#/GeneralUpdate.Extension/Updates/UpdateMetadata.cs b/src/c#/GeneralUpdate.Extension/Updates/UpdateMetadata.cs new file mode 100644 index 00000000..1ecdec0b --- /dev/null +++ b/src/c#/GeneralUpdate.Extension/Updates/UpdateMetadata.cs @@ -0,0 +1,66 @@ +using System; +using System.Collections.Generic; + +namespace MyApp.Extensions.Updates +{ + /// + /// Represents metadata for available updates, including version lists and compatibility information. + /// + public class UpdateMetadata + { + /// + /// Gets or sets the unique identifier of the extension. + /// + public string ExtensionId { get; set; } + + /// + /// Gets or sets the current version of the extension. + /// + public string CurrentVersion { get; set; } + + /// + /// Gets or sets the latest available version. + /// + public string LatestVersion { get; set; } + + /// + /// Gets or sets the list of available versions. + /// + public List AvailableVersions { get; set; } + + /// + /// Gets or sets the update channel. + /// + public UpdateChannel Channel { get; set; } + + /// + /// Gets or sets the last update check timestamp. + /// + public DateTime LastChecked { get; set; } + + /// + /// Gets or sets the release date of the latest version. + /// + public DateTime ReleaseDate { get; set; } + + /// + /// Gets or sets the minimum host version required for the update. + /// + public string MinimumHostVersion { get; set; } + + /// + /// Gets or sets the maximum host version compatible with the update. + /// + public string MaximumHostVersion { get; set; } + + /// + /// Gets or sets the changelog or release notes for the update. + /// + public string Changelog { get; set; } + + /// + /// Gets or sets a value indicating whether the update is mandatory. + /// + public bool IsMandatory { get; set; } + } +} diff --git a/src/c#/GeneralUpdate.Extension/Updates/UpdatePackageInfo.cs b/src/c#/GeneralUpdate.Extension/Updates/UpdatePackageInfo.cs new file mode 100644 index 00000000..5a3f1fee --- /dev/null +++ b/src/c#/GeneralUpdate.Extension/Updates/UpdatePackageInfo.cs @@ -0,0 +1,65 @@ +using System; + +namespace MyApp.Extensions.Updates +{ + /// + /// Represents information about an update package, including full, delta, or differential package details. + /// + public class UpdatePackageInfo + { + /// + /// Gets or sets the unique identifier of the package. + /// + public string PackageId { get; set; } + + /// + /// Gets or sets the version of the update package. + /// + public string Version { get; set; } + + /// + /// Gets or sets the type of package (e.g., "Full", "Delta", "Diff"). + /// + public string PackageType { get; set; } + + /// + /// Gets or sets the size of the package in bytes. + /// + public long Size { get; set; } + + /// + /// Gets or sets the hash of the package for integrity verification. + /// + public string Hash { get; set; } + + /// + /// Gets or sets the hash algorithm used (e.g., "SHA256", "SHA512"). + /// + public string HashAlgorithm { get; set; } + + /// + /// Gets or sets the download URL for the package. + /// + public string DownloadUrl { get; set; } + + /// + /// Gets or sets the signature of the package for verification. + /// + public string Signature { get; set; } + + /// + /// Gets or sets the timestamp when the package was created. + /// + public DateTime CreatedTimestamp { get; set; } + + /// + /// Gets or sets the baseline version required for delta/diff packages. + /// + public string BaselineVersion { get; set; } + + /// + /// Gets or sets the target version that will be achieved after applying the package. + /// + public string TargetVersion { get; set; } + } +} diff --git a/src/c#/GeneralUpdate.Extension/Updates/UpdateService.cs b/src/c#/GeneralUpdate.Extension/Updates/UpdateService.cs new file mode 100644 index 00000000..c977d2d8 --- /dev/null +++ b/src/c#/GeneralUpdate.Extension/Updates/UpdateService.cs @@ -0,0 +1,176 @@ +using System; +using System.IO; +using System.Security.Cryptography; +using System.Threading.Tasks; + +namespace MyApp.Extensions.Updates +{ + /// + /// Default implementation of IUpdateService for managing extension updates. + /// + public class UpdateService : IUpdateService + { + private readonly string _updateCachePath; + + /// + /// Initializes a new instance of the class. + /// + /// The path where updates are cached. + public UpdateService(string updateCachePath) + { + _updateCachePath = updateCachePath ?? throw new ArgumentNullException(nameof(updateCachePath)); + + if (!Directory.Exists(_updateCachePath)) + { + Directory.CreateDirectory(_updateCachePath); + } + } + + /// + /// Checks for available updates for a specific extension. + /// + /// The unique identifier of the extension. + /// A task that represents the asynchronous operation, containing the update metadata. + public async Task CheckForUpdatesAsync(string extensionId) + { + // In real implementation, would query update server/repository + await Task.Delay(100); // Placeholder for network call + + return new UpdateMetadata + { + ExtensionId = extensionId, + CurrentVersion = "1.0.0", + LatestVersion = "1.1.0", + Channel = UpdateChannel.Stable, + LastChecked = DateTime.UtcNow, + ReleaseDate = DateTime.UtcNow.AddDays(-7), + IsMandatory = false, + Changelog = "Bug fixes and improvements" + }; + } + + /// + /// Downloads an update package. + /// + /// The package information to download. + /// Optional progress reporter. + /// A task that represents the asynchronous operation, containing the path to the downloaded package. + public async Task DownloadUpdateAsync(UpdatePackageInfo packageInfo, IProgress progress = null) + { + try + { + if (packageInfo == null) + throw new ArgumentNullException(nameof(packageInfo)); + + var targetPath = Path.Combine(_updateCachePath, $"{packageInfo.PackageId}_{packageInfo.Version}.pkg"); + + // In real implementation, would download from packageInfo.DownloadUrl + // For now, simulate download with progress + for (int i = 0; i <= 100; i += 10) + { + await Task.Delay(50); // Simulate download time + progress?.Report(i / 100.0); + } + + // Create placeholder file (in real impl, would contain actual download) + File.WriteAllText(targetPath, "Update package placeholder"); + + return targetPath; + } + catch (Exception ex) + { + throw new InvalidOperationException("Failed to download update package", ex); + } + } + + /// + /// Installs an update from a downloaded package. + /// + /// The path to the update package. + /// A task that represents the asynchronous operation, indicating success or failure. + public async Task InstallUpdateAsync(string packagePath) + { + try + { + if (!File.Exists(packagePath)) + return false; + + // In real implementation, would: + // 1. Verify package integrity + // 2. Backup current version + // 3. Extract and install new version + // 4. Run migration scripts if needed + + await Task.Delay(100); // Placeholder for installation + + return true; + } + catch + { + return false; + } + } + + /// + /// Rolls back to a previous version. + /// + /// The rollback information. + /// A task that represents the asynchronous operation, indicating success or failure. + public async Task RollbackAsync(RollbackInfo rollbackInfo) + { + try + { + if (rollbackInfo == null) + return false; + + // In real implementation, would: + // 1. Verify backup exists + // 2. Stop current version + // 3. Restore backup + // 4. Restart with previous version + + await Task.Delay(100); // Placeholder for rollback + + return true; + } + catch + { + return false; + } + } + + /// + /// Verifies the integrity of an update package. + /// + /// The path to the package. + /// The expected hash value. + /// A task that represents the asynchronous operation, indicating whether the package is valid. + public async Task VerifyPackageIntegrityAsync(string packagePath, string expectedHash) + { + try + { + if (!File.Exists(packagePath)) + return false; + + if (string.IsNullOrWhiteSpace(expectedHash)) + return false; + + // Calculate hash of package + using (var sha256 = SHA256.Create()) + { + using (var stream = File.OpenRead(packagePath)) + { + var hashBytes = await Task.Run(() => sha256.ComputeHash(stream)); + var actualHash = BitConverter.ToString(hashBytes).Replace("-", "").ToLowerInvariant(); + + return actualHash.Equals(expectedHash.ToLowerInvariant(), StringComparison.Ordinal); + } + } + } + catch + { + return false; + } + } + } +}