6124d6060c
Co-authored-by: Cursor <cursoragent@cursor.com>
493 lines
18 KiB
C#
493 lines
18 KiB
C#
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<string> 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<int, string, string>(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<string>(OnStatusUpdate), message); return; }
|
||
statusLabel.Text = message;
|
||
}
|
||
|
||
private void UpdateVersionLabel(string newVersion)
|
||
{
|
||
if (this.InvokeRequired) { this.Invoke(new Action<string>(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
|
||
}
|
||
}
|