From cef9a9d863b8ba6f890882d9be3652944e8aecca Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Sun, 18 Jan 2026 16:41:39 +0000 Subject: [PATCH 1/5] Initial plan From 4d502bd9a5565ee9f04b4c10fe7903bc8e1dc3a9 Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Sun, 18 Jan 2026 16:47:10 +0000 Subject: [PATCH 2/5] Add plugin upgrade models and services Co-authored-by: JusterZhu <11714536+JusterZhu@users.noreply.github.com> --- .../PluginBootstrapExtensions.cs | 195 ++++++++++++++++++ .../JsonContext/PluginRespJsonContext.cs | 8 + .../Shared/Object/Configinfo.cs | 5 + .../Shared/Object/DTO/PluginRespDTO.cs | 8 + .../Shared/Object/Enum/AppType.cs | 5 + .../Shared/Object/Enum/PluginType.cs | 33 +++ .../Shared/Object/GlobalConfigInfo.cs | 15 ++ .../Object/PluginCompatibilityChecker.cs | 102 +++++++++ .../Shared/Object/PluginInfo.cs | 89 ++++++++ .../Shared/Service/PluginUpdateService.cs | 174 ++++++++++++++++ .../Shared/Service/VersionService.cs | 39 ++++ 11 files changed, 673 insertions(+) create mode 100644 src/c#/GeneralUpdate.ClientCore/PluginBootstrapExtensions.cs create mode 100644 src/c#/GeneralUpdate.Common/Internal/JsonContext/PluginRespJsonContext.cs create mode 100644 src/c#/GeneralUpdate.Common/Shared/Object/DTO/PluginRespDTO.cs create mode 100644 src/c#/GeneralUpdate.Common/Shared/Object/Enum/PluginType.cs create mode 100644 src/c#/GeneralUpdate.Common/Shared/Object/PluginCompatibilityChecker.cs create mode 100644 src/c#/GeneralUpdate.Common/Shared/Object/PluginInfo.cs create mode 100644 src/c#/GeneralUpdate.Common/Shared/Service/PluginUpdateService.cs diff --git a/src/c#/GeneralUpdate.ClientCore/PluginBootstrapExtensions.cs b/src/c#/GeneralUpdate.ClientCore/PluginBootstrapExtensions.cs new file mode 100644 index 00000000..62f419d0 --- /dev/null +++ b/src/c#/GeneralUpdate.ClientCore/PluginBootstrapExtensions.cs @@ -0,0 +1,195 @@ +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)) + { + GeneralTracer.Info("Plugin update URL not configured. Skipping plugin validation."); + 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); + + GeneralTracer.Info($"Found {availablePlugins.Count} compatible plugin updates."); + return availablePlugins; + } + catch (Exception e) + { + GeneralTracer.Error("Failed to retrieve plugin updates.", e); + 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 e) + { + GeneralTracer.Error($"Error comparing plugin versions for {currentPlugin.Key}.", e); + } + } + } + + GeneralTracer.Info($"Found {requiredPlugins.Count} plugins requiring upgrade for client version {targetClientVersion}."); + return requiredPlugins; + } + catch (Exception e) + { + GeneralTracer.Error("Failed to determine required plugin updates for client upgrade.", e); + 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)) + { + GeneralTracer.Error($"Plugin {plugin.Name} (v{plugin.Version}) is not compatible with client version {targetClientVersion}."); + 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..24d5c43c --- /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))] +internal 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..c7feebbd 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; } + + /// + /// List of plugins selected by user for upgrade. + /// + public List SelectedPlugins { get; set; } } \ 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..45978d6c --- /dev/null +++ b/src/c#/GeneralUpdate.Common/Shared/Object/PluginCompatibilityChecker.cs @@ -0,0 +1,102 @@ +using System; +using System.Collections.Generic; +using System.Linq; + +namespace GeneralUpdate.Common.Shared.Object +{ + /// + /// Provides plugin compatibility validation between client and plugin versions. + /// + public 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; + + return compatiblePlugins + .Where(p => !string.IsNullOrEmpty(p.Version)) + .OrderBy(p => new Version(p.Version)) + .FirstOrDefault(); + } + + /// + /// 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; + + return compatiblePlugins + .Where(p => !string.IsNullOrEmpty(p.Version)) + .OrderByDescending(p => new Version(p.Version)) + .FirstOrDefault(); + } + } +} 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..2667c12f --- /dev/null +++ b/src/c#/GeneralUpdate.Common/Shared/Service/PluginUpdateService.cs @@ -0,0 +1,174 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Threading.Tasks; +using GeneralUpdate.Common.Internal; +using GeneralUpdate.Common.Shared.Object; + +namespace GeneralUpdate.Common.Shared.Service +{ + /// + /// Service for managing plugin updates and compatibility. + /// + public class PluginUpdateService + { + private 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 From 1ea73967aa23851f11f16e6bb75cde909cec8faf Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Sun, 18 Jan 2026 16:54:17 +0000 Subject: [PATCH 3/5] Fix build by adding plugin files to linked projects Co-authored-by: JusterZhu <11714536+JusterZhu@users.noreply.github.com> --- .../GeneralUpdate.Bowl/GeneralUpdate.Bowl.csproj | 7 ++++++- .../GeneralUpdate.ClientCore.csproj | 6 ++++++ .../PluginBootstrapExtensions.cs | 14 ++++---------- .../Internal/JsonContext/PluginRespJsonContext.cs | 2 +- .../GeneralUpdate.Core/GeneralUpdate.Core.csproj | 5 +++++ 5 files changed, 22 insertions(+), 12 deletions(-) diff --git a/src/c#/GeneralUpdate.Bowl/GeneralUpdate.Bowl.csproj b/src/c#/GeneralUpdate.Bowl/GeneralUpdate.Bowl.csproj index 2b03c3a8..558e8fe6 100644 --- a/src/c#/GeneralUpdate.Bowl/GeneralUpdate.Bowl.csproj +++ b/src/c#/GeneralUpdate.Bowl/GeneralUpdate.Bowl.csproj @@ -52,7 +52,8 @@ - + + @@ -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 index 62f419d0..794fded1 100644 --- a/src/c#/GeneralUpdate.ClientCore/PluginBootstrapExtensions.cs +++ b/src/c#/GeneralUpdate.ClientCore/PluginBootstrapExtensions.cs @@ -25,7 +25,6 @@ public static async Task> GetAvailablePluginUpdatesAsync( { if (configInfo == null || string.IsNullOrEmpty(configInfo.PluginUpdateUrl)) { - GeneralTracer.Info("Plugin update URL not configured. Skipping plugin validation."); return new List(); } @@ -41,12 +40,10 @@ public static async Task> GetAvailablePluginUpdatesAsync( configInfo.Scheme, configInfo.Token); - GeneralTracer.Info($"Found {availablePlugins.Count} compatible plugin updates."); return availablePlugins; } - catch (Exception e) + catch (Exception) { - GeneralTracer.Error("Failed to retrieve plugin updates.", e); return new List(); } } @@ -100,19 +97,17 @@ public static async Task> GetRequiredPluginUpdatesForClientUpgr requiredPlugins.Add(minPlugin); } } - catch (Exception e) + catch (Exception) { - GeneralTracer.Error($"Error comparing plugin versions for {currentPlugin.Key}.", e); + // Skip plugins with version comparison errors } } } - GeneralTracer.Info($"Found {requiredPlugins.Count} plugins requiring upgrade for client version {targetClientVersion}."); return requiredPlugins; } - catch (Exception e) + catch (Exception) { - GeneralTracer.Error("Failed to determine required plugin updates for client upgrade.", e); return new List(); } } @@ -184,7 +179,6 @@ public static bool ValidatePluginCompatibilityForUpgrade( { if (!PluginUpdateService.IsPluginCompatible(targetClientVersion, plugin)) { - GeneralTracer.Error($"Plugin {plugin.Name} (v{plugin.Version}) is not compatible with client version {targetClientVersion}."); return false; } } diff --git a/src/c#/GeneralUpdate.Common/Internal/JsonContext/PluginRespJsonContext.cs b/src/c#/GeneralUpdate.Common/Internal/JsonContext/PluginRespJsonContext.cs index 24d5c43c..063669cd 100644 --- a/src/c#/GeneralUpdate.Common/Internal/JsonContext/PluginRespJsonContext.cs +++ b/src/c#/GeneralUpdate.Common/Internal/JsonContext/PluginRespJsonContext.cs @@ -5,4 +5,4 @@ namespace GeneralUpdate.Common.Internal.JsonContext; [JsonSerializable(typeof(PluginRespDTO))] -internal partial class PluginRespJsonContext : JsonSerializerContext; +public partial class PluginRespJsonContext : JsonSerializerContext; 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 @@ + + From a0b8818053af5440ab5caadb1508d5a9b42a278e Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Sun, 18 Jan 2026 16:57:00 +0000 Subject: [PATCH 4/5] Address code review feedback and add documentation Co-authored-by: JusterZhu <11714536+JusterZhu@users.noreply.github.com> --- PLUGIN_UPGRADE.md | 414 ++++++++++++++++++ .../GeneralUpdate.Bowl.csproj | 2 +- .../Shared/Object/GlobalConfigInfo.cs | 4 +- .../Object/PluginCompatibilityChecker.cs | 34 +- .../Shared/Service/PluginUpdateService.cs | 4 +- 5 files changed, 443 insertions(+), 15 deletions(-) create mode 100644 PLUGIN_UPGRADE.md 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 558e8fe6..016f6e59 100644 --- a/src/c#/GeneralUpdate.Bowl/GeneralUpdate.Bowl.csproj +++ b/src/c#/GeneralUpdate.Bowl/GeneralUpdate.Bowl.csproj @@ -52,7 +52,7 @@ - + diff --git a/src/c#/GeneralUpdate.Common/Shared/Object/GlobalConfigInfo.cs b/src/c#/GeneralUpdate.Common/Shared/Object/GlobalConfigInfo.cs index c7feebbd..d81fd7e9 100644 --- a/src/c#/GeneralUpdate.Common/Shared/Object/GlobalConfigInfo.cs +++ b/src/c#/GeneralUpdate.Common/Shared/Object/GlobalConfigInfo.cs @@ -142,10 +142,10 @@ public class GlobalConfigInfo /// /// List of plugins available for update. /// - public List AvailablePlugins { get; set; } + public List AvailablePlugins { get; set; } = new List(); /// /// List of plugins selected by user for upgrade. /// - public List SelectedPlugins { get; set; } + 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 index 45978d6c..fdfb6356 100644 --- a/src/c#/GeneralUpdate.Common/Shared/Object/PluginCompatibilityChecker.cs +++ b/src/c#/GeneralUpdate.Common/Shared/Object/PluginCompatibilityChecker.cs @@ -7,7 +7,7 @@ namespace GeneralUpdate.Common.Shared.Object /// /// Provides plugin compatibility validation between client and plugin versions. /// - public class PluginCompatibilityChecker + public static class PluginCompatibilityChecker { /// /// Checks if a plugin is compatible with the current client version. @@ -75,10 +75,18 @@ public static List FilterCompatiblePlugins(string clientVersion, Lis if (!compatiblePlugins.Any()) return null; - return compatiblePlugins - .Where(p => !string.IsNullOrEmpty(p.Version)) - .OrderBy(p => new Version(p.Version)) - .FirstOrDefault(); + 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; + } } /// @@ -93,10 +101,18 @@ public static List FilterCompatiblePlugins(string clientVersion, Lis if (!compatiblePlugins.Any()) return null; - return compatiblePlugins - .Where(p => !string.IsNullOrEmpty(p.Version)) - .OrderByDescending(p => new Version(p.Version)) - .FirstOrDefault(); + 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/Service/PluginUpdateService.cs b/src/c#/GeneralUpdate.Common/Shared/Service/PluginUpdateService.cs index 2667c12f..e9032155 100644 --- a/src/c#/GeneralUpdate.Common/Shared/Service/PluginUpdateService.cs +++ b/src/c#/GeneralUpdate.Common/Shared/Service/PluginUpdateService.cs @@ -10,10 +10,8 @@ namespace GeneralUpdate.Common.Shared.Service /// /// Service for managing plugin updates and compatibility. /// - public class PluginUpdateService + public static class PluginUpdateService { - private PluginUpdateService() { } - /// /// Retrieves available plugin updates from the server. /// From 2ed2b4e9ebb7ffe6d3700d78187d66d1adfab995 Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Sun, 18 Jan 2026 17:00:00 +0000 Subject: [PATCH 5/5] Final code review fixes - improve exception handling and using statements Co-authored-by: JusterZhu <11714536+JusterZhu@users.noreply.github.com> --- .../GeneralUpdate.ClientCore/PluginBootstrapExtensions.cs | 6 +++++- .../Shared/Service/PluginUpdateService.cs | 1 - 2 files changed, 5 insertions(+), 2 deletions(-) diff --git a/src/c#/GeneralUpdate.ClientCore/PluginBootstrapExtensions.cs b/src/c#/GeneralUpdate.ClientCore/PluginBootstrapExtensions.cs index 794fded1..31d6b596 100644 --- a/src/c#/GeneralUpdate.ClientCore/PluginBootstrapExtensions.cs +++ b/src/c#/GeneralUpdate.ClientCore/PluginBootstrapExtensions.cs @@ -40,10 +40,12 @@ public static async Task> GetAvailablePluginUpdatesAsync( configInfo.Scheme, configInfo.Token); - return availablePlugins; + return availablePlugins; } catch (Exception) { + // Catch any exceptions from API calls or network issues + // Return empty list to allow application to continue return new List(); } } @@ -108,6 +110,8 @@ public static async Task> GetRequiredPluginUpdatesForClientUpgr } catch (Exception) { + // Catch any exceptions from API calls or network issues + // Return empty list to allow application to continue return new List(); } } diff --git a/src/c#/GeneralUpdate.Common/Shared/Service/PluginUpdateService.cs b/src/c#/GeneralUpdate.Common/Shared/Service/PluginUpdateService.cs index e9032155..5188c6ce 100644 --- a/src/c#/GeneralUpdate.Common/Shared/Service/PluginUpdateService.cs +++ b/src/c#/GeneralUpdate.Common/Shared/Service/PluginUpdateService.cs @@ -2,7 +2,6 @@ using System.Collections.Generic; using System.Linq; using System.Threading.Tasks; -using GeneralUpdate.Common.Internal; using GeneralUpdate.Common.Shared.Object; namespace GeneralUpdate.Common.Shared.Service