- UpdateService: проверка через публичный CDN latest.json (тот же хост, что и установщик — надёжнее поддомена git), fallback на Gitea, лог в %TEMP%\elfisa-update.log - Настройки: кнопка «Проверить обновления» (ручной триггер + видимый результат) - Прайс-лист: убран лимит показа (300 → весь прайс/все совпадения одним запросом) Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
143 lines
6.7 KiB
C#
143 lines
6.7 KiB
C#
using System.Net.Http;
|
||
using System.Text.Json;
|
||
|
||
namespace Elfisa.Core;
|
||
|
||
/// <summary>Результат проверки обновления.</summary>
|
||
public sealed class UpdateInfo
|
||
{
|
||
public bool Available { get; init; }
|
||
public string CurrentVersion { get; init; } = "";
|
||
public string LatestVersion { get; init; } = "";
|
||
public string ReleaseUrl { get; init; } = "";
|
||
public string SetupUrl { get; init; } = "";
|
||
}
|
||
|
||
/// <summary>
|
||
/// Проверка и скачивание обновлений Avalonia-клиента.
|
||
/// Смотрит в Gitea Releases (тот же репозиторий, что и старый WinForms-апдейтер),
|
||
/// установщик тянет с CDN. Никаких токенов — репозиторий и CDN публичны.
|
||
/// </summary>
|
||
public static class UpdateService
|
||
{
|
||
// Первичный источник — публичный CDN (тот же хост, откуда качается установщик;
|
||
// надёжнее приватных API и отдельных поддоменов). Fallback — Gitea Releases.
|
||
private const string LatestJsonUrl = "https://cdn.24pharmdata.ru/latest.json";
|
||
private const string ReleasesLatest =
|
||
"https://git.24pharmdata.ru/api/v1/repos/pharmdata/elfisa-pharmacy/releases/latest";
|
||
|
||
/// <summary>Стабильный URL установщика на CDN (всегда последняя версия).</summary>
|
||
public const string SetupUrl = "https://cdn.24pharmdata.ru/ElfisaPharmacy-Setup.exe";
|
||
|
||
/// <summary>Проверяет наличие новой версии. Возвращает null при офлайне/ошибке (не мешаем работе).</summary>
|
||
public static async Task<UpdateInfo?> CheckAsync(Version current)
|
||
{
|
||
var info = await TryCdnAsync(current).ConfigureAwait(false)
|
||
?? await TryGiteaAsync(current).ConfigureAwait(false);
|
||
Log(info is null
|
||
? $"current={current}: проверка не удалась (нет ответа CDN и Gitea)"
|
||
: $"current={current} latest={info.LatestVersion} available={info.Available}");
|
||
return info;
|
||
}
|
||
|
||
// CDN latest.json: {"version":"2.0.7","setup":"https://cdn.24pharmdata.ru/ElfisaPharmacy-Setup.exe"}
|
||
private static async Task<UpdateInfo?> TryCdnAsync(Version current)
|
||
{
|
||
try
|
||
{
|
||
using var http = new HttpClient { Timeout = TimeSpan.FromSeconds(8) };
|
||
var json = await http.GetStringAsync(LatestJsonUrl).ConfigureAwait(false);
|
||
using var doc = JsonDocument.Parse(json);
|
||
var root = doc.RootElement;
|
||
var ver = root.TryGetProperty("version", out var v) ? v.GetString() : null;
|
||
var latest = ParseVersion(ver);
|
||
if (latest is null) return null;
|
||
var setup = root.TryGetProperty("setup", out var s) ? s.GetString() : null;
|
||
return new UpdateInfo
|
||
{
|
||
Available = latest > current,
|
||
CurrentVersion = current.ToString(),
|
||
LatestVersion = latest.ToString(),
|
||
ReleaseUrl = "",
|
||
SetupUrl = string.IsNullOrWhiteSpace(setup) ? SetupUrl : setup!,
|
||
};
|
||
}
|
||
catch (Exception ex) { Log("CDN latest.json недоступен: " + ex.Message); return null; }
|
||
}
|
||
|
||
private static async Task<UpdateInfo?> TryGiteaAsync(Version current)
|
||
{
|
||
try
|
||
{
|
||
using var http = new HttpClient { Timeout = TimeSpan.FromSeconds(8) };
|
||
http.DefaultRequestHeaders.Add("Accept", "application/json");
|
||
var json = await http.GetStringAsync(ReleasesLatest).ConfigureAwait(false);
|
||
using var doc = JsonDocument.Parse(json);
|
||
var root = doc.RootElement;
|
||
var tag = root.TryGetProperty("tag_name", out var t) ? t.GetString() : null;
|
||
var latest = ParseVersion(tag);
|
||
if (latest is null) return null;
|
||
var htmlUrl = root.TryGetProperty("html_url", out var h) ? h.GetString() : null;
|
||
return new UpdateInfo
|
||
{
|
||
Available = latest > current,
|
||
CurrentVersion = current.ToString(),
|
||
LatestVersion = latest.ToString(),
|
||
ReleaseUrl = htmlUrl ?? "",
|
||
SetupUrl = SetupUrl,
|
||
};
|
||
}
|
||
catch (Exception ex) { Log("Gitea releases/latest недоступен: " + ex.Message); return null; }
|
||
}
|
||
|
||
/// <summary>Лог проверки в %TEMP%\elfisa-update.log — для диагностики на машине клиента.</summary>
|
||
private static void Log(string msg)
|
||
{
|
||
try
|
||
{
|
||
var path = Path.Combine(Path.GetTempPath(), "elfisa-update.log");
|
||
File.AppendAllText(path, $"{DateTime.Now:yyyy-MM-dd HH:mm:ss} {msg}{Environment.NewLine}");
|
||
}
|
||
catch { /* лог не критичен */ }
|
||
}
|
||
|
||
/// <summary>Скачивает установщик с CDN во временный файл. Возвращает путь.</summary>
|
||
public static async Task<string> DownloadSetupAsync(IProgress<double>? progress, CancellationToken ct = default)
|
||
{
|
||
var path = Path.Combine(Path.GetTempPath(), "ElfisaPharmacy-Setup.exe");
|
||
using var http = new HttpClient { Timeout = TimeSpan.FromMinutes(10) };
|
||
using var resp = await http.GetAsync(SetupUrl, HttpCompletionOption.ResponseHeadersRead, ct).ConfigureAwait(false);
|
||
resp.EnsureSuccessStatusCode();
|
||
var total = resp.Content.Headers.ContentLength ?? -1;
|
||
await using var src = await resp.Content.ReadAsStreamAsync(ct).ConfigureAwait(false);
|
||
await using var dst = new FileStream(path, FileMode.Create, FileAccess.Write, FileShare.None);
|
||
var buffer = new byte[81920];
|
||
long readTotal = 0;
|
||
int n;
|
||
while ((n = await src.ReadAsync(buffer, ct).ConfigureAwait(false)) > 0)
|
||
{
|
||
await dst.WriteAsync(buffer.AsMemory(0, n), ct).ConfigureAwait(false);
|
||
readTotal += n;
|
||
if (total > 0) progress?.Report(readTotal * 100.0 / total);
|
||
}
|
||
return path;
|
||
}
|
||
|
||
/// <summary>Разбирает тег вида v2.0.4 / 2.0.4 / 2.0.4-beta в Version.</summary>
|
||
private static Version? ParseVersion(string? tag)
|
||
{
|
||
if (string.IsNullOrWhiteSpace(tag)) return null;
|
||
tag = tag.Trim();
|
||
if (tag.StartsWith("v", StringComparison.OrdinalIgnoreCase)) tag = tag[1..];
|
||
var dash = tag.IndexOf('-');
|
||
if (dash > 0) tag = tag[..dash];
|
||
switch (tag.Split('.').Length)
|
||
{
|
||
case 1: tag += ".0.0.0"; break;
|
||
case 2: tag += ".0.0"; break;
|
||
case 3: tag += ".0"; break;
|
||
}
|
||
return Version.TryParse(tag, out var v) ? v : null;
|
||
}
|
||
}
|