Compare commits
1 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| 7e40d61b9c |
@@ -143,6 +143,18 @@ Wichtig:
|
||||
5. Im Tab `Allgemein` Polling aktivieren und Intervall setzen.
|
||||
6. Mit `Jetzt prüfen` kann sofort ein Poll-Lauf ausgelöst werden; dabei werden alle aktuell verfügbaren Jobs verarbeitet.
|
||||
|
||||
## Updates
|
||||
|
||||
Im Tab `Updates` kann der Agent gegen die Gitea-Release-API nach neuen Versionen suchen. Standardmäßig wird das neueste Release dieses Repositorys abgefragt:
|
||||
|
||||
```text
|
||||
https://gitea.poettker-cloud.de/api/v1/repos/bjoernpoettker/LabelPrintAgent/releases/latest
|
||||
```
|
||||
|
||||
Für private Repositories kann ein Gitea-Access-Token hinterlegt werden. Der Token wird lokal verschlüsselt gespeichert.
|
||||
|
||||
Wenn ein neueres Release gefunden wird, sucht der Agent nach einem ZIP-Asset, bevorzugt mit dem Suffix `-win-x64.zip`. Beim Installieren lädt der Agent das ZIP herunter, entpackt es in ein temporäres Verzeichnis, startet ein lokales Update-Skript, beendet sich selbst, ersetzt die Dateien im Installationsordner und startet anschließend neu.
|
||||
|
||||
## Tray-Status
|
||||
|
||||
Das Tray-Icon zeigt den aktuellen Zustand:
|
||||
|
||||
@@ -2,6 +2,7 @@ using LabelPrintAgent.Backend;
|
||||
using LabelPrintAgent.Configuration;
|
||||
using LabelPrintAgent.Logging;
|
||||
using LabelPrintAgent.Printing;
|
||||
using LabelPrintAgent.Updates;
|
||||
using Serilog;
|
||||
|
||||
namespace LabelPrintAgent.App;
|
||||
@@ -23,10 +24,11 @@ internal static class Program
|
||||
|
||||
var printerService = new PrinterService();
|
||||
var backendClient = new BackendClient(settingsStore);
|
||||
var updateService = new UpdateService(settingsStore);
|
||||
using var backendWorker = new BackendPollingWorker(settingsStore, backendClient, printerService);
|
||||
backendWorker.Start();
|
||||
|
||||
Application.Run(new TrayApplicationContext(settingsStore, printerService, backendWorker));
|
||||
Application.Run(new TrayApplicationContext(settingsStore, printerService, backendWorker, updateService));
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
using LabelPrintAgent.Backend;
|
||||
using LabelPrintAgent.Configuration;
|
||||
using LabelPrintAgent.Printing;
|
||||
using LabelPrintAgent.Updates;
|
||||
using Microsoft.Win32;
|
||||
using Serilog;
|
||||
|
||||
@@ -14,6 +15,8 @@ internal sealed class SettingsForm : Form
|
||||
private readonly SettingsStore _settingsStore;
|
||||
private readonly PrinterService _printerService;
|
||||
private readonly BackendPollingWorker _backendWorker;
|
||||
private readonly UpdateService _updateService;
|
||||
private UpdateCheckResult? _lastUpdateCheck;
|
||||
|
||||
private readonly CheckBox _autoStartCheckBox = new() { Text = "Autostart aktivieren", AutoSize = true };
|
||||
private readonly CheckBox _workerEnabled = new() { Text = "Backend automatisch abfragen", AutoSize = true };
|
||||
@@ -30,6 +33,13 @@ internal sealed class SettingsForm : Form
|
||||
private readonly CheckBox _useServerSentEvents = new() { Text = "Server-Sent Events verwenden", AutoSize = true };
|
||||
private readonly TextBox _eventsPath = new() { Width = 420 };
|
||||
|
||||
private readonly TextBox _releaseApiUrl = new() { Width = 540 };
|
||||
private readonly TextBox _releaseToken = new() { Width = 420, UseSystemPasswordChar = true };
|
||||
private readonly TextBox _currentVersion = new() { Width = 180, ReadOnly = true };
|
||||
private readonly TextBox _latestVersion = new() { Width = 180, ReadOnly = true };
|
||||
private readonly TextBox _updateAsset = new() { Width = 540, ReadOnly = true };
|
||||
private readonly Button _installUpdateButton;
|
||||
|
||||
private readonly ComboBox _printer = new() { Width = 360, DropDownStyle = ComboBoxStyle.DropDownList };
|
||||
private readonly NumericUpDown _labelWidth = new() { Minimum = 1, Maximum = 500, DecimalPlaces = 1, Value = 57, Width = 100 };
|
||||
private readonly NumericUpDown _labelHeight = new() { Minimum = 1, Maximum = 500, DecimalPlaces = 1, Value = 32, Width = 100 };
|
||||
@@ -42,11 +52,18 @@ internal sealed class SettingsForm : Form
|
||||
Dock = DockStyle.Fill
|
||||
};
|
||||
|
||||
public SettingsForm(SettingsStore settingsStore, PrinterService printerService, BackendPollingWorker backendWorker)
|
||||
public SettingsForm(
|
||||
SettingsStore settingsStore,
|
||||
PrinterService printerService,
|
||||
BackendPollingWorker backendWorker,
|
||||
UpdateService updateService)
|
||||
{
|
||||
_settingsStore = settingsStore;
|
||||
_printerService = printerService;
|
||||
_backendWorker = backendWorker;
|
||||
_updateService = updateService;
|
||||
_installUpdateButton = ButtonAt("Update installieren", 340, 265, async (_, _) => await InstallUpdateFromUiAsync());
|
||||
_installUpdateButton.Enabled = false;
|
||||
|
||||
Text = "LabelPrintAgent Einstellungen";
|
||||
Width = 900;
|
||||
@@ -57,6 +74,7 @@ internal sealed class SettingsForm : Form
|
||||
tabs.TabPages.Add(CreateGeneralTab());
|
||||
tabs.TabPages.Add(CreateBackendTab());
|
||||
tabs.TabPages.Add(CreatePrinterTab());
|
||||
tabs.TabPages.Add(CreateUpdatesTab());
|
||||
tabs.TabPages.Add(CreateStatusTab());
|
||||
Controls.Add(tabs);
|
||||
|
||||
@@ -106,6 +124,20 @@ internal sealed class SettingsForm : Form
|
||||
return new TabPage("Drucker") { Controls = { panel } };
|
||||
}
|
||||
|
||||
private TabPage CreateUpdatesTab()
|
||||
{
|
||||
var panel = CreatePaddedPanel();
|
||||
panel.Controls.Add(Row(20, Label("Aktuelle Version", 160), _currentVersion));
|
||||
panel.Controls.Add(Row(60, Label("Neueste Version", 160), _latestVersion));
|
||||
panel.Controls.Add(Row(100, Label("Release API", 160), _releaseApiUrl));
|
||||
panel.Controls.Add(Row(140, Label("Access Token", 160), _releaseToken));
|
||||
panel.Controls.Add(Row(180, Label("Installationsdatei", 160), _updateAsset));
|
||||
panel.Controls.Add(ButtonAt("Speichern", 180, 265, (_, _) => SaveUpdates(showMessage: true)));
|
||||
panel.Controls.Add(ButtonAt("Nach Updates suchen", 180, 315, async (_, _) => await CheckForUpdateFromUiAsync()));
|
||||
panel.Controls.Add(_installUpdateButton);
|
||||
return new TabPage("Updates") { Controls = { panel } };
|
||||
}
|
||||
|
||||
private TabPage CreateStatusTab()
|
||||
{
|
||||
var page = new TabPage("Status");
|
||||
@@ -131,6 +163,14 @@ internal sealed class SettingsForm : Form
|
||||
_useServerSentEvents.Checked = settings.Backend.UseServerSentEvents;
|
||||
_eventsPath.Text = settings.Backend.EventsPath;
|
||||
|
||||
_releaseApiUrl.Text = settings.Updates.ReleaseApiUrl;
|
||||
_releaseToken.Text = _settingsStore.DecryptUpdateAccessToken(settings);
|
||||
_currentVersion.Text = _updateService.CurrentVersion.ToString();
|
||||
_latestVersion.Text = string.Empty;
|
||||
_updateAsset.Text = string.Empty;
|
||||
_installUpdateButton.Enabled = false;
|
||||
_lastUpdateCheck = null;
|
||||
|
||||
_labelWidth.Value = settings.Printer.LabelWidthMm;
|
||||
_labelHeight.Value = settings.Printer.LabelHeightMm;
|
||||
}
|
||||
@@ -199,6 +239,19 @@ internal sealed class SettingsForm : Form
|
||||
}
|
||||
}
|
||||
|
||||
private void SaveUpdates(bool showMessage)
|
||||
{
|
||||
var settings = _settingsStore.Load();
|
||||
settings.Updates.ReleaseApiUrl = _releaseApiUrl.Text.Trim();
|
||||
settings.Updates.EncryptedAccessToken = _settingsStore.EncryptPassword(_releaseToken.Text.Trim());
|
||||
_settingsStore.Save(settings);
|
||||
AppendStatus("Update-Einstellungen gespeichert.");
|
||||
if (showMessage)
|
||||
{
|
||||
MessageBox.Show("Update-Einstellungen gespeichert.", Text, MessageBoxButtons.OK, MessageBoxIcon.Information);
|
||||
}
|
||||
}
|
||||
|
||||
private async Task PollOnceFromUiAsync()
|
||||
{
|
||||
try
|
||||
@@ -217,6 +270,83 @@ internal sealed class SettingsForm : Form
|
||||
}
|
||||
}
|
||||
|
||||
private async Task CheckForUpdateFromUiAsync()
|
||||
{
|
||||
try
|
||||
{
|
||||
SaveUpdates(showMessage: false);
|
||||
_installUpdateButton.Enabled = false;
|
||||
_lastUpdateCheck = null;
|
||||
_latestVersion.Text = string.Empty;
|
||||
_updateAsset.Text = string.Empty;
|
||||
|
||||
AppendStatus("Suche nach neuen Releases...");
|
||||
var result = await _updateService.CheckForUpdateAsync();
|
||||
_lastUpdateCheck = result;
|
||||
_currentVersion.Text = result.CurrentVersion.ToString();
|
||||
_latestVersion.Text = result.Release.TagName;
|
||||
_updateAsset.Text = result.InstallAsset?.Name ?? "Kein ZIP-Asset gefunden";
|
||||
|
||||
if (!result.IsUpdateAvailable)
|
||||
{
|
||||
AppendStatus($"Kein Update verfügbar. Installiert: {result.CurrentVersion}, Release: {result.Release.TagName}.");
|
||||
MessageBox.Show("Es ist kein Update verfügbar.", Text, MessageBoxButtons.OK, MessageBoxIcon.Information);
|
||||
return;
|
||||
}
|
||||
|
||||
if (result.InstallAsset is null)
|
||||
{
|
||||
AppendStatus($"Update {result.Release.TagName} gefunden, aber kein ZIP-Asset zum Installieren.");
|
||||
MessageBox.Show("Update gefunden, aber das Release enthält kein ZIP-Asset.", Text, MessageBoxButtons.OK, MessageBoxIcon.Warning);
|
||||
return;
|
||||
}
|
||||
|
||||
_installUpdateButton.Enabled = true;
|
||||
AppendStatus($"Update {result.Release.TagName} gefunden: {result.InstallAsset.Name}");
|
||||
MessageBox.Show($"Update {result.Release.TagName} ist verfügbar.", Text, MessageBoxButtons.OK, MessageBoxIcon.Information);
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
Log.Error(ex, "Update check failed");
|
||||
AppendStatus($"Update-Prüfung fehlgeschlagen: {ex.Message}");
|
||||
MessageBox.Show($"Update-Prüfung fehlgeschlagen: {ex.Message}", Text, MessageBoxButtons.OK, MessageBoxIcon.Error);
|
||||
}
|
||||
}
|
||||
|
||||
private async Task InstallUpdateFromUiAsync()
|
||||
{
|
||||
if (_lastUpdateCheck is null || _lastUpdateCheck.InstallAsset is null)
|
||||
{
|
||||
MessageBox.Show("Bitte zuerst nach Updates suchen.", Text, MessageBoxButtons.OK, MessageBoxIcon.Information);
|
||||
return;
|
||||
}
|
||||
|
||||
var confirmation = MessageBox.Show(
|
||||
$"Update {_lastUpdateCheck.Release.TagName} installieren? Die Anwendung wird danach neu gestartet.",
|
||||
Text,
|
||||
MessageBoxButtons.YesNo,
|
||||
MessageBoxIcon.Question);
|
||||
|
||||
if (confirmation != DialogResult.Yes)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
try
|
||||
{
|
||||
AppendStatus($"Update {_lastUpdateCheck.Release.TagName} wird heruntergeladen...");
|
||||
await _updateService.InstallUpdateAsync(_lastUpdateCheck);
|
||||
AppendStatus("Update-Skript gestartet. Anwendung wird beendet.");
|
||||
Application.Exit();
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
Log.Error(ex, "Update installation failed");
|
||||
AppendStatus($"Update-Installation fehlgeschlagen: {ex.Message}");
|
||||
MessageBox.Show($"Update-Installation fehlgeschlagen: {ex.Message}", Text, MessageBoxButtons.OK, MessageBoxIcon.Error);
|
||||
}
|
||||
}
|
||||
|
||||
private string? GetSelectedPrinterName()
|
||||
{
|
||||
return _printer.SelectedItem switch
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
using LabelPrintAgent.Backend;
|
||||
using LabelPrintAgent.Configuration;
|
||||
using LabelPrintAgent.Printing;
|
||||
using LabelPrintAgent.Updates;
|
||||
using Serilog;
|
||||
|
||||
namespace LabelPrintAgent.App;
|
||||
@@ -11,6 +12,7 @@ internal sealed class TrayApplicationContext : ApplicationContext
|
||||
private readonly SettingsStore _settingsStore;
|
||||
private readonly PrinterService _printerService;
|
||||
private readonly BackendPollingWorker _backendWorker;
|
||||
private readonly UpdateService _updateService;
|
||||
private readonly Icon _healthyIcon;
|
||||
private readonly Icon _unhealthyIcon;
|
||||
private readonly System.Windows.Forms.Timer _statusTimer;
|
||||
@@ -19,11 +21,13 @@ internal sealed class TrayApplicationContext : ApplicationContext
|
||||
public TrayApplicationContext(
|
||||
SettingsStore settingsStore,
|
||||
PrinterService printerService,
|
||||
BackendPollingWorker backendWorker)
|
||||
BackendPollingWorker backendWorker,
|
||||
UpdateService updateService)
|
||||
{
|
||||
_settingsStore = settingsStore;
|
||||
_printerService = printerService;
|
||||
_backendWorker = backendWorker;
|
||||
_updateService = updateService;
|
||||
_healthyIcon = TrayIconFactory.CreatePrinterIcon(Color.FromArgb(36, 160, 72));
|
||||
_unhealthyIcon = TrayIconFactory.CreatePrinterIcon(Color.FromArgb(210, 48, 48));
|
||||
|
||||
@@ -58,7 +62,7 @@ internal sealed class TrayApplicationContext : ApplicationContext
|
||||
{
|
||||
if (_settingsForm is null || _settingsForm.IsDisposed)
|
||||
{
|
||||
_settingsForm = new SettingsForm(_settingsStore, _printerService, _backendWorker);
|
||||
_settingsForm = new SettingsForm(_settingsStore, _printerService, _backendWorker, _updateService);
|
||||
}
|
||||
|
||||
_settingsForm.Show();
|
||||
|
||||
@@ -3,6 +3,7 @@ namespace LabelPrintAgent.Configuration;
|
||||
public sealed class AppSettings
|
||||
{
|
||||
public BackendSettings Backend { get; set; } = new();
|
||||
public UpdateSettings Updates { get; set; } = new();
|
||||
public PrinterSettings Printer { get; set; } = new();
|
||||
public WorkerSettings Worker { get; set; } = new();
|
||||
public PathSettings Paths { get; set; } = PathSettings.CreateDefault();
|
||||
@@ -27,6 +28,13 @@ public sealed class BackendSettings
|
||||
public string EventsPath { get; set; } = "/api/label-print-agent/events";
|
||||
}
|
||||
|
||||
public sealed class UpdateSettings
|
||||
{
|
||||
public string ReleaseApiUrl { get; set; } =
|
||||
"https://gitea.poettker-cloud.de/api/v1/repos/bjoernpoettker/LabelPrintAgent/releases/latest";
|
||||
public string EncryptedAccessToken { get; set; } = string.Empty;
|
||||
}
|
||||
|
||||
public sealed class PrinterSettings
|
||||
{
|
||||
public string PrinterName { get; set; } = string.Empty;
|
||||
|
||||
@@ -38,6 +38,10 @@ public sealed class SettingsStore
|
||||
var json = File.ReadAllText(SettingsFilePath);
|
||||
var settings = JsonSerializer.Deserialize<AppSettings>(json, JsonOptions) ?? new AppSettings();
|
||||
settings.Paths ??= PathSettings.CreateDefault();
|
||||
settings.Backend ??= new BackendSettings();
|
||||
settings.Updates ??= new UpdateSettings();
|
||||
settings.Printer ??= new PrinterSettings();
|
||||
settings.Worker ??= new WorkerSettings();
|
||||
return settings;
|
||||
}
|
||||
catch (Exception ex)
|
||||
@@ -58,6 +62,8 @@ public sealed class SettingsStore
|
||||
|
||||
public string DecryptBackendApiToken(AppSettings settings) => _protectedStringService.Unprotect(settings.Backend.EncryptedApiToken);
|
||||
|
||||
public string DecryptUpdateAccessToken(AppSettings settings) => _protectedStringService.Unprotect(settings.Updates.EncryptedAccessToken);
|
||||
|
||||
public static string NormalizeBackendApiToken(string token)
|
||||
{
|
||||
if (string.IsNullOrWhiteSpace(token))
|
||||
|
||||
@@ -0,0 +1,3 @@
|
||||
namespace LabelPrintAgent.Updates;
|
||||
|
||||
internal sealed record ReleaseAsset(string Name, string DownloadUrl, long Size);
|
||||
@@ -0,0 +1,16 @@
|
||||
namespace LabelPrintAgent.Updates;
|
||||
|
||||
internal sealed record ReleaseInfo(
|
||||
string TagName,
|
||||
string Name,
|
||||
string HtmlUrl,
|
||||
IReadOnlyList<ReleaseAsset> Assets)
|
||||
{
|
||||
public ReleaseAsset? FindWindowsZipAsset()
|
||||
{
|
||||
return Assets.FirstOrDefault(asset =>
|
||||
asset.Name.EndsWith("-win-x64.zip", StringComparison.OrdinalIgnoreCase))
|
||||
?? Assets.FirstOrDefault(asset =>
|
||||
asset.Name.EndsWith(".zip", StringComparison.OrdinalIgnoreCase));
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,9 @@
|
||||
namespace LabelPrintAgent.Updates;
|
||||
|
||||
internal sealed record UpdateCheckResult(
|
||||
Version CurrentVersion,
|
||||
ReleaseInfo Release,
|
||||
bool IsUpdateAvailable)
|
||||
{
|
||||
public ReleaseAsset? InstallAsset => Release.FindWindowsZipAsset();
|
||||
}
|
||||
@@ -0,0 +1,222 @@
|
||||
using System.Diagnostics;
|
||||
using System.IO.Compression;
|
||||
using System.Net.Http.Headers;
|
||||
using System.Reflection;
|
||||
using System.Text.Json;
|
||||
using System.Text.Json.Serialization;
|
||||
using LabelPrintAgent.Configuration;
|
||||
|
||||
namespace LabelPrintAgent.Updates;
|
||||
|
||||
internal sealed class UpdateService
|
||||
{
|
||||
private static readonly JsonSerializerOptions JsonOptions = new()
|
||||
{
|
||||
PropertyNameCaseInsensitive = true
|
||||
};
|
||||
|
||||
private readonly SettingsStore _settingsStore;
|
||||
|
||||
public UpdateService(SettingsStore settingsStore)
|
||||
{
|
||||
_settingsStore = settingsStore;
|
||||
}
|
||||
|
||||
public Version CurrentVersion => GetCurrentVersion();
|
||||
|
||||
public async Task<UpdateCheckResult> CheckForUpdateAsync(CancellationToken cancellationToken = default)
|
||||
{
|
||||
var settings = _settingsStore.Load();
|
||||
if (string.IsNullOrWhiteSpace(settings.Updates.ReleaseApiUrl))
|
||||
{
|
||||
throw new InvalidOperationException("Die Release-API-URL fehlt.");
|
||||
}
|
||||
|
||||
using var client = CreateHttpClient(settings);
|
||||
using var response = await client.GetAsync(settings.Updates.ReleaseApiUrl, cancellationToken);
|
||||
response.EnsureSuccessStatusCode();
|
||||
|
||||
await using var stream = await response.Content.ReadAsStreamAsync(cancellationToken);
|
||||
var release = await JsonSerializer.DeserializeAsync<GiteaReleaseResponse>(stream, JsonOptions, cancellationToken)
|
||||
?? throw new InvalidOperationException("Die Release-Antwort konnte nicht gelesen werden.");
|
||||
|
||||
var latest = MapRelease(release);
|
||||
var latestVersion = ParseVersion(latest.TagName);
|
||||
var currentVersion = CurrentVersion;
|
||||
return new UpdateCheckResult(currentVersion, latest, latestVersion > currentVersion);
|
||||
}
|
||||
|
||||
public async Task InstallUpdateAsync(UpdateCheckResult checkResult, CancellationToken cancellationToken = default)
|
||||
{
|
||||
var asset = checkResult.InstallAsset
|
||||
?? throw new InvalidOperationException("Das Release enthält kein installierbares ZIP-Asset.");
|
||||
|
||||
var installFolder = AppContext.BaseDirectory.TrimEnd(Path.DirectorySeparatorChar, Path.AltDirectorySeparatorChar);
|
||||
var executablePath = Application.ExecutablePath;
|
||||
var updateRoot = Path.Combine(Path.GetTempPath(), "LabelPrintAgent", "updates", checkResult.Release.TagName);
|
||||
var downloadFile = Path.Combine(updateRoot, asset.Name);
|
||||
var extractFolder = Path.Combine(updateRoot, "extracted");
|
||||
var updaterScript = Path.Combine(updateRoot, "apply-update.cmd");
|
||||
|
||||
Directory.CreateDirectory(updateRoot);
|
||||
if (Directory.Exists(extractFolder))
|
||||
{
|
||||
Directory.Delete(extractFolder, recursive: true);
|
||||
}
|
||||
|
||||
Directory.CreateDirectory(extractFolder);
|
||||
|
||||
var settings = _settingsStore.Load();
|
||||
using var client = CreateHttpClient(settings);
|
||||
await using (var source = await client.GetStreamAsync(asset.DownloadUrl, cancellationToken))
|
||||
await using (var target = File.Create(downloadFile))
|
||||
{
|
||||
await source.CopyToAsync(target, cancellationToken);
|
||||
}
|
||||
|
||||
ZipFile.ExtractToDirectory(downloadFile, extractFolder, overwriteFiles: true);
|
||||
WriteUpdaterScript(updaterScript, extractFolder, installFolder, executablePath, Environment.ProcessId);
|
||||
StartUpdater(updaterScript);
|
||||
}
|
||||
|
||||
private HttpClient CreateHttpClient(AppSettings settings)
|
||||
{
|
||||
var client = new HttpClient();
|
||||
client.DefaultRequestHeaders.UserAgent.ParseAdd("LabelPrintAgent");
|
||||
client.DefaultRequestHeaders.Accept.Add(new MediaTypeWithQualityHeaderValue("application/json"));
|
||||
|
||||
var token = _settingsStore.DecryptUpdateAccessToken(settings).Trim();
|
||||
if (!string.IsNullOrWhiteSpace(token))
|
||||
{
|
||||
client.DefaultRequestHeaders.Authorization = new AuthenticationHeaderValue("token", token);
|
||||
}
|
||||
|
||||
return client;
|
||||
}
|
||||
|
||||
private static ReleaseInfo MapRelease(GiteaReleaseResponse release)
|
||||
{
|
||||
var assets = release.Assets
|
||||
.Where(asset => !string.IsNullOrWhiteSpace(asset.Name) && !string.IsNullOrWhiteSpace(asset.BrowserDownloadUrl))
|
||||
.Select(asset => new ReleaseAsset(asset.Name, asset.BrowserDownloadUrl, asset.Size))
|
||||
.ToList();
|
||||
|
||||
if (string.IsNullOrWhiteSpace(release.TagName))
|
||||
{
|
||||
throw new InvalidOperationException("Das Release enthält keinen Tag.");
|
||||
}
|
||||
|
||||
return new ReleaseInfo(
|
||||
release.TagName,
|
||||
string.IsNullOrWhiteSpace(release.Name) ? release.TagName : release.Name,
|
||||
release.HtmlUrl,
|
||||
assets);
|
||||
}
|
||||
|
||||
private static Version GetCurrentVersion()
|
||||
{
|
||||
var assembly = Assembly.GetExecutingAssembly();
|
||||
var informational = assembly
|
||||
.GetCustomAttribute<AssemblyInformationalVersionAttribute>()
|
||||
?.InformationalVersion;
|
||||
|
||||
if (!string.IsNullOrWhiteSpace(informational))
|
||||
{
|
||||
return ParseVersion(informational);
|
||||
}
|
||||
|
||||
return assembly.GetName().Version ?? new Version(0, 0, 0);
|
||||
}
|
||||
|
||||
private static Version ParseVersion(string value)
|
||||
{
|
||||
var cleaned = value.Trim();
|
||||
if (cleaned.StartsWith("v", StringComparison.OrdinalIgnoreCase))
|
||||
{
|
||||
cleaned = cleaned[1..];
|
||||
}
|
||||
|
||||
var metadataIndex = cleaned.IndexOf('+', StringComparison.Ordinal);
|
||||
if (metadataIndex >= 0)
|
||||
{
|
||||
cleaned = cleaned[..metadataIndex];
|
||||
}
|
||||
|
||||
var prereleaseIndex = cleaned.IndexOf('-', StringComparison.Ordinal);
|
||||
if (prereleaseIndex >= 0)
|
||||
{
|
||||
cleaned = cleaned[..prereleaseIndex];
|
||||
}
|
||||
|
||||
return Version.TryParse(cleaned, out var version)
|
||||
? version
|
||||
: new Version(0, 0, 0);
|
||||
}
|
||||
|
||||
private static void WriteUpdaterScript(
|
||||
string scriptPath,
|
||||
string sourceFolder,
|
||||
string targetFolder,
|
||||
string executablePath,
|
||||
int processId)
|
||||
{
|
||||
var script = $"""
|
||||
@echo off
|
||||
setlocal
|
||||
set "SOURCE={sourceFolder}"
|
||||
set "TARGET={targetFolder}"
|
||||
set "EXE={executablePath}"
|
||||
set "PID={processId}"
|
||||
|
||||
:wait
|
||||
tasklist /FI "PID eq %PID%" 2>NUL | find "%PID%" >NUL
|
||||
if not errorlevel 1 (
|
||||
timeout /t 1 /nobreak >NUL
|
||||
goto wait
|
||||
)
|
||||
|
||||
xcopy "%SOURCE%\*" "%TARGET%\" /E /I /Y >NUL
|
||||
start "" "%EXE%"
|
||||
del "%~f0"
|
||||
""";
|
||||
|
||||
File.WriteAllText(scriptPath, script);
|
||||
}
|
||||
|
||||
private static void StartUpdater(string scriptPath)
|
||||
{
|
||||
Process.Start(new ProcessStartInfo
|
||||
{
|
||||
FileName = scriptPath,
|
||||
UseShellExecute = true,
|
||||
WindowStyle = ProcessWindowStyle.Hidden
|
||||
});
|
||||
}
|
||||
|
||||
private sealed class GiteaReleaseResponse
|
||||
{
|
||||
[JsonPropertyName("tag_name")]
|
||||
public string TagName { get; set; } = string.Empty;
|
||||
|
||||
[JsonPropertyName("name")]
|
||||
public string Name { get; set; } = string.Empty;
|
||||
|
||||
[JsonPropertyName("html_url")]
|
||||
public string HtmlUrl { get; set; } = string.Empty;
|
||||
|
||||
[JsonPropertyName("assets")]
|
||||
public List<GiteaReleaseAssetResponse> Assets { get; set; } = [];
|
||||
}
|
||||
|
||||
private sealed class GiteaReleaseAssetResponse
|
||||
{
|
||||
[JsonPropertyName("name")]
|
||||
public string Name { get; set; } = string.Empty;
|
||||
|
||||
[JsonPropertyName("browser_download_url")]
|
||||
public string BrowserDownloadUrl { get; set; } = string.Empty;
|
||||
|
||||
[JsonPropertyName("size")]
|
||||
public long Size { get; set; }
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user