Files
OmniNX-Installer-Payload/desktop/OmniNX.DesktopInstaller/Services/GiteaClient.cs
T
niklascfw d573f6cca4 Add a Windows desktop installer for PC-based OmniNX installs.
This lets mass deployments and modchip setups apply the same install/update flow from a PC, with generated deletion lists kept in sync with the payload headers.
2026-09-13 21:10:58 +02:00

101 lines
3.5 KiB
C#

using System.Net.Http.Json;
using System.Text.Json.Serialization;
using OmniNX.DesktopInstaller.Models;
namespace OmniNX.DesktopInstaller.Services;
public sealed class GiteaClient
{
public const string DefaultBaseUrl = "https://git.niklascfw.de";
private readonly HttpClient _http;
private readonly string _baseUrl;
public GiteaClient(HttpClient? http = null, string? baseUrl = null)
{
_http = http ?? new HttpClient();
_http.DefaultRequestHeaders.UserAgent.ParseAdd("OmniNX-Desktop-Installer/1.0");
_baseUrl = (baseUrl ?? DefaultBaseUrl).TrimEnd('/');
}
public async Task<ReleaseInfo> GetLatestReleaseAsync(CancellationToken cancellationToken = default)
{
var url = $"{_baseUrl}/api/v1/repos/OmniNX/OmniNX/releases/latest";
var response = await _http.GetFromJsonAsync<GiteaReleaseDto>(url, cancellationToken)
?? throw new InvalidOperationException("Empty release response from Gitea.");
return MapRelease(response);
}
public async Task<string> DownloadAssetAsync(
ReleaseAsset asset,
string destinationPath,
IProgress<(long downloaded, long total)>? progress = null,
CancellationToken cancellationToken = default)
{
PathUtils.EnsureDirectory(Path.GetDirectoryName(destinationPath)!);
using var response = await _http.GetAsync(asset.DownloadUrl, HttpCompletionOption.ResponseHeadersRead, cancellationToken);
response.EnsureSuccessStatusCode();
var total = response.Content.Headers.ContentLength ?? asset.Size;
await using var input = await response.Content.ReadAsStreamAsync(cancellationToken);
await using var output = File.Create(destinationPath);
var buffer = new byte[81920];
long downloaded = 0;
int read;
while ((read = await input.ReadAsync(buffer, cancellationToken)) > 0)
{
await output.WriteAsync(buffer.AsMemory(0, read), cancellationToken);
downloaded += read;
progress?.Report((downloaded, total));
}
return destinationPath;
}
public ReleaseAsset? FindPackAsset(ReleaseInfo release, OmniNxVariant variant)
{
var prefix = VariantHelper.GetZipAssetPrefix(variant);
return release.Assets.FirstOrDefault(a =>
a.Name.StartsWith(prefix, StringComparison.OrdinalIgnoreCase) &&
a.Name.EndsWith(".zip", StringComparison.OrdinalIgnoreCase));
}
private static ReleaseInfo MapRelease(GiteaReleaseDto dto) => new()
{
TagName = dto.TagName ?? string.Empty,
Name = dto.Name ?? dto.TagName ?? string.Empty,
Assets = dto.Assets?.Select(a => new ReleaseAsset
{
Name = a.Name ?? string.Empty,
DownloadUrl = a.BrowserDownloadUrl ?? string.Empty,
Size = a.Size,
}).ToList() ?? [],
};
private sealed class GiteaReleaseDto
{
[JsonPropertyName("tag_name")]
public string? TagName { get; set; }
[JsonPropertyName("name")]
public string? Name { get; set; }
[JsonPropertyName("assets")]
public List<GiteaAssetDto>? Assets { get; set; }
}
private sealed class GiteaAssetDto
{
[JsonPropertyName("name")]
public string? Name { get; set; }
[JsonPropertyName("browser_download_url")]
public string? BrowserDownloadUrl { get; set; }
[JsonPropertyName("size")]
public long Size { get; set; }
}
}