using System.Diagnostics; using System.Net.Http.Json; using System.Security.Cryptography; using System.Text.Json; using TheCr3w.Shared.Crypto; using TheCr3w.Shared.Models; namespace TheCr3w.WindowsUpdate.Sdk; public class UpdateCheckResult { public bool UpdateAvailable { get; set; } public string AppId { get; set; } = string.Empty; public Guid ReleaseId { get; set; } public string LatestVersion { get; set; } = string.Empty; public int VersionCode { get; set; } public string ReleaseChannel { get; set; } = "stable"; public string? ReleaseNotes { get; set; } public bool Mandatory { get; set; } public long FileSize { get; set; } public string Sha256 { get; set; } = string.Empty; public string? SignatureEd25519 { get; set; } public string? DownloadUrl { get; set; } } public class TheCr3wUpdaterOptions { public string ApiBaseUrl { get; set; } = "https://app.thecr3wnet.com"; public string AppId { get; set; } = string.Empty; public string CurrentVersion { get; set; } = "1.0.0"; public int CurrentVersionCode { get; set; } = 1; public string Channel { get; set; } = "stable"; public string Architecture { get; set; } = "x86_64"; public string TrustedPublicKeyHex { get; set; } = string.Empty; } public class TheCr3wUpdater { private readonly TheCr3wUpdaterOptions _options; private readonly HttpClient _httpClient; public TheCr3wUpdater(TheCr3wUpdaterOptions options, HttpClient? httpClient = null) { _options = options ?? throw new ArgumentNullException(nameof(options)); _httpClient = httpClient ?? new HttpClient { BaseAddress = new Uri(options.ApiBaseUrl) }; } /// /// Odpytuje Native Update API o dostępność nowszej wersji aplikacji. /// public async Task CheckForUpdateAsync(CancellationToken cancellationToken = default) { var url = $"/api/v1/updates/check?app_id={Uri.EscapeDataString(_options.AppId)}" + $"¤t_version={Uri.EscapeDataString(_options.CurrentVersion)}" + $"&version_code={_options.CurrentVersionCode}" + $"&platform=windows" + $"&architecture={Uri.EscapeDataString(_options.Architecture)}" + $"&channel={Uri.EscapeDataString(_options.Channel)}"; var response = await _httpClient.GetAsync(url, cancellationToken); response.EnsureSuccessStatusCode(); var result = await response.Content.ReadFromJsonAsync(cancellationToken: cancellationToken); return result ?? new UpdateCheckResult { UpdateAvailable = false }; } /// /// Pobiera plik instalacyjny z Pre-signed URL bezpośrednio z Cloudflare R2 i weryfikuje sumę SHA256. /// public async Task DownloadAndVerifyAsync( UpdateCheckResult update, string destinationFilePath, IProgress? progress = null, CancellationToken cancellationToken = default) { if (string.IsNullOrEmpty(update.DownloadUrl)) throw new InvalidOperationException("Brak adresu URL pobierania."); using var response = await _httpClient.GetAsync(update.DownloadUrl, HttpCompletionOption.ResponseHeadersRead, cancellationToken); response.EnsureSuccessStatusCode(); var totalBytes = response.Content.Headers.ContentLength ?? update.FileSize; await using var contentStream = await response.Content.ReadAsStreamAsync(cancellationToken); await using var fileStream = new FileStream(destinationFilePath, FileMode.Create, FileAccess.Write, FileShare.None, 8192, true); var buffer = new byte[8192]; long totalBytesRead = 0; int bytesRead; while ((bytesRead = await contentStream.ReadAsync(buffer, 0, buffer.Length, cancellationToken)) > 0) { await fileStream.WriteAsync(buffer.AsMemory(0, bytesRead), cancellationToken); totalBytesRead += bytesRead; if (totalBytes > 0 && progress != null) { progress.Report((double)totalBytesRead / totalBytes); } } await fileStream.FlushAsync(cancellationToken); fileStream.Close(); // Weryfikacja sumy kontrolnej SHA256 await using var verifyStream = File.OpenRead(destinationFilePath); var computedSha256 = await Ed25519Service.ComputeSha256Async(verifyStream, cancellationToken); if (!string.Equals(computedSha256, update.Sha256, StringComparison.OrdinalIgnoreCase)) { File.Delete(destinationFilePath); throw new CryptographicException($"Błąd integralności pliku! Wyliczony SHA256: {computedSha256}, Oczekiwany: {update.Sha256}"); } return destinationFilePath; } /// /// Uruchamia zewnętrzny instalator i zamyka bieżącą aplikację. /// public static void LaunchInstallerAndExit(string installerPath, string args = "") { var psi = new ProcessStartInfo { FileName = installerPath, Arguments = args, UseShellExecute = true }; Process.Start(psi); Environment.Exit(0); } }