diff --git a/Ink Canvas/Helpers/AutoUpdateHelper.cs b/Ink Canvas/Helpers/AutoUpdateHelper.cs index 896c42f5..2cc8864a 100644 --- a/Ink Canvas/Helpers/AutoUpdateHelper.cs +++ b/Ink Canvas/Helpers/AutoUpdateHelper.cs @@ -11,6 +11,7 @@ using System.Windows.Controls; using System.Text; using System.Collections.Generic; using Newtonsoft.Json; +using Newtonsoft.Json.Linq; namespace Ink_Canvas.Helpers { @@ -342,44 +343,92 @@ namespace Ink_Canvas.Helpers } } + // 通过GitHub API获取最新Release信息 + private static async Task<(string version, string downloadUrl, string releaseNotes)> GetLatestGithubRelease(UpdateChannel channel) + { + try + { + string apiUrl = channel == UpdateChannel.Beta + ? "https://api.github.com/repos/InkCanvasForClass/community-beta/releases/latest" + : "https://api.github.com/repos/InkCanvasForClass/community/releases/latest"; + using (var client = new HttpClient()) + { + client.DefaultRequestHeaders.Add("User-Agent", "ICC-CE Auto Updater"); + var response = await client.GetStringAsync(apiUrl); + var json = JObject.Parse(response); + string version = json["tag_name"]?.ToString(); + string releaseNotes = json["body"]?.ToString(); + string downloadUrl = json["assets"]?.First?["browser_download_url"]?.ToString(); + if (!string.IsNullOrEmpty(version) && !string.IsNullOrEmpty(downloadUrl)) + return (version, downloadUrl, releaseNotes); + } + } + catch (Exception ex) + { + LogHelper.WriteLogToFile($"AutoUpdate | GitHub Releases API 获取失败: {ex.Message}", LogHelper.LogType.Warning); + } + return (null, null, null); + } + // 主要的更新检测方法(优先检测延迟,失败时自动切换线路组) - public static async Task<(string remoteVersion, UpdateLineGroup lineGroup)> CheckForUpdates(UpdateChannel channel = UpdateChannel.Release, bool alwaysGetRemote = false) + public static async Task<(string remoteVersion, UpdateLineGroup lineGroup, string releaseNotes, string directDownloadUrl)> CheckForUpdates(UpdateChannel channel = UpdateChannel.Release, bool alwaysGetRemote = false) { try { string localVersion = Assembly.GetExecutingAssembly().GetName().Version.ToString(); LogHelper.WriteLogToFile($"AutoUpdate | 本地版本: {localVersion}"); - LogHelper.WriteLogToFile($"AutoUpdate | 检测通道 {channel} 下最快线路组..."); - - // 获取所有可用线路组(按延迟排序) + LogHelper.WriteLogToFile($"AutoUpdate | 优先通过GitHub Releases API检测..."); + // 1. 优先通过GitHub Releases API获取 + var (apiVersion, apiDownloadUrl, apiReleaseNotes) = await GetLatestGithubRelease(channel); + if (!string.IsNullOrEmpty(apiVersion) && !string.IsNullOrEmpty(apiDownloadUrl)) + { + Version local = new Version(localVersion); + Version remote = new Version(apiVersion.TrimStart('v', 'V')); + if (remote > local || alwaysGetRemote) + { + LogHelper.WriteLogToFile($"AutoUpdate | 通过GitHub Releases API发现新版本: {apiVersion}"); + // 用一个虚拟的UpdateLineGroup表示API直链 + var apiGroup = new UpdateLineGroup + { + GroupName = "GitHub Releases API", + VersionUrl = "", + DownloadUrlFormat = apiDownloadUrl, + LogUrl = "" + }; + return (apiVersion, apiGroup, apiReleaseNotes, apiDownloadUrl); + } + else + { + LogHelper.WriteLogToFile($"AutoUpdate | 当前版本已是最新 (GitHub Releases API)"); + return (null, null, apiReleaseNotes, null); + } + } + // 2. 回退到原有txt方案 + LogHelper.WriteLogToFile($"AutoUpdate | GitHub Releases API获取失败,回退到txt方案..."); var availableGroups = await GetAvailableLineGroupsOrdered(channel); if (availableGroups.Count == 0) { LogHelper.WriteLogToFile("AutoUpdate | 所有线路组均不可用", LogHelper.LogType.Error); - return (null, null); + return (null, null, null, null); } - - // 依次尝试每个线路组,直到成功获取版本信息 foreach (var group in availableGroups) { LogHelper.WriteLogToFile($"AutoUpdate | 尝试使用线路组获取版本信息: {group.GroupName}"); string remoteVersion = await GetRemoteVersion(group.VersionUrl); - if (remoteVersion != null) { LogHelper.WriteLogToFile($"AutoUpdate | 成功从线路组 {group.GroupName} 获取远程版本: {remoteVersion}"); Version local = new Version(localVersion); Version remote = new Version(remoteVersion); - if (remote > local || alwaysGetRemote) { LogHelper.WriteLogToFile($"AutoUpdate | 发现新版本或强制获取: {remoteVersion}"); - return (remoteVersion, group); + return (remoteVersion, group, null, null); } else { LogHelper.WriteLogToFile($"AutoUpdate | 当前版本已是最新"); - return (null, group); + return (null, group, null, null); } } else @@ -387,14 +436,13 @@ namespace Ink_Canvas.Helpers LogHelper.WriteLogToFile($"AutoUpdate | 线路组 {group.GroupName} 获取版本失败,尝试下一个线路组", LogHelper.LogType.Warning); } } - LogHelper.WriteLogToFile("AutoUpdate | 所有线路组均无法获取版本信息", LogHelper.LogType.Error); - return (null, null); + return (null, null, null, null); } catch (Exception ex) { LogHelper.WriteLogToFile($"AutoUpdate | CheckForUpdates错误: {ex.Message}", LogHelper.LogType.Error); - return (null, null); + return (null, null, null, null); } } @@ -1065,7 +1113,7 @@ namespace Ink_Canvas.Helpers LogHelper.WriteLogToFile($"AutoUpdate | 开始修复版本,通道: {channel}"); // 获取远程版本号(自动选择最快线路组,始终下载远程版本) - var (remoteVersion, group) = await CheckForUpdates(channel, true); + var (remoteVersion, group, _, _) = await CheckForUpdates(channel, true); if (string.IsNullOrEmpty(remoteVersion) || group == null) { LogHelper.WriteLogToFile("AutoUpdate | 修复版本时获取远程版本失败", LogHelper.LogType.Error); @@ -1097,6 +1145,37 @@ namespace Ink_Canvas.Helpers } } + // 获取所有GitHub历史版本(Release) + public static async Task> GetAllGithubReleases(UpdateChannel channel = UpdateChannel.Release) + { + var result = new List<(string, string, string)>(); + try + { + string apiUrl = channel == UpdateChannel.Beta + ? "https://api.github.com/repos/InkCanvasForClass/community-beta/releases" + : "https://api.github.com/repos/InkCanvasForClass/community/releases"; + using (var client = new HttpClient()) + { + client.DefaultRequestHeaders.Add("User-Agent", "ICC-CE Auto Updater"); + var response = await client.GetStringAsync(apiUrl); + var arr = JArray.Parse(response); + foreach (var item in arr) + { + string version = item["tag_name"]?.ToString(); + string releaseNotes = item["body"]?.ToString(); + string downloadUrl = item["assets"]?.First?["browser_download_url"]?.ToString(); + if (!string.IsNullOrEmpty(version) && !string.IsNullOrEmpty(downloadUrl)) + result.Add((version, downloadUrl, releaseNotes)); + } + } + } + catch (Exception ex) + { + LogHelper.WriteLogToFile($"AutoUpdate | 获取历史版本失败: {ex.Message}", LogHelper.LogType.Error); + } + return result; + } + // 测试Windows 7 TLS连接的方法 public static async Task TestWindows7TlsConnection() { diff --git a/Ink Canvas/MainWindow.xaml.cs b/Ink Canvas/MainWindow.xaml.cs index 0a65c121..d25d39fc 100644 --- a/Ink Canvas/MainWindow.xaml.cs +++ b/Ink Canvas/MainWindow.xaml.cs @@ -552,7 +552,7 @@ namespace Ink_Canvas { AvailableLatestLineGroup = null; // 使用当前选择的更新通道检查更新 - var (remoteVersion, lineGroup) = await AutoUpdateHelper.CheckForUpdates(Settings.Startup.UpdateChannel); + var (remoteVersion, lineGroup, apiReleaseNotes, directDownloadUrl) = await AutoUpdateHelper.CheckForUpdates(Settings.Startup.UpdateChannel); AvailableLatestVersion = remoteVersion; AvailableLatestLineGroup = lineGroup; diff --git a/Ink Canvas/Windows/HasNewUpdateWindow.xaml b/Ink Canvas/Windows/HasNewUpdateWindow.xaml index c190e9ba..61d8f5d9 100644 --- a/Ink Canvas/Windows/HasNewUpdateWindow.xaml +++ b/Ink Canvas/Windows/HasNewUpdateWindow.xaml @@ -78,6 +78,12 @@ Background="#f8fafc" BorderBrush="#cbd5e1" ToolTip="跳过此版本更新" Visibility="Visible" IsEnabled="True"/> + + + + + + diff --git a/Ink Canvas/Windows/HasNewUpdateWindow.xaml.cs b/Ink Canvas/Windows/HasNewUpdateWindow.xaml.cs index 7548e423..36a1b1d6 100644 --- a/Ink Canvas/Windows/HasNewUpdateWindow.xaml.cs +++ b/Ink Canvas/Windows/HasNewUpdateWindow.xaml.cs @@ -7,6 +7,8 @@ using System.Windows; using System.Windows.Controls; using System.Windows.Interop; using System.Windows.Media; +using System.Threading.Tasks; +using System.IO; namespace Ink_Canvas { @@ -129,16 +131,46 @@ namespace Ink_Canvas LogHelper.WriteLogToFile("AutoUpdate | Update dialog buttons visibility ensured"); } - private void UpdateNowButton_Click(object sender, RoutedEventArgs e) + private async void UpdateNowButton_Click(object sender, RoutedEventArgs e) { LogHelper.WriteLogToFile("AutoUpdate | Update Now button clicked"); - - // 设置结果为立即更新 - Result = UpdateResult.UpdateNow; - - // 关闭窗口,返回到MainWindow处理后续下载和安装流程 - DialogResult = true; - Close(); + // 禁用按钮,显示进度条 + UpdateNowButton.IsEnabled = false; + UpdateLaterButton.IsEnabled = false; + SkipVersionButton.IsEnabled = false; + DownloadProgressPanel.Visibility = Visibility.Visible; + DownloadProgressBar.Value = 0; + DownloadProgressText.Text = "正在准备下载..."; + + // 启动下载 + bool downloadSuccess = false; + try + { + downloadSuccess = await DownloadUpdateAsync(); + } + catch (Exception ex) + { + DownloadProgressText.Text = $"下载失败: {ex.Message}"; + LogHelper.WriteLogToFile($"AutoUpdate | 下载异常: {ex.Message}", LogHelper.LogType.Error); + } + + if (downloadSuccess) + { + DownloadProgressBar.Value = 100; + DownloadProgressText.Text = "下载完成,准备安装..."; + await Task.Delay(800); + // 设置结果为立即更新 + Result = UpdateResult.UpdateNow; + DialogResult = true; + Close(); + } + else + { + DownloadProgressText.Text = "下载失败,请检查网络后重试。"; + UpdateNowButton.IsEnabled = true; + UpdateLaterButton.IsEnabled = true; + SkipVersionButton.IsEnabled = true; + } } private void UpdateLaterButton_Click(object sender, RoutedEventArgs e) @@ -279,5 +311,84 @@ namespace Ink_Canvas } } } + + // 下载更新并实时更新进度条(支持断点续传) + private async Task DownloadUpdateAsync() + { + string version = NewVersion; + string updatesFolderPath = Path.Combine(Path.GetDirectoryName(System.Reflection.Assembly.GetExecutingAssembly().Location), "AutoUpdate"); + if (!Directory.Exists(updatesFolderPath)) + Directory.CreateDirectory(updatesFolderPath); + string zipFilePath = Path.Combine(updatesFolderPath, $"InkCanvasForClass.CE.{version}.zip"); + string tmpFilePath = zipFilePath + ".tmp"; + + // 获取下载链接(此处假设主程序已将下载直链传入,或可通过AutoUpdateHelper获取) + string downloadUrl = null; + if (App.Current.Properties.Contains("UpdateDirectDownloadUrl")) + downloadUrl = App.Current.Properties["UpdateDirectDownloadUrl"] as string; + if (string.IsNullOrEmpty(downloadUrl)) + { + // 兜底:用GitHub主线格式 + downloadUrl = $"https://github.com/InkCanvasForClass/community/releases/download/{version}/InkCanvasForClass.CE.{version}.zip"; + } + + long existingLength = 0; + if (File.Exists(tmpFilePath)) + existingLength = new FileInfo(tmpFilePath).Length; + + try + { + using (var client = new System.Net.Http.HttpClient()) + { + client.Timeout = System.TimeSpan.FromMinutes(10); + client.DefaultRequestHeaders.Add("User-Agent", "ICC-CE Auto Updater"); + if (existingLength > 0) + client.DefaultRequestHeaders.Range = new System.Net.Http.Headers.RangeHeaderValue(existingLength, null); + + using (var response = await client.GetAsync(downloadUrl, System.Net.Http.HttpCompletionOption.ResponseHeadersRead)) + { + response.EnsureSuccessStatusCode(); + var totalBytes = response.Content.Headers.ContentLength.HasValue + ? response.Content.Headers.ContentLength.Value + existingLength + : -1L; + + using (var stream = await response.Content.ReadAsStreamAsync()) + using (var fs = new FileStream(tmpFilePath, FileMode.Append, FileAccess.Write, FileShare.None)) + { + byte[] buffer = new byte[8192]; + long totalRead = existingLength; + int read; + var lastUpdate = DateTime.Now; + while ((read = await stream.ReadAsync(buffer, 0, buffer.Length)) > 0) + { + await fs.WriteAsync(buffer, 0, read); + totalRead += read; + if ((DateTime.Now - lastUpdate).TotalMilliseconds > 200) + { + int percent = totalBytes > 0 ? (int)(totalRead * 100 / totalBytes) : 0; + DownloadProgressBar.Value = percent; + DownloadProgressText.Text = totalBytes > 0 + ? $"已下载 {totalRead / 1024 / 1024.0:F2} MB / {totalBytes / 1024 / 1024.0:F2} MB ({percent}%)" + : $"已下载 {totalRead / 1024 / 1024.0:F2} MB"; + lastUpdate = DateTime.Now; + } + } + DownloadProgressBar.Value = 100; + DownloadProgressText.Text = "下载完成,正在校验..."; + await fs.FlushAsync(); + } + if (File.Exists(zipFilePath)) File.Delete(zipFilePath); + File.Move(tmpFilePath, zipFilePath); + return true; + } + } + } + catch (Exception ex) + { + LogHelper.WriteLogToFile($"AutoUpdate | 断点续传下载失败: {ex.Message}", LogHelper.LogType.Error); + // 不删除 .tmp 文件,便于下次断点续传 + return false; + } + } } } diff --git a/Ink Canvas/Windows/HistoryRollbackWindow.xaml b/Ink Canvas/Windows/HistoryRollbackWindow.xaml new file mode 100644 index 00000000..ce811560 --- /dev/null +++ b/Ink Canvas/Windows/HistoryRollbackWindow.xaml @@ -0,0 +1,24 @@ + + + + + + + + +