From e4215ec9931251191fd18545d86fae2373727683 Mon Sep 17 00:00:00 2001 From: JusterZhu Date: Wed, 20 May 2026 22:57:10 +0800 Subject: [PATCH] feat: add embedded ASP.NET LocalUpdateServer for simulate module - Add FrameworkReference Microsoft.AspNetCore.App - LocalUpdateServer: minimal API with three endpoints - GET /Upgrade/Verification - returns version info with local patch URL - POST /Upgrade/Report - accepts update status reports - GET /patch/{filename} - serves patch .zip files - Auto-detect bound port, static file lookup for patches --- src/GeneralUpdate.Tools.csproj | 4 ++ src/Services/LocalUpdateServer.cs | 104 ++++++++++++++++++++++++++++++ 2 files changed, 108 insertions(+) create mode 100644 src/Services/LocalUpdateServer.cs diff --git a/src/GeneralUpdate.Tools.csproj b/src/GeneralUpdate.Tools.csproj index 37a6f8d..b284fb4 100644 --- a/src/GeneralUpdate.Tools.csproj +++ b/src/GeneralUpdate.Tools.csproj @@ -31,4 +31,8 @@ + + + + diff --git a/src/Services/LocalUpdateServer.cs b/src/Services/LocalUpdateServer.cs new file mode 100644 index 0000000..8f0d598 --- /dev/null +++ b/src/Services/LocalUpdateServer.cs @@ -0,0 +1,104 @@ +using System; +using System.Collections.Generic; +using System.IO; +using System.Linq; +using System.Threading.Tasks; +using Microsoft.AspNetCore.Builder; +using Microsoft.AspNetCore.Hosting; +using Microsoft.AspNetCore.Http; +using Microsoft.Extensions.DependencyInjection; +using Microsoft.Extensions.Hosting; + +namespace GeneralUpdate.Tools.Services; + +public class LocalUpdateServer : IAsyncDisposable +{ + private WebApplication? _app; + private int _port; + private Task? _runTask; + + public int Port => _port; + public string BaseUrl => $"http://127.0.0.1:{_port}"; + + public List<(string CurrentVersion, string TargetVersion, string Hash, string ZipPath, int AppType)> Updates { get; } = new(); + + public async Task StartAsync(int port = 5000) + { + var builder = WebApplication.CreateBuilder(); + builder.WebHost.UseUrls($"http://127.0.0.1:{port}"); + + _app = builder.Build(); + + // GET /Upgrade/Verification + _app.MapGet("/Upgrade/Verification", async (HttpContext context) => + { + var q = context.Request.Query; + var currentVer = q["currentVersion"].ToString(); + _ = int.TryParse(q["appType"].ToString(), out var appType); + + var match = Updates.Find(u => u.CurrentVersion == currentVer); + if (match == default) + { + await context.Response.WriteAsJsonAsync(new { code = 204, body = Array.Empty() }); + return; + } + + var body = new[] + { + new + { + name = Path.GetFileName(match.ZipPath), + version = match.TargetVersion, + hash = match.Hash, + url = $"{BaseUrl}/patch/{Uri.EscapeDataString(Path.GetFileName(match.ZipPath))}", + appType = match.AppType, + releaseDate = DateTime.UtcNow.ToString("yyyy-MM-ddTHH:mm:ssZ"), + isForcibly = false + } + }; + await context.Response.WriteAsJsonAsync(new { code = 200, body }); + }); + + // POST /Upgrade/Report + _app.MapPost("/Upgrade/Report", () => Results.Ok(new { code = 200 })); + + // GET /patch/{filename} + _app.MapGet("/patch/{filename}", async (string filename) => + { + var filePath = LocalUpdateServerFiles.TryGet(filename); + if (filePath == null || !File.Exists(filePath)) + return Results.NotFound(); + return Results.File(filePath, "application/zip", filename); + }); + + _runTask = _app.RunAsync(); + // Give Kestrel a moment to bind + await Task.Delay(500); + // Read actual port from addresses + var urls = _app.Urls; + if (urls.Count > 0) + { + var uri = new Uri(urls.First()); + _port = uri.Port; + } + } + + public async ValueTask DisposeAsync() + { + if (_app != null) + { + await _app.StopAsync(); + await _app.DisposeAsync(); + } + if (_runTask != null) + await _runTask; + } +} + +internal static class LocalUpdateServerFiles +{ + private static readonly Dictionary _files = new(); + public static void Register(string filename, string filePath) => _files[filename] = filePath; + public static string? TryGet(string filename) => _files.TryGetValue(filename, out var p) ? p : null; + public static void Clear() => _files.Clear(); +}