using System.Diagnostics;
using System.IO.Compression;
using System.Reflection;
namespace ReleaseUpdater;
///
/// Фасад для работы с релизами и обновлением.
/// Содержит события жизненного цикла.
///
public static class ReleaseUpdaterFacade
{
///
/// Событие вызывается перед установкой новой версии (после скачивания архива).
///
public static event Action? BeforeInstall;
///
/// Событие вызывается при ошибке обновления.
///
public static event Action? UpdateFailed;
///
/// Событие вызывается, если текущая версия совпадает с требуемой.
///
public static event Action? AlreadyUp;
///
/// Получает список доступных версий из Gitea.
///
public static async Task> GetVersionsAsync(Uri apiUrl, string? token = null)
{
var provider = new ReleaseProvider();
var releases = await provider.GetReleasesAsync(apiUrl, token);
return releases.Select(r => r.TagName).ToList();
}
///
/// Обновление через внешний Updater.exe.
///
/// API github/gitea release
/// Token авторизации
/// Путь для установки приложения
/// Наименование файла приложения
/// Папка для временного хранилища zip архива
/// Путь к updater.exe
/// Тэг с версией / "latest"
/// Маска наименовая ассета обновления. В маске может содержаться {version}
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);
}
}
///
/// Получение текущей версии приложения
///
///
public static string GetCurrentVersion()
{
var entryAssembly = Assembly.GetEntryAssembly();
var attr = entryAssembly?.GetCustomAttribute();
return attr?.InformationalVersion
?? entryAssembly?.GetName().Version?.ToString().Split("+")[0]
?? "unknown";
}
}