using System.Net.Http; using System.Net.Http.Json; using System.Text.Json; using System.Text.Json.Serialization; using LanMountainDesktop.Launcher.Models; namespace LanMountainDesktop.Launcher.Services; /// /// 更新检查服务 - 基于 GitHub Release API /// internal sealed class UpdateCheckService { private const string GitHubApiBase = "https://api.github.com"; private readonly string _repoOwner; private readonly string _repoName; private readonly HttpClient _httpClient; public UpdateCheckService(string repoOwner, string repoName) { _repoOwner = repoOwner; _repoName = repoName; _httpClient = new HttpClient(); _httpClient.DefaultRequestHeaders.Add("User-Agent", "LanMountainDesktop-Launcher"); _httpClient.DefaultRequestHeaders.Add("Accept", "application/vnd.github+json"); } /// /// 检查更新 /// public async Task CheckForUpdateAsync( string currentVersion, UpdateChannel channel, CancellationToken cancellationToken = default) { try { var releases = await FetchReleasesAsync(cancellationToken); // 根据频道过滤版本 var filteredReleases = channel == UpdateChannel.Stable ? releases.Where(r => !r.Prerelease).ToList() : releases; // 找到最新版本 var latestRelease = filteredReleases .OrderByDescending(r => ParseVersion(r.TagName)) .FirstOrDefault(); if (latestRelease == null) { return new UpdateCheckResult { HasUpdate = false, CurrentVersion = currentVersion, ErrorMessage = "No releases found" }; } var latestVersion = ParseVersionString(latestRelease.TagName); var current = ParseVersion(currentVersion); var latest = ParseVersion(latestVersion); return new UpdateCheckResult { HasUpdate = latest > current, LatestVersion = latestVersion, CurrentVersion = currentVersion, Release = latestRelease }; } catch (Exception ex) { return new UpdateCheckResult { HasUpdate = false, CurrentVersion = currentVersion, ErrorMessage = ex.Message }; } } /// /// 获取所有 Release /// private async Task> FetchReleasesAsync(CancellationToken cancellationToken) { var url = $"{GitHubApiBase}/repos/{_repoOwner}/{_repoName}/releases"; var response = await _httpClient.GetAsync(url, cancellationToken); response.EnsureSuccessStatusCode(); var json = await response.Content.ReadAsStringAsync(cancellationToken); var releases = JsonSerializer.Deserialize(json, AppJsonContext.Default.ListGitHubRelease); return releases?.Select(r => new ReleaseInfo { TagName = r.TagName ?? "", Name = r.Name ?? "", Prerelease = r.Prerelease, PublishedAt = r.PublishedAt, Body = r.Body, Assets = r.Assets?.Select(a => new ReleaseAsset { Name = a.Name ?? "", BrowserDownloadUrl = a.BrowserDownloadUrl ?? "", Size = a.Size }).ToList() ?? [] }).ToList() ?? []; } /// /// 从 tag 解析版本号 (例如: v1.0.0 -> 1.0.0) /// private static string ParseVersionString(string tag) { return tag.TrimStart('v', 'V'); } /// /// 解析版本号 /// private static Version ParseVersion(string versionString) { var cleaned = ParseVersionString(versionString); return Version.TryParse(cleaned, out var version) ? version : new Version(0, 0, 0); } } // GitHub API 响应模型 internal sealed class GitHubRelease { [JsonPropertyName("tag_name")] public string? TagName { get; set; } [JsonPropertyName("name")] public string? Name { get; set; } [JsonPropertyName("prerelease")] public bool Prerelease { get; set; } [JsonPropertyName("published_at")] public DateTime PublishedAt { get; set; } [JsonPropertyName("body")] public string? Body { get; set; } [JsonPropertyName("assets")] public List? Assets { get; set; } } internal sealed class GitHubAsset { [JsonPropertyName("name")] public string? Name { get; set; } [JsonPropertyName("browser_download_url")] public string? BrowserDownloadUrl { get; set; } [JsonPropertyName("size")] public long Size { get; set; } }