Files

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);
}
}
}
}