From 43c3a1ae34a33499c073c1e4022451a7f5ac8ff4 Mon Sep 17 00:00:00 2001 From: SALTWOOD <105980161+SALTWOOD@users.noreply.github.com> Date: Sun, 14 Jul 2024 22:13:30 +0800 Subject: [PATCH] =?UTF-8?q?feat:=20=E4=BF=AE=E4=BA=86=E4=B8=80=E5=A0=86=20?= =?UTF-8?q?bug=EF=BC=8C=E7=8E=B0=E5=9C=A8=E8=83=BD=E4=B8=8A=E7=BA=BF?= =?UTF-8?q?=E4=BA=86=20remove:=20=E5=88=A0=E6=8E=89=E4=BA=86=E6=8F=92?= =?UTF-8?q?=E4=BB=B6=E7=B3=BB=E7=BB=9F=E4=BB=80=E4=B9=88=E7=9A=84?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- CSharp-OpenBMCLAPI/Modules/Cluster.cs | 16 +- .../Modules/ClusterRequiredData.cs | 4 +- CSharp-OpenBMCLAPI/Modules/HttpRequest.cs | 1 + .../Modules/HttpServiceProvider.cs | 158 ++++++++---------- .../Modules/Plugin/HttpEventType.cs | 9 - .../Modules/Plugin/PluginAttribute.cs | 8 - .../Modules/Plugin/PluginBase.cs | 43 ----- .../Modules/Plugin/PluginHttpEvent.cs | 21 --- .../Modules/Plugin/PluginManager.cs | 106 ------------ .../Modules/Plugin/ProgramEventType.cs | 10 -- CSharp-OpenBMCLAPI/Program.cs | 42 ----- 11 files changed, 82 insertions(+), 336 deletions(-) delete mode 100644 CSharp-OpenBMCLAPI/Modules/Plugin/HttpEventType.cs delete mode 100644 CSharp-OpenBMCLAPI/Modules/Plugin/PluginAttribute.cs delete mode 100644 CSharp-OpenBMCLAPI/Modules/Plugin/PluginBase.cs delete mode 100644 CSharp-OpenBMCLAPI/Modules/Plugin/PluginHttpEvent.cs delete mode 100644 CSharp-OpenBMCLAPI/Modules/Plugin/PluginManager.cs delete mode 100644 CSharp-OpenBMCLAPI/Modules/Plugin/ProgramEventType.cs diff --git a/CSharp-OpenBMCLAPI/Modules/Cluster.cs b/CSharp-OpenBMCLAPI/Modules/Cluster.cs index d028ba1..5314f71 100644 --- a/CSharp-OpenBMCLAPI/Modules/Cluster.cs +++ b/CSharp-OpenBMCLAPI/Modules/Cluster.cs @@ -1,5 +1,4 @@ -using CSharpOpenBMCLAPI.Modules.Plugin; -using CSharpOpenBMCLAPI.Modules.Storage; +using CSharpOpenBMCLAPI.Modules.Storage; using Microsoft.AspNetCore.Builder; using Microsoft.AspNetCore.Hosting; using Microsoft.AspNetCore.Http; @@ -91,12 +90,10 @@ protected SocketIOClient.SocketIO InitializeSocket() /// public int Start() { - requiredData.PluginManager.TriggerEvent(this, ProgramEventType.ClusterStarted); // 工作进程启动 Logger.Instance.LogSystem($"工作进程 {guid} 已启动"); Task task = AsyncRun(); task.Wait(); - requiredData.PluginManager.TriggerEvent(this, ProgramEventType.ClusterStopped); return task.Result; } @@ -177,7 +174,7 @@ private void InitializeService() WebApplicationBuilder builder = WebApplication.CreateBuilder(); builder.WebHost.UseKestrel(options => { - options.ListenAnyIP(9388, cert != null ? configure => + options.ListenAnyIP(ClusterRequiredData.Config.PORT, cert != null ? configure => { configure.UseHttps(cert); } @@ -186,15 +183,16 @@ private void InitializeService() application = builder.Build(); // 下载路由 - application.MapGet("/download/{hash}", (HttpContext context, string hash) => + application.MapGet("/download/{hash}", async (HttpContext context, string hash) => { - FileAccessInfo fai = HttpServiceProvider.DownloadHash(context, this).Result; + FileAccessInfo fai = await HttpServiceProvider.DownloadHash(context, this, hash); this.counter.Add(fai); - return Task.CompletedTask; }); // 测速路由 - application.MapGet("/measure", (context) => HttpServiceProvider.Measure(context, this)); + application.MapGet("/measure/{size}", async (HttpContext context, int size) => await HttpServiceProvider.Measure(context, this, size)); + + application.MapGet("/api/{name}", async (HttpContext context, string name) => await HttpServiceProvider.Api(context, name, this)); // 因为暂时禁用面板而注释掉 diff --git a/CSharp-OpenBMCLAPI/Modules/ClusterRequiredData.cs b/CSharp-OpenBMCLAPI/Modules/ClusterRequiredData.cs index 43e6e6b..f72f0ae 100644 --- a/CSharp-OpenBMCLAPI/Modules/ClusterRequiredData.cs +++ b/CSharp-OpenBMCLAPI/Modules/ClusterRequiredData.cs @@ -1,5 +1,4 @@ -using CSharpOpenBMCLAPI.Modules.Plugin; -using CSharpOpenBMCLAPI.Modules.Statistician; +using CSharpOpenBMCLAPI.Modules.Statistician; namespace CSharpOpenBMCLAPI.Modules { @@ -9,7 +8,6 @@ public class ClusterRequiredData public Logger Logger { get => Logger.Instance; } public ClusterInfo ClusterInfo { get; set; } public TokenManager Token { get; set; } - public PluginManager PluginManager { get => PluginManager.Instance; } public static DataStatistician DataStatistician { get; set; } = new DataStatistician(); public SemaphoreSlim SemaphoreSlim { get; set; } = new SemaphoreSlim(0); diff --git a/CSharp-OpenBMCLAPI/Modules/HttpRequest.cs b/CSharp-OpenBMCLAPI/Modules/HttpRequest.cs index c9a63e3..a66dc5d 100644 --- a/CSharp-OpenBMCLAPI/Modules/HttpRequest.cs +++ b/CSharp-OpenBMCLAPI/Modules/HttpRequest.cs @@ -10,6 +10,7 @@ static HttpRequest() { client = new HttpClient() { + // BaseAddress = new Uri("http://saltwood.top:9393") // 设置基础地址,根据配置文件中的StagingMode属性判断使用哪个地址 BaseAddress = ClusterRequiredData.Config.StagingMode ? new Uri("https://openbmclapi.staging.bangbang93.com/") : diff --git a/CSharp-OpenBMCLAPI/Modules/HttpServiceProvider.cs b/CSharp-OpenBMCLAPI/Modules/HttpServiceProvider.cs index dab00a7..43223ac 100644 --- a/CSharp-OpenBMCLAPI/Modules/HttpServiceProvider.cs +++ b/CSharp-OpenBMCLAPI/Modules/HttpServiceProvider.cs @@ -1,8 +1,8 @@ -using CSharpOpenBMCLAPI.Modules.Plugin; -using CSharpOpenBMCLAPI.Modules.Storage; +using CSharpOpenBMCLAPI.Modules.Storage; using Microsoft.AspNetCore.Http; using Microsoft.Extensions.Primitives; using Newtonsoft.Json; +using System; namespace CSharpOpenBMCLAPI.Modules { @@ -28,20 +28,20 @@ public static void LogAccess(HttpContext context) /// /// /// - public static async Task Measure(HttpContext context, Cluster cluster) + public static async Task Measure(HttpContext context, Cluster cluster, int size) { - PluginManager.Instance.TriggerHttpEvent(context, HttpEventType.ClientMeasure); - var pairs = Utils.GetQueryStrings(context.Request.Path.Value?.Split('?').Last()); + context.Request.Query.TryGetValue("s", out StringValues s); + context.Request.Query.TryGetValue("e", out StringValues e); bool valid = Utils.CheckSign(context.Request.Path.Value?.Split('?').First() , cluster.requiredData.ClusterInfo.ClusterSecret - , pairs.GetValueOrDefault("s") - , pairs.GetValueOrDefault("e") + , s.FirstOrDefault() + , e.FirstOrDefault() ); if (valid) { context.Response.StatusCode = 200; byte[] buffer = new byte[1024]; - for (int i = 0; i < Convert.ToInt32(context.Request.Path.Value?.Split('/').Last().Split('?').First()); i++) + for (int i = 0; i < size; i++) { for (int j = 0; j < 1024; j++) { @@ -63,108 +63,96 @@ public static async Task Measure(HttpContext context, Cluster cluster) /// /// /// - public static async Task DownloadHash(HttpContext context, Cluster cluster) + public static async Task DownloadHash(HttpContext context, Cluster cluster, string hash) { - PluginManager.Instance.TriggerHttpEvent(context, HttpEventType.ClientDownload); - // 处理用户下载 - FileAccessInfo fai = default; - var pairs = Utils.GetQueryStrings(context.Request.Path.Value?.Split('?').Last()); - string? hash = context.Request.Path.Value?.Split('/').LastOrDefault()?.Split('?').First(); - string? s = pairs.GetValueOrDefault("s"); - string? e = pairs.GetValueOrDefault("e"); + context.Request.Query.TryGetValue("s", out StringValues s); + context.Request.Query.TryGetValue("e", out StringValues e); - bool valid = Utils.CheckSign(hash, cluster.clusterInfo.ClusterSecret, s, e); + bool isValid = Utils.CheckSign(hash, cluster.clusterInfo.ClusterSecret, s.FirstOrDefault(), e.FirstOrDefault()); - if (valid && hash != null && s != null && e != null) + if (!cluster.storage.Exists(Utils.HashToFileName(hash))) { - if (!cluster.storage.Exists(Utils.HashToFileName(hash))) - { - await cluster.FetchFileFromCenter(hash, force: true); - } + LogAccess(context); + context.Response.StatusCode = 404; + } - long from, to; - try - { - if (context.Request.Headers.ContainsKey("range")) - { - // 206 处理部分 - context.Response.StatusCode = 206; - (from, to) = ToRangeByte(context.Request.Headers["range"].FirstOrDefault()?.Split("=").Last().Split("-")); - if (to < from && to != -1) (from, to) = (to, from); - long length = 0; + // 获取文件信息 + using var stream = cluster.storage.ReadFileStream(Utils.HashToFileName(hash)); + long fileSize = cluster.storage.GetFileSize(Utils.HashToFileName(hash)); - using (Stream file = cluster.storage.ReadFileStream(Utils.HashToFileName(hash))) - { - if (to == -1) to = file.Length; + // 检查是否支持断点续传 + var isRangeRequest = context.Request.Headers.ContainsKey("Range"); + if (isRangeRequest) + { + // 解析 Range 头部,获取断点续传的起始位置和结束位置 + var rangeHeader = context.Request.Headers["Range"].ToString(); + var (startByte, endByte) = GetRange(rangeHeader, fileSize); - length = (to - from + 1); - context.Response.Headers["Content-Length"] = length.ToString(); + // 设置响应头部 + context.Response.StatusCode = 206; // Partial Content + context.Response.Headers.Append("Accept-Ranges", "bytes"); + context.Response.Headers.Append("Content-Range", $"bytes {startByte}-{endByte}/{fileSize}"); + // context.Response.Headers.Append("Content-Type", MimeTypesMap.GetMimeType(fullPath)); + context.Response.Headers.Append("Content-Disposition", $"attachment; filename=\"{Path.GetFileName(hash)}\""); - file.Seek(from, SeekOrigin.Begin); - byte[] buffer = new byte[4096]; - for (; file.Position < to;) - { - int count = file.Read(buffer, 0, buffer.Length); - if (file.Position > to && file.Position - count < to) await context.Response.Body.WriteAsync(buffer[..(int)(count - file.Position + to + 1)]); - else if (count != buffer.Length) await context.Response.Body.WriteAsync(buffer[..(count)]); - else await context.Response.Body.WriteAsync(buffer); - } - context.Response.Headers["Content-Range"] = $"{from}-{to}/{file.Length}"; - } + // 计算要读取的字节数 + var totalBytesToRead = endByte - startByte + 1; + using (Stream file = cluster.storage.ReadFileStream(Utils.HashToFileName(hash))) + { + context.Response.Headers["Content-Length"] = totalBytesToRead.ToString(); - context.Response.Headers["x-bmclapi-hash"] = hash; - context.Response.Headers["Accept-Ranges"] = "bytes"; - context.Response.Headers["Content-Type"] = "application/octet-stream"; - context.Response.Headers["Connection"] = "closed"; - fai = new FileAccessInfo - { - hits = 1, - bytes = length - }; - ClusterRequiredData.DataStatistician.DownloadCount(fai); - } - else + file.Seek(startByte, SeekOrigin.Begin); + byte[] buffer = new byte[4096]; + for (; file.Position < endByte;) { - fai = await cluster.storage.HandleRequest(Utils.HashToFileName(hash), context); - ClusterRequiredData.DataStatistician.DownloadCount(fai); + int count = file.Read(buffer, 0, buffer.Length); + if (file.Position > endByte && file.Position - count < endByte) await context.Response.Body.WriteAsync(buffer[..(int)(count - file.Position + endByte + 1)]); + else if (count != buffer.Length) await context.Response.Body.WriteAsync(buffer[..(count)]); + else await context.Response.Body.WriteAsync(buffer); } } - catch (Exception ex) + LogAccess(context); + return new FileAccessInfo { - Logger.Instance.LogError(ex.ExceptionToDetail()); - Logger.Instance.LogError(context.Request.Path); - //Logger.Instance.LogError(ex.StackTrace); - context.Response.StatusCode = 404; - } + hits = 1, + bytes = totalBytesToRead + }; } else { - context.Response.StatusCode = 403; - context.Response.Headers.Remove("Content-Length"); - await context.Response.WriteAsync($"Access to \"{context.Request.Path}\" has been blocked due to your request timeout or invalidity."); + // 设置响应头部 + context.Response.Headers.Append("Accept-Ranges", "bytes"); + context.Response.Headers.Append("Content-Disposition", $"attachment; filename=\"{Path.GetFileName(hash)}\""); + //context.Response.Headers.Append("Content-Type", MimeTypesMap.GetMimeType(fullPath)); + context.Response.Headers.Append("Content-Range", $"bytes {0}-{fileSize - 1}/{fileSize}"); + LogAccess(context); + return await cluster.storage.HandleRequest(Utils.HashToFileName(hash), context); } - LogAccess(context); - return fai; } - private static (long from, long to) ToRangeByte(string[]? rangeHeader) + + private static (long startByte, long endByte) GetRange(string rangeHeader, long fileSize) { - int from, to; - if (string.IsNullOrWhiteSpace(rangeHeader?.FirstOrDefault())) - from = 0; - else - from = Convert.ToInt32(rangeHeader?.FirstOrDefault()); - if (string.IsNullOrWhiteSpace(rangeHeader?.LastOrDefault())) - to = -1; - else - to = Convert.ToInt32(rangeHeader?.LastOrDefault()); - return (from, to); + if (rangeHeader.Length <= 6) return (0, fileSize); + var ranges = rangeHeader[6..].Split("-"); + try + { + if (ranges[1].Length > 0) + { + return (long.Parse(ranges[0]), long.Parse(ranges[1])); + } + } + catch (Exception) + { + return (long.Parse(ranges[0]), fileSize - 1); + } + + return (long.Parse(ranges[0]), fileSize - 1); } public static async Task Api(HttpContext context, string query, Cluster cluster) { - PluginManager.Instance.TriggerHttpEvent(context, HttpEventType.ClientOtherRequest); context.Response.Headers["content-type"] = "application/json"; context.Response.Headers["access-control-allow-origin"] = "*"; context.Response.StatusCode = 200; diff --git a/CSharp-OpenBMCLAPI/Modules/Plugin/HttpEventType.cs b/CSharp-OpenBMCLAPI/Modules/Plugin/HttpEventType.cs deleted file mode 100644 index d2ffc0d..0000000 --- a/CSharp-OpenBMCLAPI/Modules/Plugin/HttpEventType.cs +++ /dev/null @@ -1,9 +0,0 @@ -namespace CSharpOpenBMCLAPI.Modules.Plugin -{ - public enum HttpEventType - { - ClientDownload, - ClientMeasure, - ClientOtherRequest - } -} diff --git a/CSharp-OpenBMCLAPI/Modules/Plugin/PluginAttribute.cs b/CSharp-OpenBMCLAPI/Modules/Plugin/PluginAttribute.cs deleted file mode 100644 index c8d9448..0000000 --- a/CSharp-OpenBMCLAPI/Modules/Plugin/PluginAttribute.cs +++ /dev/null @@ -1,8 +0,0 @@ -namespace CSharpOpenBMCLAPI.Modules.Plugin -{ - [AttributeUsage(AttributeTargets.Class)] - public class PluginAttribute : Attribute - { - public bool Hidden { get; set; } = false; - } -} diff --git a/CSharp-OpenBMCLAPI/Modules/Plugin/PluginBase.cs b/CSharp-OpenBMCLAPI/Modules/Plugin/PluginBase.cs deleted file mode 100644 index 1aa4e83..0000000 --- a/CSharp-OpenBMCLAPI/Modules/Plugin/PluginBase.cs +++ /dev/null @@ -1,43 +0,0 @@ -namespace CSharpOpenBMCLAPI.Modules.Plugin -{ - public abstract class PluginBase - { - public virtual string Name { get; private set; } = "Default"; - public virtual string Description { get; private set; } = "Default plugin implementation"; - - public virtual void OnProgramStarted() - { - - } - - public virtual void OnClusterStarted(Cluster? cluster) - { - - } - - public virtual void OnClusterStopped(Cluster? cluster) - { - - } - - public virtual void OnProgramStopped() - { - - } - - public virtual void RegisterClientEvents() - { - - } - - public virtual void RegisterStorageType() - { - - } - - public override string ToString() - { - return $"<{this.GetType().FullName} Name={this.Name} at 0x{this.GetHashCode().ToString("X").PadLeft(16, '0')}>"; - } - } -} diff --git a/CSharp-OpenBMCLAPI/Modules/Plugin/PluginHttpEvent.cs b/CSharp-OpenBMCLAPI/Modules/Plugin/PluginHttpEvent.cs deleted file mode 100644 index d232211..0000000 --- a/CSharp-OpenBMCLAPI/Modules/Plugin/PluginHttpEvent.cs +++ /dev/null @@ -1,21 +0,0 @@ -using Microsoft.AspNetCore.Http; - -namespace CSharpOpenBMCLAPI.Modules.Plugin -{ - public class PluginHttpEvent - { - public HttpEventType EventType { get; protected set; } - public Action Action { get; protected set; } - - public PluginHttpEvent(Action action, HttpEventType type) - { - this.EventType = type; - this.Action = action; - } - - public void Trigger(HttpEventType eventType, HttpContext context) - { - if (this.EventType == eventType) this.Action.Invoke(context); - } - } -} \ No newline at end of file diff --git a/CSharp-OpenBMCLAPI/Modules/Plugin/PluginManager.cs b/CSharp-OpenBMCLAPI/Modules/Plugin/PluginManager.cs deleted file mode 100644 index 43fbdc8..0000000 --- a/CSharp-OpenBMCLAPI/Modules/Plugin/PluginManager.cs +++ /dev/null @@ -1,106 +0,0 @@ -using Microsoft.AspNetCore.Http; -using System.Diagnostics; - -namespace CSharpOpenBMCLAPI.Modules.Plugin -{ - public class PluginManager - { - private List events = new List(); - private List plugins = new List(); - private static PluginManager _instance = new PluginManager(); - public static PluginManager Instance { get => _instance; } - - /// - /// 私有构造器,保证只有一个实例 - /// - private PluginManager() - { - - } - - public void RegisterPlugin(PluginBase plugin) - { - string? caller = null; - try - { - StackTrace st = new StackTrace(); - StackFrame sf = st.GetFrame(1).ThrowIfNull(); - caller = sf.GetMethod()?.Name; - - plugins.Add(plugin); - - Logger.Instance.LogInfo($"插件 {plugin} 已成功加载!"); - } - catch (InvalidOperationException) - { - Logger.Instance.LogError($"无法执行的操作:在不允许的位置调用了 {nameof(RegisterPlugin)}:{caller}!"); - } - catch (Exception ex) - { - Logger.Instance.LogError($"注册插件 {plugin} 时出现错误(阶段 1,添加插件):", ex.ExceptionToDetail()); - } - } - - public void RegisterHttpEvent(PluginHttpEvent e) - { - string? caller = null; - try - { - StackTrace st = new StackTrace(); - StackFrame sf = st.GetFrame(1).ThrowIfNull(); - caller = sf.GetMethod()?.Name; - - events.Add(e); - } - catch (InvalidOperationException) - { - Logger.Instance.LogError($"无法执行的操作:在不允许的位置调用了 {nameof(RegisterHttpEvent)}:{caller}!"); - } - catch (Exception ex) - { - Logger.Instance.LogError($"注册事件时出现错误:", ex.ExceptionToDetail()); - } - } - - public void RegisterPlugin(Type type) - { - try - { - PluginBase plugin; - - plugin = (Activator.CreateInstance(type) as PluginBase).ThrowIfNull(); - - RegisterPlugin(plugin); - } - catch (Exception ex) - { - Logger.Instance.LogError("注册插件时出现错误(阶段 0,实例化):", ex.ExceptionToDetail()); - } - } - - public void TriggerEvent(object sender, ProgramEventType eventType) - { - Cluster? senderCluster = sender as Cluster; - switch (eventType) - { - case ProgramEventType.ProgramStarted: - plugins.ForEach(p => p.OnProgramStarted()); - break; - case ProgramEventType.ProgramStopped: - plugins.ForEach(p => p.OnProgramStopped()); - break; - case ProgramEventType.ClusterStarted: - plugins.ForEach(p => p.OnClusterStarted(senderCluster)); - break; - case ProgramEventType.ClusterStopped: - plugins.ForEach(p => p.OnClusterStopped(senderCluster)); - break; - } - } - - public void TriggerHttpEvent(HttpContext context, HttpEventType eventType) - { - events.ForEach(e => e.Trigger(eventType, context)); - } - } -} diff --git a/CSharp-OpenBMCLAPI/Modules/Plugin/ProgramEventType.cs b/CSharp-OpenBMCLAPI/Modules/Plugin/ProgramEventType.cs deleted file mode 100644 index 0c24529..0000000 --- a/CSharp-OpenBMCLAPI/Modules/Plugin/ProgramEventType.cs +++ /dev/null @@ -1,10 +0,0 @@ -namespace CSharpOpenBMCLAPI.Modules.Plugin -{ - public enum ProgramEventType - { - ProgramStarted, - ClusterStarted, - ClusterStopped, - ProgramStopped - } -} diff --git a/CSharp-OpenBMCLAPI/Program.cs b/CSharp-OpenBMCLAPI/Program.cs index 3cd9552..8fd681e 100644 --- a/CSharp-OpenBMCLAPI/Program.cs +++ b/CSharp-OpenBMCLAPI/Program.cs @@ -1,5 +1,4 @@ using CSharpOpenBMCLAPI.Modules; -using CSharpOpenBMCLAPI.Modules.Plugin; using CSharpOpenBMCLAPI.Modules.Statistician; using Newtonsoft.Json; using System.Reflection; @@ -22,43 +21,6 @@ static void Main(string[] args) program.WaitForStop(); } - protected void LoadPlugins() - { - string path = Path.Combine(ClusterRequiredData.Config.clusterWorkingDirectory, "plugins"); - - Directory.CreateDirectory(path); - - foreach (var file in Directory.GetFiles(path)) - { - if (!file.EndsWith(".dll")) continue; - try - { - Assembly assembly = Assembly.LoadFrom(file); - foreach (var type in assembly.GetTypes()) - { - Type? parent = type; - while (parent != null) - { - if (parent == typeof(PluginBase)) - { - PluginAttribute? attr = type.GetCustomAttribute(); - if (attr == null || !attr.Hidden) - { - PluginManager.Instance.RegisterPlugin(type); - } - break; - } - parent = parent.BaseType; - } - } - } - catch (Exception ex) - { - Logger.Instance.LogError($"跳过加载插件 {Path.Combine(file)}。加载插件时出现未知错误。\n", Utils.ExceptionToDetail(ex)); - } - } - } - protected Config GetConfig() { const string configFileName = "config.yml"; @@ -99,8 +61,6 @@ protected override int Run(string[] args) const string bsonFile = "totals.bson"; string bsonFilePath = Path.Combine(ClusterRequiredData.Config.clusterWorkingDirectory, bsonFile); ClusterRequiredData.Config = GetConfig(); - LoadPlugins(); - PluginManager.Instance.TriggerEvent(this, ProgramEventType.ProgramStarted); int returns = 0; @@ -136,8 +96,6 @@ protected override int Run(string[] args) Console.CancelKeyPress += (sender, e) => Utils.ExitCluster(cluster).Wait(); cluster.Start(); - - requiredData.PluginManager.TriggerEvent(this, ProgramEventType.ProgramStopped); return returns; } catch (Exception ex)