6124d6060c
Co-authored-by: Cursor <cursoragent@cursor.com>
233 lines
8.2 KiB
C#
233 lines
8.2 KiB
C#
using System;
|
|
using System.Collections.Generic;
|
|
using System.Diagnostics;
|
|
using System.IO;
|
|
using System.Threading.Tasks;
|
|
|
|
namespace R0Installer
|
|
{
|
|
/// <summary>
|
|
/// 静默更新执行器:不创建任何窗口,直接在后台完成检查/下载/应用更新。
|
|
/// 复用与 UpdateForm 完全相同的配置加载与 UpdateManager 逻辑,
|
|
/// 仅去除界面展示与纯用于观感的等待,因此更新行为与有界面模式一致。
|
|
/// </summary>
|
|
public class SilentUpdater
|
|
{
|
|
private readonly string currentVersion;
|
|
private readonly bool updateConfirmed;
|
|
private readonly string projectId;
|
|
private readonly string updateTarget;
|
|
private readonly string installPath;
|
|
|
|
private UpdateManager updateManager;
|
|
private string projectConfig;
|
|
private string updateConfig;
|
|
|
|
private string[] processesToClose = new string[0];
|
|
private string launchAfterUpdate = "";
|
|
private string holdFile = "";
|
|
|
|
public SilentUpdater(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();
|
|
}
|
|
|
|
/// <summary>同步入口:阻塞当前线程直到静默更新完成,返回是否成功。</summary>
|
|
public bool Run()
|
|
{
|
|
return RunAsync().GetAwaiter().GetResult();
|
|
}
|
|
|
|
private void LoadConfig()
|
|
{
|
|
projectConfig = ConfigManager.GetProjectConfigSync(projectId);
|
|
|
|
if (projectConfig != null)
|
|
updateConfig = ConfigManager.GetUpdateConfig(projectConfig, updateTarget);
|
|
|
|
if (!string.IsNullOrEmpty(updateConfig))
|
|
{
|
|
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))
|
|
processesToClose = ConfigManager.ParseJsonStringArray(procArray).ToArray();
|
|
|
|
updateManager = new UpdateManager(updateConfig, projectConfig);
|
|
}
|
|
else
|
|
{
|
|
string fallbackUpdateJson = "{\"check_api\":\"/v3/update/check?project=" + projectId + "&v={0}\",\"temp_filename_template\":\"update_v{version}.exe\"}";
|
|
updateManager = new UpdateManager(fallbackUpdateJson, "{}");
|
|
}
|
|
|
|
// 静默模式下没有界面,进度/状态仅写入调试输出,便于排查问题。
|
|
updateManager.OnProgress = (percent, message, speed) =>
|
|
Debug.WriteLine(String.Format("[静默更新] {0}% {1} {2}", percent, message, speed));
|
|
updateManager.OnStatus = (message) => Debug.WriteLine("[静默更新] " + message);
|
|
}
|
|
|
|
public async Task<bool> RunAsync()
|
|
{
|
|
bool hasHoldFile = !string.IsNullOrEmpty(holdFile) && holdFile != "null";
|
|
try
|
|
{
|
|
UpdateCheckResult result = await updateManager.CheckForUpdate(currentVersion);
|
|
|
|
if (result == null)
|
|
{
|
|
Console.WriteLine("ERROR");
|
|
return false;
|
|
}
|
|
|
|
if (!updateConfirmed && !result.has_update)
|
|
{
|
|
Console.WriteLine("OK");
|
|
return true;
|
|
}
|
|
|
|
if (hasHoldFile) CreateHoldFile();
|
|
|
|
CloseProcesses();
|
|
// 给系统一点时间释放文件句柄,确保增量补丁能成功覆盖文件。
|
|
await Task.Delay(500);
|
|
|
|
bool success = false;
|
|
|
|
switch (result.update_type)
|
|
{
|
|
case "full":
|
|
// 全量包(Inno Setup)静默安装。
|
|
success = await updateManager.PerformFullUpdate(result, true);
|
|
break;
|
|
|
|
case "incremental":
|
|
success = await updateManager.PerformIncrementalUpdate(result.download_url, installPath);
|
|
break;
|
|
|
|
case "multi_incremental":
|
|
success = await updateManager.PerformMultiIncrementalUpdate(result.incremental_updates, installPath);
|
|
break;
|
|
|
|
default:
|
|
Debug.WriteLine("未知的更新类型: " + result.update_type);
|
|
if (hasHoldFile) DeleteHoldFile();
|
|
Console.WriteLine("ERROR");
|
|
return false;
|
|
}
|
|
|
|
// 无论全量/增量,静默更新由本程序负责全流程,完成后统一清理 hold 文件并拉起主程序。
|
|
if (hasHoldFile) DeleteHoldFile();
|
|
|
|
if (success)
|
|
LaunchMainProgram();
|
|
|
|
Console.WriteLine(success ? "OK" : "ERROR");
|
|
return success;
|
|
}
|
|
catch (Exception ex)
|
|
{
|
|
Debug.WriteLine("静默更新出错: " + ex);
|
|
if (hasHoldFile) DeleteHoldFile();
|
|
Console.WriteLine("ERROR: " + ex.Message);
|
|
return false;
|
|
}
|
|
}
|
|
|
|
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
|
|
{
|
|
File.WriteAllText(GetHoldFilePath(), 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);
|
|
}
|
|
}
|
|
}
|
|
}
|