using System; using System.Collections.Generic; using System.Diagnostics; using System.Drawing; using System.Drawing.Drawing2D; using System.IO; using System.Threading.Tasks; using System.Windows.Forms; namespace R0Installer { public class UpdateForm : Form { private Panel progressBar; private Panel progressFill; private Label titleLabel; private Label statusLabel; private Label percentLabel; private Label versionLabel; private string currentVersion; private UpdateManager updateManager; private readonly Color primaryGreen = Color.FromArgb(76, 175, 80); private readonly Color darkBg = Color.FromArgb(18, 18, 18); private readonly Color panelBg = Color.FromArgb(28, 28, 28); private readonly Color textGray = Color.FromArgb(180, 180, 180); private readonly Color textDark = Color.FromArgb(100, 100, 100); private bool isDragging = false; private Point dragStart; private string installPath; private bool updateConfirmed; private string projectId; private string updateTarget; private string projectConfig; private string updateConfig; private string windowTitle; private string[] processesToClose; private string launchAfterUpdate; private string holdFile; public UpdateForm(string version, bool updateConfirmed, string projectId, string updateTarget) { this.currentVersion = version; this.updateConfirmed = updateConfirmed; this.projectId = projectId; this.updateTarget = updateTarget; this.installPath = Path.GetDirectoryName(System.Reflection.Assembly.GetExecutingAssembly().Location); LoadConfig(); InitializeComponent(); SetupUI(); this.Load += UpdateForm_Load; } private void LoadConfig() { projectConfig = ConfigManager.GetProjectConfigSync(projectId); if (projectConfig != null) { updateConfig = ConfigManager.GetUpdateConfig(projectConfig, updateTarget); } if (updateConfig != null) { windowTitle = ConfigManager.ExtractJsonValue(updateConfig, "window_title"); launchAfterUpdate = ConfigManager.ExtractJsonValue(updateConfig, "launch_after_update"); holdFile = ConfigManager.ExtractJsonValue(updateConfig, "hold_file"); string procArray = ConfigManager.ExtractJsonArray(updateConfig, "processes_to_close"); if (!string.IsNullOrEmpty(procArray)) { List procs = ConfigManager.ParseJsonStringArray(procArray); processesToClose = procs.ToArray(); } this.updateManager = new UpdateManager(updateConfig, projectConfig); } else { windowTitle = "更新程序"; processesToClose = new string[0]; launchAfterUpdate = ""; holdFile = ""; string fallbackUpdateJson = "{\"check_api\":\"/v3/update/check?project=" + projectId + "&v={0}\",\"temp_filename_template\":\"update_v{version}.exe\"}"; string fallbackProjectJson = "{}"; this.updateManager = new UpdateManager(fallbackUpdateJson, fallbackProjectJson); } if (string.IsNullOrEmpty(windowTitle)) windowTitle = "更新程序"; if (processesToClose == null) processesToClose = new string[0]; this.updateManager.OnProgress = OnProgressUpdate; this.updateManager.OnStatus = OnStatusUpdate; } private async void UpdateForm_Load(object sender, EventArgs e) { await StartUpdateProcess(); } private void InitializeComponent() { this.SuspendLayout(); this.Text = windowTitle; this.Size = new Size(500, 200); this.StartPosition = FormStartPosition.CenterScreen; this.FormBorderStyle = FormBorderStyle.None; this.BackColor = darkBg; this.DoubleBuffered = true; try { this.Icon = Icon.ExtractAssociatedIcon(System.Reflection.Assembly.GetExecutingAssembly().Location); } catch { } this.Region = CreateRoundedRegion(this.Width, this.Height, 12); this.MouseDown += Form_MouseDown; this.MouseMove += Form_MouseMove; this.MouseUp += Form_MouseUp; this.ResumeLayout(false); } private Region CreateRoundedRegion(int width, int height, int radius) { GraphicsPath path = new GraphicsPath(); path.AddArc(0, 0, radius * 2, radius * 2, 180, 90); path.AddArc(width - radius * 2, 0, radius * 2, radius * 2, 270, 90); path.AddArc(width - radius * 2, height - radius * 2, radius * 2, radius * 2, 0, 90); path.AddArc(0, height - radius * 2, radius * 2, radius * 2, 90, 90); path.CloseAllFigures(); return new Region(path); } private void SetupUI() { Label closeBtn = new Label(); closeBtn.Text = "×"; closeBtn.Font = new Font("Segoe UI", 14, FontStyle.Regular); closeBtn.ForeColor = textGray; closeBtn.Size = new Size(36, 36); closeBtn.Location = new Point(this.Width - 40, 4); closeBtn.TextAlign = ContentAlignment.MiddleCenter; closeBtn.Cursor = Cursors.Hand; closeBtn.Click += CloseBtn_Click; closeBtn.MouseEnter += (s, e) => ((Label)s).ForeColor = Color.White; closeBtn.MouseLeave += (s, e) => ((Label)s).ForeColor = textGray; this.Controls.Add(closeBtn); titleLabel = new Label(); titleLabel.Text = windowTitle.Replace("程序", ""); titleLabel.Font = new Font("Microsoft YaHei", 16, FontStyle.Bold); titleLabel.ForeColor = primaryGreen; titleLabel.AutoSize = true; titleLabel.BackColor = Color.Transparent; titleLabel.Location = new Point((this.Width - 180) / 2, 30); this.Controls.Add(titleLabel); versionLabel = new Label(); versionLabel.Text = "当前版本: " + currentVersion; versionLabel.Font = new Font("Microsoft YaHei", 9, FontStyle.Regular); versionLabel.ForeColor = textGray; versionLabel.AutoSize = true; versionLabel.Location = new Point(40, 75); this.Controls.Add(versionLabel); statusLabel = new Label(); statusLabel.Text = "正在检查更新..."; statusLabel.Font = new Font("Microsoft YaHei", 9, FontStyle.Regular); statusLabel.ForeColor = textGray; statusLabel.AutoSize = false; statusLabel.Size = new Size(300, 25); statusLabel.Location = new Point(40, 110); this.Controls.Add(statusLabel); percentLabel = new Label(); percentLabel.Text = ""; percentLabel.Font = new Font("Microsoft YaHei", 9, FontStyle.Bold); percentLabel.ForeColor = primaryGreen; percentLabel.AutoSize = false; percentLabel.Size = new Size(160, 25); percentLabel.TextAlign = ContentAlignment.MiddleRight; percentLabel.Location = new Point(this.Width - 200, 110); this.Controls.Add(percentLabel); progressBar = new Panel(); progressBar.Size = new Size(this.Width - 80, 8); progressBar.Location = new Point(40, 140); progressBar.BackColor = panelBg; this.Controls.Add(progressBar); progressFill = new Panel(); progressFill.Size = new Size(0, 8); progressFill.Location = new Point(0, 0); progressFill.BackColor = primaryGreen; progressBar.Controls.Add(progressFill); this.Paint += UpdateForm_Paint; } private void UpdateForm_Paint(object sender, PaintEventArgs e) { Graphics g = e.Graphics; g.SmoothingMode = SmoothingMode.AntiAlias; using (Pen pen = new Pen(Color.FromArgb(50, 255, 255, 255), 1)) { Rectangle rect = new Rectangle(0, 0, this.Width - 1, this.Height - 1); using (GraphicsPath path = CreateRoundedPath(rect, 12)) { g.DrawPath(pen, path); } } } private GraphicsPath CreateRoundedPath(Rectangle rect, int radius) { GraphicsPath path = new GraphicsPath(); path.AddArc(rect.X, rect.Y, radius * 2, radius * 2, 180, 90); path.AddArc(rect.Right - radius * 2, rect.Y, radius * 2, radius * 2, 270, 90); path.AddArc(rect.Right - radius * 2, rect.Bottom - radius * 2, radius * 2, radius * 2, 0, 90); path.AddArc(rect.X, rect.Bottom - radius * 2, radius * 2, radius * 2, 90, 90); path.CloseAllFigures(); return path; } #region Window events private void CloseBtn_Click(object sender, EventArgs e) { Application.Exit(); } private void Form_MouseDown(object sender, MouseEventArgs e) { if (e.Button == MouseButtons.Left) { isDragging = true; dragStart = e.Location; } } private void Form_MouseMove(object sender, MouseEventArgs e) { if (isDragging) { Point diff = new Point(e.X - dragStart.X, e.Y - dragStart.Y); this.Location = new Point(this.Location.X + diff.X, this.Location.Y + diff.Y); } } private void Form_MouseUp(object sender, MouseEventArgs e) { isDragging = false; } #endregion #region Progress private void OnProgressUpdate(int percent, string message, string speedText) { if (this.InvokeRequired) { this.Invoke(new Action(OnProgressUpdate), percent, message, speedText); return; } int targetWidth = (int)(progressBar.Width * percent / 100.0); progressFill.Width = targetWidth; if (message != null) statusLabel.Text = message; if (!string.IsNullOrEmpty(speedText)) percentLabel.Text = String.Format("{0}% {1}", percent, speedText); else percentLabel.Text = String.Format("{0}%", percent); } private void OnStatusUpdate(string message) { if (this.InvokeRequired) { this.Invoke(new Action(OnStatusUpdate), message); return; } statusLabel.Text = message; } private void UpdateVersionLabel(string newVersion) { if (this.InvokeRequired) { this.Invoke(new Action(UpdateVersionLabel), newVersion); return; } versionLabel.Text = String.Format("更新: {0} → {1}", currentVersion, newVersion); } #endregion #region Update process private async Task StartUpdateProcess() { try { OnStatusUpdate("正在获取更新信息..."); UpdateCheckResult result = await updateManager.CheckForUpdate(currentVersion); if (result == null) { OnStatusUpdate("获取更新信息失败,请检查网络连接"); Console.WriteLine("ERROR"); await Task.Delay(3000); Application.Exit(); return; } if (!updateConfirmed && !result.has_update) { OnStatusUpdate("当前已是最新版本"); Console.WriteLine("OK"); Application.Exit(); return; } bool hasHoldFile = !string.IsNullOrEmpty(holdFile) && holdFile != "null"; if (hasHoldFile) { CreateHoldFile(); } OnStatusUpdate("正在关闭相关程序..."); CloseProcesses(); await Task.Delay(500); UpdateVersionLabel(result.latest_version); bool success = false; bool isIncrementalUpdate = false; switch (result.update_type) { case "full": OnStatusUpdate("正在准备全量更新..."); success = await updateManager.PerformFullUpdate(result); if (success) { OnProgressUpdate(100, "更新完成,正在启动安装程序...", ""); await Task.Delay(1000); } break; case "incremental": isIncrementalUpdate = true; OnStatusUpdate("正在准备增量更新..."); success = await updateManager.PerformIncrementalUpdate(result.download_url, installPath); if (success) { OnProgressUpdate(100, "更新完成!", ""); await Task.Delay(1000); if (hasHoldFile) DeleteHoldFile(); LaunchMainProgram(); } break; case "multi_incremental": isIncrementalUpdate = true; OnStatusUpdate("正在准备增量更新..."); success = await updateManager.PerformMultiIncrementalUpdate(result.incremental_updates, installPath); if (success) { OnProgressUpdate(100, "更新完成!", ""); await Task.Delay(1000); if (hasHoldFile) DeleteHoldFile(); LaunchMainProgram(); } break; default: OnStatusUpdate("未知的更新类型: " + result.update_type); await Task.Delay(3000); break; } if (!success) { OnStatusUpdate("更新失败,请稍后重试"); if (hasHoldFile && isIncrementalUpdate) DeleteHoldFile(); await Task.Delay(3000); } Application.Exit(); } catch (Exception ex) { OnStatusUpdate("更新出错: " + ex.Message); Debug.WriteLine("更新错误: " + ex.ToString()); Console.WriteLine("ERROR: " + ex.Message); System.Threading.Thread.Sleep(5000); Application.Exit(); } } private void CloseProcesses() { foreach (string name in processesToClose) { try { Process[] processes = Process.GetProcessesByName(name); foreach (Process proc in processes) { try { proc.Kill(); proc.WaitForExit(5000); } catch (Exception ex) { Debug.WriteLine("关闭进程失败: " + ex.Message); } finally { proc.Dispose(); } } } catch (Exception ex) { Debug.WriteLine("获取进程失败: " + ex.Message); } } } private string GetHoldFilePath() { return Path.Combine(Path.GetTempPath(), holdFile); } private void CreateHoldFile() { try { string holdPath = GetHoldFilePath(); File.WriteAllText(holdPath, DateTime.Now.ToString("yyyy-MM-dd HH:mm:ss")); } catch (Exception ex) { Debug.WriteLine("创建 hold 文件失败: " + ex.Message); } } private void DeleteHoldFile() { try { string holdPath = GetHoldFilePath(); if (File.Exists(holdPath)) { File.Delete(holdPath); } } catch (Exception ex) { Debug.WriteLine("删除 hold 文件失败: " + ex.Message); } } private void LaunchMainProgram() { if (string.IsNullOrEmpty(launchAfterUpdate)) return; try { string mainExe = Path.Combine(installPath, launchAfterUpdate); if (File.Exists(mainExe)) { ProcessStartInfo psi = new ProcessStartInfo(); psi.FileName = mainExe; psi.UseShellExecute = true; psi.WorkingDirectory = installPath; Process.Start(psi); } else { Debug.WriteLine("程序未找到: " + mainExe); } } catch (Exception ex) { Debug.WriteLine("启动程序失败: " + ex.Message); } } #endregion } }