diff --git a/PLUGIN_UPGRADE.md b/PLUGIN_UPGRADE.md new file mode 100644 index 00000000..e5642876 --- /dev/null +++ b/PLUGIN_UPGRADE.md @@ -0,0 +1,414 @@ +# Plugin Upgrade Functionality + +## Overview + +GeneralUpdate now supports plugin upgrade functionality, allowing client applications to manage plugins (JavaScript, Lua, Python, WASM, or external executables) with version compatibility checking and selective upgrades, similar to VS Code's extension management. + +## Features + +1. **Plugin Version Management**: Track and manage multiple plugin versions with compatibility ranges +2. **Automatic Compatibility Checking**: Ensures plugins are compatible with the current client version +3. **Selective Plugin Upgrades**: Users can choose which plugins to upgrade +4. **Automatic Minimum Version Matching**: When the client upgrades, automatically determines the minimum required plugin versions + +## Plugin Types + +The system supports the following plugin types: + +- **JavaScript** (`PluginType = 1`): Plugins using embedded script engine +- **Lua** (`PluginType = 2`): Plugins using Lua script engine +- **Python** (`PluginType = 3`): Plugins using Python script engine +- **WASM** (`PluginType = 4`): WebAssembly plugins +- **ExternalExecutable** (`PluginType = 5`): External executable programs with protocol communication + +## Quick Start + +### 1. Configure Plugin Update URL + +```csharp +var configInfo = new Configinfo +{ + AppName = "MyApp", + MainAppName = "MyApp", + ClientVersion = "1.0.0", + InstallPath = @"C:\MyApp", + UpdateUrl = "https://api.example.com/update", + PluginUpdateUrl = "https://api.example.com/plugin-update", // NEW: Plugin update endpoint + AppSecretKey = "your-secret-key", + ProductId = "product123" +}; + +GeneralClientBootstrap.Instance + .SetConfig(configInfo) + .LaunchAsync(); +``` + +### 2. Get Available Plugin Updates + +```csharp +using GeneralUpdate.ClientCore; +using GeneralUpdate.Common.Shared.Object; + +var configInfo = new GlobalConfigInfo +{ + PluginUpdateUrl = "https://api.example.com/plugin-update", + ClientVersion = "1.0.0", + AppSecretKey = "your-secret-key", + ProductId = "product123", + Scheme = "Bearer", + Token = "your-auth-token" +}; + +// Get all available plugin updates compatible with current client version +var availablePlugins = await PluginBootstrapExtensions.GetAvailablePluginUpdatesAsync( + configInfo, + PlatformType.Windows +); + +Console.WriteLine($"Found {availablePlugins.Count} plugin updates"); +foreach (var plugin in availablePlugins) +{ + Console.WriteLine($"- {plugin.Name} v{plugin.Version} ({plugin.PluginType})"); +} +``` + +### 3. Selective Plugin Upgrade (VS Code-style) + +```csharp +// Let user select which plugins to upgrade +var selectedPluginIds = new List { "plugin-id-1", "plugin-id-3" }; + +var pluginsToUpgrade = PluginBootstrapExtensions.SelectPluginsForUpgrade( + availablePlugins, + selectedPluginIds +); + +Console.WriteLine($"User selected {pluginsToUpgrade.Count} plugins to upgrade"); +``` + +### 4. Get Mandatory Plugin Updates + +```csharp +// Get only mandatory plugin updates +var mandatoryPlugins = PluginBootstrapExtensions.GetMandatoryPluginUpdates(availablePlugins); + +if (mandatoryPlugins.Any()) +{ + Console.WriteLine("The following plugins MUST be upgraded:"); + foreach (var plugin in mandatoryPlugins) + { + Console.WriteLine($"- {plugin.Name} v{plugin.Version}"); + } +} +``` + +### 5. Automatic Plugin Version Matching on Client Upgrade + +```csharp +// When upgrading client to version 2.0.0, automatically determine required plugin versions +var currentPlugins = new Dictionary +{ + { "plugin-id-1", "1.0.0" }, + { "plugin-id-2", "1.5.0" }, + { "plugin-id-3", "2.0.0" } +}; + +var requiredPluginUpdates = await PluginBootstrapExtensions.GetRequiredPluginUpdatesForClientUpgradeAsync( + configInfo, + "2.0.0", // Target client version + PlatformType.Windows, + currentPlugins +); + +Console.WriteLine($"{requiredPluginUpdates.Count} plugins need to be upgraded for client v2.0.0"); +``` + +### 6. Validate Plugin Compatibility + +```csharp +// Check if a specific plugin is compatible with the client version +var plugin = new PluginInfo +{ + PluginId = "my-plugin", + Version = "1.5.0", + MinClientVersion = "1.0.0", + MaxClientVersion = "2.0.0" +}; + +bool isCompatible = PluginUpdateService.IsPluginCompatible("1.8.0", plugin); +Console.WriteLine($"Plugin is compatible: {isCompatible}"); // Output: true + +isCompatible = PluginUpdateService.IsPluginCompatible("2.5.0", plugin); +Console.WriteLine($"Plugin is compatible: {isCompatible}"); // Output: false +``` + +## Server-Side API Implementation + +### Plugin Validation Endpoint + +Your server should implement a plugin validation endpoint that returns available plugin updates: + +**Request:** +```json +POST https://api.example.com/plugin-update +Content-Type: application/json +Authorization: Bearer your-token + +{ + "ClientVersion": "1.0.0", + "Platform": 1, + "PluginId": "optional-specific-plugin-id", + "AppKey": "your-secret-key", + "ProductId": "product123" +} +``` + +**Response:** +```json +{ + "code": 200, + "message": "OK", + "body": [ + { + "pluginId": "js-plugin-1", + "name": "JavaScript Plugin", + "version": "1.2.0", + "pluginType": 1, + "minClientVersion": "1.0.0", + "maxClientVersion": "2.0.0", + "url": "https://cdn.example.com/plugins/js-plugin-1-1.2.0.zip", + "hash": "sha256-hash-here", + "releaseDate": "2026-01-15T00:00:00Z", + "size": 1024000, + "format": "zip", + "description": "A useful JavaScript plugin", + "isMandatory": false + }, + { + "pluginId": "python-plugin-2", + "name": "Python Data Processor", + "version": "2.0.0", + "pluginType": 3, + "minClientVersion": "1.5.0", + "maxClientVersion": "3.0.0", + "url": "https://cdn.example.com/plugins/python-plugin-2-2.0.0.zip", + "hash": "sha256-hash-here", + "releaseDate": "2026-01-10T00:00:00Z", + "size": 2048000, + "format": "zip", + "description": "Enhanced data processing capabilities", + "isMandatory": true + } + ] +} +``` + +## Complete Usage Example + +```csharp +using System; +using System.Collections.Generic; +using System.Linq; +using System.Threading.Tasks; +using GeneralUpdate.ClientCore; +using GeneralUpdate.Common.Shared.Object; +using GeneralUpdate.Common.Shared.Object.Enum; + +public class PluginUpgradeExample +{ + public static async Task Main() + { + // Step 1: Configure the client + var configInfo = new GlobalConfigInfo + { + PluginUpdateUrl = "https://api.example.com/plugin-update", + ClientVersion = "1.0.0", + AppSecretKey = "your-secret-key", + ProductId = "product123" + }; + + // Step 2: Check for available plugin updates + var availablePlugins = await PluginBootstrapExtensions.GetAvailablePluginUpdatesAsync( + configInfo, + PlatformType.Windows + ); + + if (!availablePlugins.Any()) + { + Console.WriteLine("No plugin updates available"); + return; + } + + // Step 3: Separate mandatory and optional updates + var mandatoryPlugins = PluginBootstrapExtensions.GetMandatoryPluginUpdates(availablePlugins); + var optionalPlugins = availablePlugins.Except(mandatoryPlugins).ToList(); + + // Step 4: Handle mandatory updates + if (mandatoryPlugins.Any()) + { + Console.WriteLine("Installing mandatory plugin updates..."); + foreach (var plugin in mandatoryPlugins) + { + Console.WriteLine($"Installing {plugin.Name} v{plugin.Version}"); + // Download and install plugin + await DownloadAndInstallPlugin(plugin); + } + } + + // Step 5: Let user choose optional updates + if (optionalPlugins.Any()) + { + Console.WriteLine("\nOptional plugin updates:"); + for (int i = 0; i < optionalPlugins.Count; i++) + { + var plugin = optionalPlugins[i]; + Console.WriteLine($"{i + 1}. {plugin.Name} v{plugin.Version} - {plugin.Description}"); + } + + Console.Write("\nEnter plugin numbers to install (comma-separated): "); + var input = Console.ReadLine(); + var selectedIndices = input.Split(',').Select(s => int.Parse(s.Trim()) - 1); + + var selectedPluginIds = selectedIndices + .Select(i => optionalPlugins[i].PluginId) + .ToList(); + + var selectedPlugins = PluginBootstrapExtensions.SelectPluginsForUpgrade( + optionalPlugins, + selectedPluginIds + ); + + foreach (var plugin in selectedPlugins) + { + Console.WriteLine($"Installing {plugin.Name} v{plugin.Version}"); + await DownloadAndInstallPlugin(plugin); + } + } + + Console.WriteLine("\nPlugin updates completed!"); + } + + private static async Task DownloadAndInstallPlugin(PluginInfo plugin) + { + // Implementation for downloading and installing plugin + // This would use DownloadManager similar to client updates + await Task.Delay(100); // Placeholder + } +} +``` + +## Advanced Scenarios + +### Scenario 1: Client Upgrade with Plugin Compatibility Check + +```csharp +// Before upgrading client, check if current plugins will be compatible +var targetClientVersion = "2.0.0"; +var currentPlugins = new List +{ + new PluginInfo + { + Name = "Plugin A", + Version = "1.0.0", + MinClientVersion = "1.0.0", + MaxClientVersion = "1.9.0" // Won't work with 2.0.0! + } +}; + +bool allCompatible = PluginBootstrapExtensions.ValidatePluginCompatibilityForUpgrade( + targetClientVersion, + currentPlugins +); + +if (!allCompatible) +{ + Console.WriteLine("Warning: Some plugins are not compatible with the new client version!"); + // Prompt user to upgrade plugins first +} +``` + +### Scenario 2: Determine Plugin Updates Needed for Client Upgrade + +```csharp +var currentPluginVersions = new Dictionary +{ + { "plugin-a", "1.0.0" }, + { "plugin-b", "1.5.0" } +}; + +var targetClientVersion = "2.0.0"; + +// Automatically find minimum required plugin versions for the new client +var requiredUpdates = await PluginBootstrapExtensions.GetRequiredPluginUpdatesForClientUpgradeAsync( + configInfo, + targetClientVersion, + PlatformType.Windows, + currentPluginVersions +); + +Console.WriteLine($"To upgrade to client v{targetClientVersion}, you need to upgrade:"); +foreach (var plugin in requiredUpdates) +{ + Console.WriteLine($"- {plugin.Name} to v{plugin.Version}"); +} +``` + +## Data Models + +### PluginInfo +```csharp +public class PluginInfo +{ + public string PluginId { get; set; } // Unique plugin identifier + public string Name { get; set; } // Plugin display name + public string Version { get; set; } // Plugin version + public int PluginType { get; set; } // 1-5 (JS/Lua/Python/WASM/Exe) + public string MinClientVersion { get; set; } // Minimum compatible client version + public string MaxClientVersion { get; set; } // Maximum compatible client version + public string Url { get; set; } // Download URL + public string Hash { get; set; } // File integrity hash + public DateTime ReleaseDate { get; set; } // Release timestamp + public long Size { get; set; } // File size in bytes + public string Format { get; set; } // Package format (e.g., "zip") + public string Description { get; set; } // Plugin description + public bool IsMandatory { get; set; } // Whether upgrade is mandatory +} +``` + +## Best Practices + +1. **Always check compatibility** before installing plugins +2. **Handle mandatory updates first** before offering optional updates +3. **Validate plugin compatibility** before upgrading the client +4. **Use version ranges** to define plugin compatibility (MinClientVersion, MaxClientVersion) +5. **Implement rollback mechanisms** for failed plugin installations +6. **Cache plugin metadata** to reduce API calls +7. **Show clear UI** to users about what plugins are being updated and why + +## Migration Guide + +If you're adding plugin support to an existing GeneralUpdate implementation: + +1. Add `PluginUpdateUrl` to your `Configinfo` +2. Implement the plugin validation endpoint on your server +3. Use `PluginBootstrapExtensions` methods to manage plugin updates +4. Update your UI to show plugin update options to users + +## Troubleshooting + +**Q: Plugins aren't showing up as available** +A: Ensure your `PluginUpdateUrl` is configured and the server returns plugins compatible with your `ClientVersion` + +**Q: Plugin compatibility check fails** +A: Verify that `MinClientVersion` and `MaxClientVersion` are valid semantic versions + +**Q: Mandatory plugins aren't being enforced** +A: Check that `IsMandatory` is set to `true` in the server response + +## API Reference + +See the following classes for detailed API documentation: +- `PluginInfo` - Plugin metadata model +- `PluginBootstrapExtensions` - Main API for plugin management +- `PluginUpdateService` - Service methods for plugin operations +- `PluginCompatibilityChecker` - Compatibility validation utilities diff --git a/src/c#/GeneralUpdate.Bowl/GeneralUpdate.Bowl.csproj b/src/c#/GeneralUpdate.Bowl/GeneralUpdate.Bowl.csproj index 2b03c3a8..016f6e59 100644 --- a/src/c#/GeneralUpdate.Bowl/GeneralUpdate.Bowl.csproj +++ b/src/c#/GeneralUpdate.Bowl/GeneralUpdate.Bowl.csproj @@ -53,6 +53,7 @@ + @@ -61,11 +62,13 @@ + + @@ -73,6 +76,8 @@ + + diff --git a/src/c#/GeneralUpdate.ClientCore/GeneralUpdate.ClientCore.csproj b/src/c#/GeneralUpdate.ClientCore/GeneralUpdate.ClientCore.csproj index 9907131b..cf206cb8 100644 --- a/src/c#/GeneralUpdate.ClientCore/GeneralUpdate.ClientCore.csproj +++ b/src/c#/GeneralUpdate.ClientCore/GeneralUpdate.ClientCore.csproj @@ -51,6 +51,7 @@ + @@ -59,11 +60,13 @@ + + @@ -71,10 +74,13 @@ + + + diff --git a/src/c#/GeneralUpdate.ClientCore/PluginBootstrapExtensions.cs b/src/c#/GeneralUpdate.ClientCore/PluginBootstrapExtensions.cs new file mode 100644 index 00000000..31d6b596 --- /dev/null +++ b/src/c#/GeneralUpdate.ClientCore/PluginBootstrapExtensions.cs @@ -0,0 +1,193 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Threading.Tasks; +using GeneralUpdate.Common.Internal; +using GeneralUpdate.Common.Shared.Object; +using GeneralUpdate.Common.Shared.Service; + +namespace GeneralUpdate.ClientCore +{ + /// + /// Extension methods for GeneralClientBootstrap to support plugin upgrades. + /// + public static class PluginBootstrapExtensions + { + /// + /// Validates and retrieves available plugin updates for the current client version. + /// + /// Global configuration information. + /// Platform type. + /// List of available plugin updates compatible with the current client version. + public static async Task> GetAvailablePluginUpdatesAsync( + GlobalConfigInfo configInfo, + int platform) + { + if (configInfo == null || string.IsNullOrEmpty(configInfo.PluginUpdateUrl)) + { + return new List(); + } + + try + { + var availablePlugins = await PluginUpdateService.GetAvailablePlugins( + configInfo.PluginUpdateUrl, + configInfo.ClientVersion, + null, // Check all plugins + configInfo.AppSecretKey, + platform, + configInfo.ProductId, + configInfo.Scheme, + configInfo.Token); + + return availablePlugins; + } + catch (Exception) + { + // Catch any exceptions from API calls or network issues + // Return empty list to allow application to continue + return new List(); + } + } + + /// + /// Automatically determines minimum required plugin versions for a new client version. + /// This is called when the client is being upgraded to ensure plugin compatibility. + /// + /// Global configuration information. + /// Target client version after upgrade. + /// Platform type. + /// Dictionary of currently installed plugins (PluginId -> Version). + /// List of plugins that need to be upgraded. + public static async Task> GetRequiredPluginUpdatesForClientUpgradeAsync( + GlobalConfigInfo configInfo, + string targetClientVersion, + int platform, + Dictionary currentPlugins) + { + if (configInfo == null || string.IsNullOrEmpty(configInfo.PluginUpdateUrl)) + { + return new List(); + } + + try + { + var requiredPlugins = new List(); + + foreach (var currentPlugin in currentPlugins) + { + var minPlugin = await PluginUpdateService.GetMinimumSupportedPluginForClientVersion( + configInfo.PluginUpdateUrl, + targetClientVersion, + currentPlugin.Key, + configInfo.AppSecretKey, + platform, + configInfo.ProductId, + configInfo.Scheme, + configInfo.Token); + + if (minPlugin != null) + { + try + { + var currentVersion = new Version(currentPlugin.Value); + var minVersion = new Version(minPlugin.Version); + + // Only add if current version is below minimum required + if (currentVersion < minVersion) + { + requiredPlugins.Add(minPlugin); + } + } + catch (Exception) + { + // Skip plugins with version comparison errors + } + } + } + + return requiredPlugins; + } + catch (Exception) + { + // Catch any exceptions from API calls or network issues + // Return empty list to allow application to continue + return new List(); + } + } + + /// + /// Filters plugin updates based on user selection. + /// Allows users to choose which plugins to upgrade, similar to VS Code. + /// + /// List of available plugin updates. + /// List of plugin IDs selected by user for upgrade. + /// List of plugins to upgrade based on user selection. + public static List SelectPluginsForUpgrade( + List availablePlugins, + List selectedPluginIds) + { + if (availablePlugins == null || !availablePlugins.Any()) + return new List(); + + if (selectedPluginIds == null || !selectedPluginIds.Any()) + return new List(); + + return availablePlugins + .Where(p => !string.IsNullOrEmpty(p.PluginId) && selectedPluginIds.Contains(p.PluginId)) + .ToList(); + } + + /// + /// Determines mandatory plugin updates that must be installed. + /// + /// List of available plugin updates. + /// List of mandatory plugins. + public static List GetMandatoryPluginUpdates(List availablePlugins) + { + return PluginUpdateService.GetMandatoryPlugins(availablePlugins); + } + + /// + /// Checks if any plugin updates are incompatible with the current client version. + /// + /// Current client version. + /// List of plugins to check. + /// True if all plugins are compatible, false otherwise. + public static bool AreAllPluginsCompatible(string clientVersion, List plugins) + { + if (plugins == null || !plugins.Any()) + return true; + + return plugins.All(p => PluginUpdateService.IsPluginCompatible(clientVersion, p)); + } + + /// + /// Validates that selected plugins are compatible with a target client version. + /// Used before performing client upgrade to prevent compatibility issues. + /// + /// Target client version after upgrade. + /// Plugins selected for installation. + /// True if all selected plugins are compatible with target version. + public static bool ValidatePluginCompatibilityForUpgrade( + string targetClientVersion, + List selectedPlugins) + { + if (string.IsNullOrEmpty(targetClientVersion)) + return false; + + if (selectedPlugins == null || !selectedPlugins.Any()) + return true; + + foreach (var plugin in selectedPlugins) + { + if (!PluginUpdateService.IsPluginCompatible(targetClientVersion, plugin)) + { + return false; + } + } + + return true; + } + } +} diff --git a/src/c#/GeneralUpdate.Common/Internal/JsonContext/PluginRespJsonContext.cs b/src/c#/GeneralUpdate.Common/Internal/JsonContext/PluginRespJsonContext.cs new file mode 100644 index 00000000..063669cd --- /dev/null +++ b/src/c#/GeneralUpdate.Common/Internal/JsonContext/PluginRespJsonContext.cs @@ -0,0 +1,8 @@ +using System.Collections.Generic; +using System.Text.Json.Serialization; +using GeneralUpdate.Common.Shared.Object; + +namespace GeneralUpdate.Common.Internal.JsonContext; + +[JsonSerializable(typeof(PluginRespDTO))] +public partial class PluginRespJsonContext : JsonSerializerContext; diff --git a/src/c#/GeneralUpdate.Common/Shared/Object/Configinfo.cs b/src/c#/GeneralUpdate.Common/Shared/Object/Configinfo.cs index 0f4c41b0..845078cd 100644 --- a/src/c#/GeneralUpdate.Common/Shared/Object/Configinfo.cs +++ b/src/c#/GeneralUpdate.Common/Shared/Object/Configinfo.cs @@ -84,6 +84,11 @@ public class Configinfo /// public string Script { get; set; } + /// + /// Plugin validation API endpoint URL (optional). + /// + public string PluginUpdateUrl { get; set; } + public void Validate() { if (string.IsNullOrWhiteSpace(UpdateUrl) || !Uri.IsWellFormedUriString(UpdateUrl, UriKind.Absolute)) diff --git a/src/c#/GeneralUpdate.Common/Shared/Object/DTO/PluginRespDTO.cs b/src/c#/GeneralUpdate.Common/Shared/Object/DTO/PluginRespDTO.cs new file mode 100644 index 00000000..f9cbfb8a --- /dev/null +++ b/src/c#/GeneralUpdate.Common/Shared/Object/DTO/PluginRespDTO.cs @@ -0,0 +1,8 @@ +using System.Collections.Generic; + +namespace GeneralUpdate.Common.Shared.Object; + +/// +/// Response DTO for plugin validation containing list of available plugin updates. +/// +public class PluginRespDTO : BaseResponseDTO>; diff --git a/src/c#/GeneralUpdate.Common/Shared/Object/Enum/AppType.cs b/src/c#/GeneralUpdate.Common/Shared/Object/Enum/AppType.cs index 18648695..84267496 100644 --- a/src/c#/GeneralUpdate.Common/Shared/Object/Enum/AppType.cs +++ b/src/c#/GeneralUpdate.Common/Shared/Object/Enum/AppType.cs @@ -11,5 +11,10 @@ public class AppType /// upgrade program. /// public const int UpgradeApp = 2; + + /// + /// plugin component. + /// + public const int Plugin = 3; } } \ No newline at end of file diff --git a/src/c#/GeneralUpdate.Common/Shared/Object/Enum/PluginType.cs b/src/c#/GeneralUpdate.Common/Shared/Object/Enum/PluginType.cs new file mode 100644 index 00000000..806bef3d --- /dev/null +++ b/src/c#/GeneralUpdate.Common/Shared/Object/Enum/PluginType.cs @@ -0,0 +1,33 @@ +namespace GeneralUpdate.Common.Shared.Object.Enum +{ + /// + /// Plugin type enumeration for different plugin formats. + /// + public enum PluginType + { + /// + /// JavaScript plugin using embedded script engine. + /// + JavaScript = 1, + + /// + /// Lua plugin using embedded script engine. + /// + Lua = 2, + + /// + /// Python plugin using embedded script engine. + /// + Python = 3, + + /// + /// WebAssembly plugin. + /// + WASM = 4, + + /// + /// External executable program with protocol communication. + /// + ExternalExecutable = 5 + } +} diff --git a/src/c#/GeneralUpdate.Common/Shared/Object/GlobalConfigInfo.cs b/src/c#/GeneralUpdate.Common/Shared/Object/GlobalConfigInfo.cs index a214137a..d81fd7e9 100644 --- a/src/c#/GeneralUpdate.Common/Shared/Object/GlobalConfigInfo.cs +++ b/src/c#/GeneralUpdate.Common/Shared/Object/GlobalConfigInfo.cs @@ -133,4 +133,19 @@ public class GlobalConfigInfo /// Script to grant permissions to a specified file on Linux /// public string Script { get; set; } + + /// + /// Plugin validation API endpoint URL. + /// + public string PluginUpdateUrl { get; set; } + + /// + /// List of plugins available for update. + /// + public List AvailablePlugins { get; set; } = new List(); + + /// + /// List of plugins selected by user for upgrade. + /// + public List SelectedPlugins { get; set; } = new List(); } \ No newline at end of file diff --git a/src/c#/GeneralUpdate.Common/Shared/Object/PluginCompatibilityChecker.cs b/src/c#/GeneralUpdate.Common/Shared/Object/PluginCompatibilityChecker.cs new file mode 100644 index 00000000..fdfb6356 --- /dev/null +++ b/src/c#/GeneralUpdate.Common/Shared/Object/PluginCompatibilityChecker.cs @@ -0,0 +1,118 @@ +using System; +using System.Collections.Generic; +using System.Linq; + +namespace GeneralUpdate.Common.Shared.Object +{ + /// + /// Provides plugin compatibility validation between client and plugin versions. + /// + public static class PluginCompatibilityChecker + { + /// + /// Checks if a plugin is compatible with the current client version. + /// + /// Current client version. + /// Plugin information to validate. + /// True if compatible, false otherwise. + public static bool IsCompatible(string clientVersion, PluginInfo plugin) + { + if (string.IsNullOrEmpty(clientVersion) || plugin == null) + return false; + + if (string.IsNullOrEmpty(plugin.MinClientVersion) && string.IsNullOrEmpty(plugin.MaxClientVersion)) + return true; + + try + { + var currentVersion = new Version(clientVersion); + + if (!string.IsNullOrEmpty(plugin.MinClientVersion)) + { + var minVersion = new Version(plugin.MinClientVersion); + if (currentVersion < minVersion) + return false; + } + + if (!string.IsNullOrEmpty(plugin.MaxClientVersion)) + { + var maxVersion = new Version(plugin.MaxClientVersion); + if (currentVersion > maxVersion) + return false; + } + + return true; + } + catch (Exception) + { + return false; + } + } + + /// + /// Filters a list of plugins to only include compatible ones for the given client version. + /// + /// Current client version. + /// List of plugins to filter. + /// List of compatible plugins. + public static List FilterCompatiblePlugins(string clientVersion, List plugins) + { + if (plugins == null || !plugins.Any()) + return new List(); + + return plugins.Where(p => IsCompatible(clientVersion, p)).ToList(); + } + + /// + /// Gets the minimum supported plugin version for a specific client version range. + /// + /// Current client version. + /// List of available plugin versions. + /// The minimum compatible plugin version, or null if none found. + public static PluginInfo? GetMinimumSupportedPlugin(string clientVersion, List plugins) + { + var compatiblePlugins = FilterCompatiblePlugins(clientVersion, plugins); + if (!compatiblePlugins.Any()) + return null; + + try + { + return compatiblePlugins + .Where(p => !string.IsNullOrEmpty(p.Version)) + .OrderBy(p => new Version(p.Version)) + .FirstOrDefault(); + } + catch (Exception) + { + // If any version is invalid, return null + return null; + } + } + + /// + /// Gets the latest compatible plugin version for the given client version. + /// + /// Current client version. + /// List of available plugin versions. + /// The latest compatible plugin version, or null if none found. + public static PluginInfo? GetLatestCompatiblePlugin(string clientVersion, List plugins) + { + var compatiblePlugins = FilterCompatiblePlugins(clientVersion, plugins); + if (!compatiblePlugins.Any()) + return null; + + try + { + return compatiblePlugins + .Where(p => !string.IsNullOrEmpty(p.Version)) + .OrderByDescending(p => new Version(p.Version)) + .FirstOrDefault(); + } + catch (Exception) + { + // If any version is invalid, return null + return null; + } + } + } +} diff --git a/src/c#/GeneralUpdate.Common/Shared/Object/PluginInfo.cs b/src/c#/GeneralUpdate.Common/Shared/Object/PluginInfo.cs new file mode 100644 index 00000000..8197adf9 --- /dev/null +++ b/src/c#/GeneralUpdate.Common/Shared/Object/PluginInfo.cs @@ -0,0 +1,89 @@ +using System; +using System.Text.Json.Serialization; + +namespace GeneralUpdate.Common.Shared.Object +{ + /// + /// Plugin information including version, type, and compatibility range. + /// + public class PluginInfo + { + /// + /// Unique identifier for the plugin. + /// + [JsonPropertyName("pluginId")] + public string? PluginId { get; set; } + + /// + /// Plugin name. + /// + [JsonPropertyName("name")] + public string? Name { get; set; } + + /// + /// Current version of the plugin. + /// + [JsonPropertyName("version")] + public string? Version { get; set; } + + /// + /// Plugin type (JavaScript, Lua, Python, WASM, ExternalExecutable). + /// + [JsonPropertyName("pluginType")] + public int? PluginType { get; set; } + + /// + /// Minimum client version that supports this plugin. + /// + [JsonPropertyName("minClientVersion")] + public string? MinClientVersion { get; set; } + + /// + /// Maximum client version that supports this plugin. + /// + [JsonPropertyName("maxClientVersion")] + public string? MaxClientVersion { get; set; } + + /// + /// Download URL for the plugin. + /// + [JsonPropertyName("url")] + public string? Url { get; set; } + + /// + /// Hash for integrity verification. + /// + [JsonPropertyName("hash")] + public string? Hash { get; set; } + + /// + /// Plugin release date. + /// + [JsonPropertyName("releaseDate")] + public DateTime? ReleaseDate { get; set; } + + /// + /// File size of the plugin package. + /// + [JsonPropertyName("size")] + public long? Size { get; set; } + + /// + /// Package format (e.g., ZIP). + /// + [JsonPropertyName("format")] + public string? Format { get; set; } + + /// + /// Description of the plugin. + /// + [JsonPropertyName("description")] + public string? Description { get; set; } + + /// + /// Whether the plugin upgrade is mandatory. + /// + [JsonPropertyName("isMandatory")] + public bool? IsMandatory { get; set; } + } +} diff --git a/src/c#/GeneralUpdate.Common/Shared/Service/PluginUpdateService.cs b/src/c#/GeneralUpdate.Common/Shared/Service/PluginUpdateService.cs new file mode 100644 index 00000000..5188c6ce --- /dev/null +++ b/src/c#/GeneralUpdate.Common/Shared/Service/PluginUpdateService.cs @@ -0,0 +1,171 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Threading.Tasks; +using GeneralUpdate.Common.Shared.Object; + +namespace GeneralUpdate.Common.Shared.Service +{ + /// + /// Service for managing plugin updates and compatibility. + /// + public static class PluginUpdateService + { + /// + /// Retrieves available plugin updates from the server. + /// + /// Plugin validation endpoint. + /// Current client version. + /// Optional specific plugin ID to check. + /// Application secret key. + /// Platform type. + /// Product identifier. + /// Authentication scheme. + /// Authentication token. + /// List of available plugin updates. + public static async Task> GetAvailablePlugins( + string httpUrl, + string clientVersion, + string pluginId = null, + string appKey = null, + int platform = 0, + string productId = null, + string scheme = null, + string token = null) + { + try + { + var response = await VersionService.ValidatePlugins( + httpUrl, clientVersion, pluginId, appKey, platform, productId, scheme, token); + + if (response?.Code == 200 && response.Body != null) + { + // Filter only compatible plugins + var compatiblePlugins = PluginCompatibilityChecker.FilterCompatiblePlugins( + clientVersion, response.Body); + return compatiblePlugins; + } + + return new List(); + } + catch (Exception e) + { + GeneralTracer.Error("Failed to retrieve available plugins.", e); + return new List(); + } + } + + /// + /// Gets the minimum supported plugin version for the given client version. + /// This is automatically called when the client upgrades to match plugin compatibility. + /// + /// Plugin validation endpoint. + /// Target client version after upgrade. + /// Plugin ID to check. + /// Application secret key. + /// Platform type. + /// Product identifier. + /// Authentication scheme. + /// Authentication token. + /// Minimum supported plugin version for the client. + public static async Task GetMinimumSupportedPluginForClientVersion( + string httpUrl, + string clientVersion, + string pluginId, + string appKey = null, + int platform = 0, + string productId = null, + string scheme = null, + string token = null) + { + try + { + var availablePlugins = await GetAvailablePlugins( + httpUrl, clientVersion, pluginId, appKey, platform, productId, scheme, token); + + if (!availablePlugins.Any()) + return null; + + return PluginCompatibilityChecker.GetMinimumSupportedPlugin(clientVersion, availablePlugins); + } + catch (Exception e) + { + GeneralTracer.Error($"Failed to get minimum plugin version for client {clientVersion}.", e); + return null; + } + } + + /// + /// Checks if a specific plugin version is compatible with the client version. + /// + /// Current client version. + /// Plugin to validate. + /// True if compatible, false otherwise. + public static bool IsPluginCompatible(string clientVersion, PluginInfo plugin) + { + return PluginCompatibilityChecker.IsCompatible(clientVersion, plugin); + } + + /// + /// Determines which plugins need to be upgraded based on current versions. + /// + /// Dictionary of currently installed plugin versions (PluginId -> Version). + /// List of available plugin updates. + /// List of plugins that should be upgraded. + public static List DeterminePluginUpdates( + Dictionary currentPlugins, + List availablePlugins) + { + var updates = new List(); + + if (currentPlugins == null || !currentPlugins.Any() || availablePlugins == null || !availablePlugins.Any()) + return updates; + + foreach (var availablePlugin in availablePlugins) + { + if (string.IsNullOrEmpty(availablePlugin.PluginId) || string.IsNullOrEmpty(availablePlugin.Version)) + continue; + + // Check if plugin is installed + if (currentPlugins.TryGetValue(availablePlugin.PluginId, out var currentVersion)) + { + try + { + var current = new Version(currentVersion); + var available = new Version(availablePlugin.Version); + + // Add to updates if available version is newer + if (available > current) + { + updates.Add(availablePlugin); + } + } + catch (Exception e) + { + GeneralTracer.Error($"Error comparing plugin versions for {availablePlugin.PluginId}.", e); + } + } + else + { + // Plugin not installed, add to updates + updates.Add(availablePlugin); + } + } + + return updates; + } + + /// + /// Filters mandatory plugins from the available updates. + /// + /// List of plugins to filter. + /// List of mandatory plugins. + public static List GetMandatoryPlugins(List plugins) + { + if (plugins == null || !plugins.Any()) + return new List(); + + return plugins.Where(p => p.IsMandatory == true).ToList(); + } + } +} diff --git a/src/c#/GeneralUpdate.Common/Shared/Service/VersionService.cs b/src/c#/GeneralUpdate.Common/Shared/Service/VersionService.cs index 3bcfd10b..fcc15ebe 100644 --- a/src/c#/GeneralUpdate.Common/Shared/Service/VersionService.cs +++ b/src/c#/GeneralUpdate.Common/Shared/Service/VersionService.cs @@ -72,6 +72,45 @@ public static async Task Validate(string httpUrl return await PostTaskAsync(httpUrl, parameters, VersionRespJsonContext.Default.VersionRespDTO, scheme, token); } + /// + /// Validate and retrieve available plugin updates for the current client version. + /// + /// Plugin validation API endpoint. + /// Current client version. + /// Optional plugin ID to check specific plugin. + /// Application secret key. + /// Platform type (Windows/Linux). + /// Product identifier. + /// Authentication scheme. + /// Authentication token. + /// PluginRespDTO containing available plugin updates. + public static async Task ValidatePlugins(string httpUrl + , string clientVersion + , string pluginId = null + , string appKey = null + , int platform = 0 + , string productId = null + , string scheme = null + , string token = null) + { + var parameters = new Dictionary + { + { "ClientVersion", clientVersion }, + { "Platform", platform } + }; + + if (!string.IsNullOrEmpty(pluginId)) + parameters.Add("PluginId", pluginId); + + if (!string.IsNullOrEmpty(appKey)) + parameters.Add("AppKey", appKey); + + if (!string.IsNullOrEmpty(productId)) + parameters.Add("ProductId", productId); + + return await PostTaskAsync(httpUrl, parameters, PluginRespJsonContext.Default.PluginRespDTO, scheme, token); + } + private static async Task PostTaskAsync(string httpUrl, Dictionary parameters, JsonTypeInfo? typeInfo = null, string scheme = null, string token = null) { try diff --git a/src/c#/GeneralUpdate.Core/GeneralUpdate.Core.csproj b/src/c#/GeneralUpdate.Core/GeneralUpdate.Core.csproj index ac296dd1..42d4a924 100644 --- a/src/c#/GeneralUpdate.Core/GeneralUpdate.Core.csproj +++ b/src/c#/GeneralUpdate.Core/GeneralUpdate.Core.csproj @@ -56,6 +56,7 @@ + @@ -64,11 +65,13 @@ + + @@ -76,6 +79,8 @@ + +