133 lines
5.2 KiB
C#
133 lines
5.2 KiB
C#
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) };
|
|
}
|
|
|
|
/// <summary>
|
|
/// Odpytuje Native Update API o dostępność nowszej wersji aplikacji.
|
|
/// </summary>
|
|
public async Task<UpdateCheckResult> 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<UpdateCheckResult>(cancellationToken: cancellationToken);
|
|
return result ?? new UpdateCheckResult { UpdateAvailable = false };
|
|
}
|
|
|
|
/// <summary>
|
|
/// Pobiera plik instalacyjny z Pre-signed URL bezpośrednio z Cloudflare R2 i weryfikuje sumę SHA256.
|
|
/// </summary>
|
|
public async Task<string> DownloadAndVerifyAsync(
|
|
UpdateCheckResult update,
|
|
string destinationFilePath,
|
|
IProgress<double>? 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;
|
|
}
|
|
|
|
/// <summary>
|
|
/// Uruchamia zewnętrzny instalator i zamyka bieżącą aplikację.
|
|
/// </summary>
|
|
public static void LaunchInstallerAndExit(string installerPath, string args = "")
|
|
{
|
|
var psi = new ProcessStartInfo
|
|
{
|
|
FileName = installerPath,
|
|
Arguments = args,
|
|
UseShellExecute = true
|
|
};
|
|
|
|
Process.Start(psi);
|
|
Environment.Exit(0);
|
|
}
|
|
}
|
|
|