153 lines
5.6 KiB
C#
153 lines
5.6 KiB
C#
using System.Diagnostics;
|
||
using System.IO.Compression;
|
||
using System.Reflection;
|
||
|
||
namespace ReleaseUpdater;
|
||
|
||
/// <summary>
|
||
/// Фасад для работы с релизами и обновлением.
|
||
/// Содержит события жизненного цикла.
|
||
/// </summary>
|
||
public static class ReleaseUpdaterFacade
|
||
{
|
||
/// <summary>
|
||
/// Событие вызывается перед установкой новой версии (после скачивания архива).
|
||
/// </summary>
|
||
public static event Action? BeforeInstall;
|
||
|
||
/// <summary>
|
||
/// Событие вызывается при ошибке обновления.
|
||
/// </summary>
|
||
public static event Action<Exception>? UpdateFailed;
|
||
|
||
/// <summary>
|
||
/// Событие вызывается, если текущая версия совпадает с требуемой.
|
||
/// </summary>
|
||
public static event Action<string>? AlreadyUp;
|
||
|
||
/// <summary>
|
||
/// Получает список доступных версий из Gitea.
|
||
/// </summary>
|
||
public static async Task<IReadOnlyList<string>> GetVersionsAsync(Uri apiUrl, string? token = null)
|
||
{
|
||
var provider = new ReleaseProvider();
|
||
var releases = await provider.GetReleasesAsync(apiUrl, token);
|
||
return releases.Select(r => r.TagName).ToList();
|
||
}
|
||
|
||
/// <summary>
|
||
/// Обновление через внешний Updater.exe.
|
||
/// </summary>
|
||
/// <param name="apiUrl">API github/gitea release</param>
|
||
/// <param name="token">Token авторизации</param>
|
||
/// <param name="installPath">Путь для установки приложения</param>
|
||
/// <param name="appExeName">Наименование файла приложения</param>
|
||
/// <param name="tempDirectory">Папка для временного хранилища zip архива</param>
|
||
/// <param name="updaterExePath">Путь к updater.exe</param>
|
||
/// <param name="versionOrLatest">Тэг с версией / "latest"</param>
|
||
/// <param name="assetMask">Маска наименовая ассета обновления. В маске может содержаться {version}</param>
|
||
public static async Task UpdateAsync(
|
||
Uri apiUrl,
|
||
string? token,
|
||
string installPath,
|
||
string appExeName,
|
||
string tempDirectory,
|
||
string updaterExePath,
|
||
string versionOrLatest = "latest",
|
||
string? assetMask = null
|
||
)
|
||
{
|
||
try
|
||
{
|
||
var provider = new ReleaseProvider();
|
||
var release = await provider.FindReleaseAsync(apiUrl, versionOrLatest, token)
|
||
?? throw new Exception("Release not found");
|
||
|
||
|
||
var currentVersion = GetCurrentVersion(); // реализуй сам
|
||
|
||
|
||
if (SemVerService.Compare(release.TagName, currentVersion) > 0)
|
||
{
|
||
AlreadyUp?.Invoke(currentVersion);
|
||
return;
|
||
}
|
||
|
||
// Маска: myapp-{version}.zip
|
||
string? mask = assetMask?.Replace("{version}", release.TagName);
|
||
|
||
var asset = release.Assets.FirstOrDefault(a =>
|
||
mask != null
|
||
? a.Name.Equals(mask, StringComparison.OrdinalIgnoreCase)
|
||
: a.Name.EndsWith(".zip", StringComparison.OrdinalIgnoreCase)
|
||
) ?? throw new Exception("No matching asset found");
|
||
|
||
string tempNumber = $"{Guid.NewGuid():N}";
|
||
var tempUpdaterName = $"updater_{tempNumber}.exe";
|
||
|
||
if (string.IsNullOrWhiteSpace(tempDirectory))
|
||
{
|
||
tempDirectory = Path.GetTempPath();
|
||
}
|
||
else if (!Directory.Exists(tempDirectory))
|
||
{
|
||
Directory.CreateDirectory(tempDirectory);
|
||
}
|
||
|
||
var tempUpdaterPath = Path.Combine(tempDirectory, tempUpdaterName);
|
||
|
||
var downloader = new HttpAssetDownloader();
|
||
var zipPath = await downloader.DownloadAssetAsync(asset.DownloadUrl, token, Path.Combine(tempDirectory, $"updater_{tempNumber}.zip"));
|
||
|
||
|
||
File.Copy(updaterExePath, tempUpdaterPath);
|
||
|
||
if (installPath.EndsWith("\\"))
|
||
{
|
||
installPath = installPath[0..(installPath.Length - 1)];
|
||
}
|
||
|
||
var updaterOptions = new Common.Options()
|
||
{
|
||
ZipPath = zipPath,
|
||
InstallPath = installPath,
|
||
AppExe = appExeName,
|
||
};
|
||
|
||
int pid = Process.GetCurrentProcess().Id;
|
||
updaterOptions.WaitProcess = pid;
|
||
|
||
var args = ArgumentsToolkit.ArgumentsParser.ToArguments(updaterOptions, true);
|
||
|
||
BeforeInstall?.Invoke();
|
||
|
||
var process = Process.Start(new ProcessStartInfo
|
||
{
|
||
FileName = tempUpdaterPath,
|
||
Arguments = args,
|
||
UseShellExecute = true,
|
||
WorkingDirectory = AppDomain.CurrentDomain.BaseDirectory
|
||
});
|
||
|
||
Environment.Exit(0);
|
||
}
|
||
catch (Exception ex)
|
||
{
|
||
UpdateFailed?.Invoke(ex);
|
||
}
|
||
}
|
||
|
||
/// <summary>
|
||
/// Получение текущей версии приложения
|
||
/// </summary>
|
||
/// <returns></returns>
|
||
public static string GetCurrentVersion()
|
||
{
|
||
var entryAssembly = Assembly.GetEntryAssembly();
|
||
var attr = entryAssembly?.GetCustomAttribute<AssemblyInformationalVersionAttribute>();
|
||
return attr?.InformationalVersion
|
||
?? entryAssembly?.GetName().Version?.ToString().Split("+")[0]
|
||
?? "unknown";
|
||
}
|
||
}
|