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