Add auto updater
This commit is contained in:
@@ -1,3 +1,7 @@
|
||||
|
||||
[plc]
|
||||
port = COM1
|
||||
|
||||
[update]
|
||||
url = https://elwig.at/files/pamhagen-sysctrl/?format=json
|
||||
auto = true
|
||||
|
||||
@@ -1,3 +1,4 @@
|
||||
using PamhagenSysCtrl.Dialogs;
|
||||
using PamhagenSysCtrl.Helpers;
|
||||
using System.IO;
|
||||
using System.Reflection;
|
||||
@@ -10,6 +11,9 @@ namespace PamhagenSysCtrl {
|
||||
public static PamhagenPlant? Plant;
|
||||
public static Dispatcher? MainDispatcher;
|
||||
|
||||
private readonly DispatcherTimer AutoUpdateTimer = new() { Interval = TimeSpan.FromHours(1) };
|
||||
private bool IsCheckingForUpdates;
|
||||
|
||||
public static readonly string DataPath = Path.Combine(Environment.GetFolderPath(Environment.SpecialFolder.CommonApplicationData), "Anlagensteuerung Pamhagen");
|
||||
public static readonly string ConfigPath = Path.Combine(DataPath, "config.ini");
|
||||
public static readonly string InstallPath = Path.Combine(Environment.GetFolderPath(Environment.SpecialFolder.ProgramFiles), "Anlagensteuerung Pamhagen");
|
||||
@@ -45,6 +49,71 @@ namespace PamhagenSysCtrl {
|
||||
}
|
||||
|
||||
base.OnStartup(evt);
|
||||
|
||||
if (Config.UpdateAuto && Config.UpdateUrl != null) {
|
||||
AutoUpdateTimer.Tick += async (_, _) => await CheckForUpdates();
|
||||
AutoUpdateTimer.Start();
|
||||
_ = Dispatcher.BeginInvoke(async () => {
|
||||
await Task.Delay(1500);
|
||||
await CheckForUpdates();
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
public async Task CheckForUpdates(bool showResult = false) {
|
||||
if (IsCheckingForUpdates) {
|
||||
if (showResult) {
|
||||
MessageBox.Show("Es wird bereits nach Updates gesucht.", "Nach Updates suchen", MessageBoxButton.OK, MessageBoxImage.Information);
|
||||
}
|
||||
return;
|
||||
}
|
||||
if (Config.UpdateUrl == null) {
|
||||
if (showResult) {
|
||||
MessageBox.Show("Die automatische Update-Suche ist deaktiviert.", "Nach Updates suchen", MessageBoxButton.OK, MessageBoxImage.Information);
|
||||
}
|
||||
return;
|
||||
}
|
||||
|
||||
IsCheckingForUpdates = true;
|
||||
try {
|
||||
using var timeout = new CancellationTokenSource(TimeSpan.FromSeconds(15));
|
||||
var latest = await UpdateService.GetLatestInstallerAsync(Config.UpdateUrl, timeout.Token);
|
||||
if (latest.Version > Version) {
|
||||
var dialog = new UpdateDialog(latest) {
|
||||
Owner = MainWindow,
|
||||
};
|
||||
if (dialog.ShowDialog() == true) {
|
||||
Shutdown();
|
||||
}
|
||||
} else if (showResult) {
|
||||
MessageBox.Show(
|
||||
$"Die Anlagensteuerung ist auf dem aktuellen Stand. (Version {Version})",
|
||||
"Nach Updates suchen",
|
||||
MessageBoxButton.OK,
|
||||
MessageBoxImage.Information
|
||||
);
|
||||
}
|
||||
} catch (OperationCanceledException) {
|
||||
if (showResult) {
|
||||
MessageBox.Show(
|
||||
"Zeitüberschreitung beim Abrufen der Update-Informationen.",
|
||||
"Nach Updates suchen",
|
||||
MessageBoxButton.OK,
|
||||
MessageBoxImage.Error
|
||||
);
|
||||
}
|
||||
} catch (Exception exc) {
|
||||
if (showResult) {
|
||||
MessageBox.Show(
|
||||
$"Die Update-Informationen konnten nicht abgerufen werden:\n\n{exc.Message}",
|
||||
"Nach Updates suchen",
|
||||
MessageBoxButton.OK,
|
||||
MessageBoxImage.Error
|
||||
);
|
||||
}
|
||||
} finally {
|
||||
IsCheckingForUpdates = false;
|
||||
}
|
||||
}
|
||||
|
||||
protected async void Application_Exit(object sender, ExitEventArgs evt) {
|
||||
|
||||
@@ -0,0 +1,37 @@
|
||||
<Window x:Class="PamhagenSysCtrl.Dialogs.UpdateDialog"
|
||||
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
|
||||
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
|
||||
ResizeMode="NoResize" ShowInTaskbar="False" Topmost="True"
|
||||
WindowStartupLocation="CenterOwner"
|
||||
Title="Update verfügbar - Anlagensteuerung Pamhagen"
|
||||
Height="220" Width="460"
|
||||
Closed="OnClosed">
|
||||
<Grid Margin="20">
|
||||
<Grid.RowDefinitions>
|
||||
<RowDefinition Height="*"/>
|
||||
<RowDefinition Height="Auto"/>
|
||||
<RowDefinition Height="Auto"/>
|
||||
</Grid.RowDefinitions>
|
||||
|
||||
<TextBlock x:Name="Description" FontSize="14" TextAlignment="Center"
|
||||
HorizontalAlignment="Center" VerticalAlignment="Center">
|
||||
Version <Run x:Name="VersionText" FontWeight="Bold">0.0.0</Run> ist verfügbar.<LineBreak/>
|
||||
Soll das Update heruntergeladen und installiert werden?<LineBreak/>
|
||||
(ca. <Run x:Name="SizeText">0</Run> MB)<LineBreak/><LineBreak/>
|
||||
<Run FontWeight="Bold">Hinweis:</Run> Die Anlagensteuerung wird zur Installation geschlossen.
|
||||
</TextBlock>
|
||||
|
||||
<StackPanel x:Name="DownloadPanel" Grid.Row="0" VerticalAlignment="Center" Visibility="Collapsed">
|
||||
<TextBlock x:Name="StatusText" Text="Update wird heruntergeladen..."
|
||||
FontSize="14" TextAlignment="Center" Margin="0,0,0,10"/>
|
||||
<ProgressBar x:Name="DownloadProgress" Height="24" Minimum="0" Maximum="100"/>
|
||||
</StackPanel>
|
||||
|
||||
<StackPanel Grid.Row="2" Orientation="Horizontal" HorizontalAlignment="Center" Margin="0,18,0,0">
|
||||
<Button x:Name="InstallButton" Content="Installieren" IsDefault="True"
|
||||
Width="110" Height="30" Margin="0,0,12,0" Click="InstallButton_Click"/>
|
||||
<Button x:Name="CancelButton" Content="Abbrechen" IsCancel="True"
|
||||
Width="110" Height="30"/>
|
||||
</StackPanel>
|
||||
</Grid>
|
||||
</Window>
|
||||
@@ -0,0 +1,49 @@
|
||||
using PamhagenSysCtrl.Helpers;
|
||||
using System.Windows;
|
||||
|
||||
namespace PamhagenSysCtrl.Dialogs {
|
||||
public partial class UpdateDialog : Window {
|
||||
|
||||
private readonly UpdateInstaller Installer;
|
||||
private readonly CancellationTokenSource Cancellation = new();
|
||||
|
||||
public UpdateDialog(UpdateInstaller installer) {
|
||||
Installer = installer;
|
||||
InitializeComponent();
|
||||
VersionText.Text = installer.Version.ToString();
|
||||
SizeText.Text = Math.Ceiling(installer.Size / 1024d / 1024d).ToString("N0");
|
||||
}
|
||||
|
||||
private void OnClosed(object? sender, EventArgs evt) {
|
||||
Cancellation.Cancel();
|
||||
Cancellation.Dispose();
|
||||
}
|
||||
|
||||
private async void InstallButton_Click(object sender, RoutedEventArgs evt) {
|
||||
Description.Visibility = Visibility.Collapsed;
|
||||
DownloadPanel.Visibility = Visibility.Visible;
|
||||
InstallButton.IsEnabled = false;
|
||||
|
||||
try {
|
||||
var progress = new Progress<double>(value => DownloadProgress.Value = value * 100);
|
||||
var fileName = await UpdateService.DownloadInstallerAsync(Installer, App.TempPath, progress, Cancellation.Token);
|
||||
StatusText.Text = "Installer wird gestartet...";
|
||||
UpdateService.StartInstaller(fileName);
|
||||
DialogResult = true;
|
||||
} catch (OperationCanceledException) {
|
||||
// Closing the dialog cancels an active download.
|
||||
} catch (Exception exc) {
|
||||
MessageBox.Show(
|
||||
this,
|
||||
$"Das Update konnte nicht installiert werden:\n\n{exc.Message}",
|
||||
"Update installieren",
|
||||
MessageBoxButton.OK,
|
||||
MessageBoxImage.Error
|
||||
);
|
||||
Description.Visibility = Visibility.Visible;
|
||||
DownloadPanel.Visibility = Visibility.Collapsed;
|
||||
InstallButton.IsEnabled = true;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -9,6 +9,8 @@ namespace PamhagenSysCtrl.Helpers {
|
||||
private readonly string FileName;
|
||||
|
||||
public string PlcPort = "COM1";
|
||||
public string? UpdateUrl = "https://elwig.at/files/pamhagen-sysctrl/?format=json";
|
||||
public bool UpdateAuto = true;
|
||||
|
||||
public Config(string filename) {
|
||||
FileName = filename;
|
||||
@@ -19,6 +21,9 @@ namespace PamhagenSysCtrl.Helpers {
|
||||
try {
|
||||
var config = new ConfigurationBuilder().AddIniFile(FileName).Build();
|
||||
PlcPort = config["plc:port"] ?? "COM1";
|
||||
UpdateUrl = config["update:url"] ?? UpdateUrl;
|
||||
var updateAuto = config["update:auto"];
|
||||
UpdateAuto = updateAuto == null || TrueValues.Contains(updateAuto.ToLower());
|
||||
} catch (Exception exc) {
|
||||
MessageBox.Show($"Die Konfigurationsdatei konnte nicht gelesen werden:\n\n{exc.Message}", "Konfigurationsdatei lesen", MessageBoxButton.OK, MessageBoxImage.Error);
|
||||
Application.Current.Shutdown();
|
||||
|
||||
@@ -0,0 +1,74 @@
|
||||
using System.Diagnostics;
|
||||
using System.IO;
|
||||
using System.Net.Http;
|
||||
using System.Text.Json.Nodes;
|
||||
|
||||
namespace PamhagenSysCtrl.Helpers {
|
||||
public sealed record UpdateInstaller(Version Version, Uri Url, long Size);
|
||||
|
||||
public static class UpdateService {
|
||||
|
||||
private static readonly HttpClient HttpClient = new() {
|
||||
Timeout = Timeout.InfiniteTimeSpan,
|
||||
};
|
||||
|
||||
public static async Task<UpdateInstaller> GetLatestInstallerAsync(string feedUrl, CancellationToken cancellationToken = default) {
|
||||
using var response = await HttpClient.GetAsync(feedUrl, cancellationToken);
|
||||
response.EnsureSuccessStatusCode();
|
||||
|
||||
var json = JsonNode.Parse(await response.Content.ReadAsStringAsync(cancellationToken));
|
||||
var latest = json!["data"]!.AsArray()[^1]!;
|
||||
return new(
|
||||
new Version((string)latest["version"]!),
|
||||
new Uri((string)latest["url"]!),
|
||||
(long)latest["size"]!
|
||||
);
|
||||
}
|
||||
|
||||
public static async Task<string> DownloadInstallerAsync(UpdateInstaller installer, string targetDirectory, IProgress<double>? progress = null, CancellationToken cancellationToken = default) {
|
||||
Directory.CreateDirectory(targetDirectory);
|
||||
var fileName = Path.Combine(targetDirectory, $"PamhagenSysCtrl-{installer.Version}.msi");
|
||||
|
||||
try {
|
||||
using var response = await HttpClient.GetAsync(installer.Url, HttpCompletionOption.ResponseHeadersRead, cancellationToken);
|
||||
response.EnsureSuccessStatusCode();
|
||||
var contentLength = response.Content.Headers.ContentLength;
|
||||
|
||||
await using (var destination = new FileStream(fileName, FileMode.Create)) {
|
||||
await using var source = await response.Content.ReadAsStreamAsync(cancellationToken);
|
||||
var buffer = new byte[81920];
|
||||
long downloaded = 0;
|
||||
int read;
|
||||
while ((read = await source.ReadAsync(buffer, cancellationToken)) != 0) {
|
||||
await destination.WriteAsync(buffer.AsMemory(0, read), cancellationToken);
|
||||
downloaded += read;
|
||||
if (contentLength.HasValue) {
|
||||
progress?.Report((double)downloaded / contentLength.Value);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
progress?.Report(1);
|
||||
return fileName;
|
||||
} catch {
|
||||
File.Delete(fileName);
|
||||
throw;
|
||||
}
|
||||
}
|
||||
|
||||
public static void StartInstaller(string fileName) {
|
||||
var startInfo = new ProcessStartInfo {
|
||||
FileName = "msiexec.exe",
|
||||
UseShellExecute = true,
|
||||
Verb = "runas",
|
||||
};
|
||||
startInfo.ArgumentList.Add("/i");
|
||||
startInfo.ArgumentList.Add(Path.GetFullPath(fileName));
|
||||
|
||||
if (Process.Start(startInfo) == null) {
|
||||
throw new InvalidOperationException("Der Installer konnte nicht gestartet werden.");
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
@@ -50,6 +50,11 @@
|
||||
<TextBlock FontFamily="Segoe MDL2 Assets" FontSize="16" Text=""/>
|
||||
</MenuItem.Icon>
|
||||
</MenuItem>
|
||||
<MenuItem Header="Nach Updates suchen..." Click="Menu_Help_CheckForUpdates_Click">
|
||||
<MenuItem.Icon>
|
||||
<TextBlock FontFamily="Segoe MDL2 Assets" FontSize="16" Text=""/>
|
||||
</MenuItem.Icon>
|
||||
</MenuItem>
|
||||
</MenuItem>
|
||||
</Menu>
|
||||
|
||||
|
||||
@@ -75,6 +75,12 @@ namespace PamhagenSysCtrl.Windows {
|
||||
} catch { }
|
||||
}
|
||||
|
||||
private async void Menu_Help_CheckForUpdates_Click(object sender, RoutedEventArgs evt) {
|
||||
if (Application.Current is App app) {
|
||||
await app.CheckForUpdates(true);
|
||||
}
|
||||
}
|
||||
|
||||
private void SchemeCanvas_MouseMove(object sender, MouseEventArgs evt) {
|
||||
var p = evt.GetPosition(SchemeCanvas);
|
||||
UpdateScheme(evt.GetPosition(SchemeCanvas), evt.LeftButton == MouseButtonState.Pressed);
|
||||
|
||||
Reference in New Issue
Block a user