add partial background downloading and update prompt

This commit is contained in:
2025-12-15 11:03:30 +01:00
parent 5cb7895e24
commit c6097ab6dc
5 changed files with 153 additions and 59 deletions

View File

@@ -53,10 +53,6 @@ namespace BlueMine
/// </summary>
private async void OnStartup(object sender, StartupEventArgs e)
{
#if !DEBUG
var update = new UpdateManager();
await update.CheckAndInstallAsync();
#endif
await _host.StartAsync();
var mainWindow = _host.Services.GetRequiredService<MainWindow>();
mainWindow.Show();

View File

@@ -28,5 +28,41 @@
"Nem elszámolható hívások, email, chat",
"Nem elszámolható telefon, chat, email kommunikáció",
];
public static readonly string UpdateScript = @"# Wait for the main app to close completely
Start-Sleep -Seconds 2
$exePath = '{currentExe}'
$zipPath = '{tempZip}'
$destDir = '{appDir}'
# Retry logic for deletion (in case antivirus or OS holds the lock)
$maxRetries = 10
$retryCount = 0
while ($retryCount -lt $maxRetries) {{
try {{
# Attempt to delete the old executable
if (Test-Path $exePath) {{ Remove-Item $exePath -Force -ErrorAction Stop }}
break # If successful, exit loop
}}
catch {{
Start-Sleep -Milliseconds 500
$retryCount++
}}
}}
# Unzip the new version
Expand-Archive -Path $zipPath -DestinationPath $destDir -Force
# CLEANUP: Delete the zip
Remove-Item $zipPath -Force
# RESTART: Launch the new executable
# 'Start-Process' is the robust way to launch detached processes in PS
Start-Process -FilePath $exePath -WorkingDirectory $destDir
# SELF-DESTRUCT: Remove this script
Remove-Item -LiteralPath $MyInvocation.MyCommand.Path -Force";
}
}

View File

@@ -256,6 +256,15 @@
<ui:ProgressRing x:Name="progressRing" Grid.Row="4" Height="10" Width="10" Margin="10" HorizontalAlignment="Left" IsIndeterminate="True" />
<ui:TextBlock x:Name="statusTextBlock" Grid.Row="4" Grid.ColumnSpan="6" FontSize="8" Text="Staus: OK" Margin="30, 10, 10, 10" />
<ui:TextBlock x:Name="versionTextBlock" Grid.Row="4" Grid.Column="2" HorizontalAlignment="Right" FontSize="8" Text="0.0.0" Margin="10" />
<Grid Grid.Row="4" Grid.Column="2" >
<Grid.ColumnDefinitions>
<ColumnDefinition Width="1*" />
<ColumnDefinition Width="Auto" />
<ColumnDefinition Width="Auto" />
</Grid.ColumnDefinitions>
<ui:SymbolIcon x:Name="updateIcon" Symbol="ArrowCircleUp24" Grid.Column="1" Margin="10" Filled="True"
Foreground="{ui:ThemeResource AccentTextFillColorPrimaryBrush}" Visibility="Hidden" />
<ui:TextBlock x:Name="versionTextBlock" Grid.Column="2" HorizontalAlignment="Right" FontSize="8" Text="0.0.0" Margin="10" />
</Grid>
</Grid>
</ui:FluentWindow>

View File

@@ -45,7 +45,33 @@ namespace BlueMine
Task getHoursTask = GetHours();
await Task.WhenAll(loadIssuesTask, getHoursTask);
#if !DEBUG
if(await UpdateManager.IsUpdateAvailable())
{
updateIcon.Visibility = Visibility.Visible;
UpdateManager.DownloadCompleted += UpdateManager_DownloadCompleted;
await UpdateManager.DownloadUpdateAsync();
}
#endif
}
}
private async void UpdateManager_DownloadCompleted()
{
await Dispatcher.Invoke(async () =>
{
var result = await new Wpf.Ui.Controls.MessageBox
{
Title = "Frissítés elérhető",
Content = "Szeretnél most frissíteni?",
PrimaryButtonText = "Frissítés",
SecondaryButtonText = "Később",
IsCloseButtonEnabled = false,
}.ShowDialogAsync();
if (result == Wpf.Ui.Controls.MessageBoxResult.Primary)
await UpdateManager.PerformUpdate();
});
}
private void CalendarButtonClicked(object sender, RoutedEventArgs e)

View File

@@ -2,33 +2,69 @@
using System.IO;
using System.Net.Http;
using System.Text.Json;
using Windows.Media.Protection.PlayReady;
namespace Blueberry
{
public class UpdateManager
public static class UpdateManager
{
private const string releaseUrl = "https://git.technopunk.space/api/v1/repos/tomi/Blueberry/releases/latest";
public const string CurrentVersion = "0.1.1";
public const string CurrentVersion = "0.1.2";
private static readonly string AppDir = AppDomain.CurrentDomain.BaseDirectory;
private static readonly HttpClient client = new();
public delegate void DownloadCompletedEventArgs();
public static event DownloadCompletedEventArgs DownloadCompleted;
private static bool isDownloading = false;
public async Task CheckAndInstallAsync()
public static async Task<bool> IsUpdateAvailable()
{
using var client = new HttpClient();
var json = await client.GetStringAsync(releaseUrl);
var release = JsonSerializer.Deserialize<Root>(json);
return release != null && release.tag_name != CurrentVersion;
}
public static async Task DownloadUpdateAsync()
{
client.DefaultRequestHeaders.Add("User-Agent", "Blueberry-Updater");
try
{
var json = client.GetStringAsync(releaseUrl).ConfigureAwait(false).GetAwaiter().GetResult();
// 1. Use await here, don't block
var json = await client.GetStringAsync(releaseUrl);
var release = JsonSerializer.Deserialize<Root>(json);
if (release == null)
throw new NullReferenceException();
if (release == null) return;
if (release.tag_name != CurrentVersion)
{
var file = release.assets.Find(x => x.name.Contains(".zip")) ?? throw new NullReferenceException();
string downloadUrl = file.browser_download_url;
await PerformUpdate(client, downloadUrl);
var file = release.assets.Find(x => x.name.Contains(".zip"));
if (file == null) return;
string zipPath = Path.Combine(AppDir, "blueberry_update.zip");
long offset = 0;
if (File.Exists(zipPath))
{
long localSize = new FileInfo(zipPath).Length;
if (localSize == file.size)
{
DownloadCompleted?.Invoke();
return;
}
if (localSize > file.size)
{
File.Delete(zipPath);
offset = 0;
}
else
{
offset = localSize;
}
}
if (!isDownloading)
await Download(client, file.browser_download_url, offset);
}
}
catch (Exception)
@@ -37,55 +73,45 @@ namespace Blueberry
}
}
private async Task PerformUpdate(HttpClient client, string url)
private static async Task Download(HttpClient client, string url, long offset = 0)
{
try
{
isDownloading = true;
var request = new HttpRequestMessage(HttpMethod.Get, url);
// If we have an offset, ask the server for the rest of the file
if (offset > 0)
{
request.Headers.Range = new System.Net.Http.Headers.RangeHeaderValue(offset, null);
}
using var response = await client.SendAsync(request, HttpCompletionOption.ResponseHeadersRead);
response.EnsureSuccessStatusCode();
// If offset > 0, we APPEND. If 0, we CREATE/OVERWRITE.
var fileMode = offset > 0 ? FileMode.Append : FileMode.Create;
string filePath = Path.Combine(AppDir, "blueberry_update.zip");
using var contentStream = await response.Content.ReadAsStreamAsync();
using var fileStream = new FileStream(filePath, fileMode, FileAccess.Write, FileShare.None);
await contentStream.CopyToAsync(fileStream);
isDownloading = false;
DownloadCompleted?.Invoke();
} catch (Exception)
{
isDownloading = false;
}
}
public static async Task PerformUpdate()
{
string tempZip = Path.Combine(Path.GetTempPath(), "blueberry_update.zip");
string currentExe = Process.GetCurrentProcess().MainModule.FileName;
string appDir = AppDomain.CurrentDomain.BaseDirectory;
// 1. Download
var data = await client.GetByteArrayAsync(url);
File.WriteAllBytes(tempZip, data);
// 2. Create a temporary batch script to handle the swap
// We use a small delay (timeout) to allow the main app to close fully
string psScript = $@"
# Wait for the main app to close completely
Start-Sleep -Seconds 2
$exePath = '{currentExe}'
$zipPath = '{tempZip}'
$destDir = '{appDir}'
# Retry logic for deletion (in case antivirus or OS holds the lock)
$maxRetries = 10
$retryCount = 0
while ($retryCount -lt $maxRetries) {{
try {{
# Attempt to delete the old executable
if (Test-Path $exePath) {{ Remove-Item $exePath -Force -ErrorAction Stop }}
break # If successful, exit loop
}}
catch {{
Start-Sleep -Milliseconds 500
$retryCount++
}}
}}
# Unzip the new version
Expand-Archive -Path $zipPath -DestinationPath $destDir -Force
# CLEANUP: Delete the zip
Remove-Item $zipPath -Force
# RESTART: Launch the new executable
# 'Start-Process' is the robust way to launch detached processes in PS
Start-Process -FilePath $exePath -WorkingDirectory $destDir
# SELF-DESTRUCT: Remove this script
Remove-Item -LiteralPath $MyInvocation.MyCommand.Path -Force
";
string psScript = Constants.UpdateScript;
string psPath = Path.Combine(Path.GetTempPath(), "blueberry_updater.ps1");
File.WriteAllText(psPath, psScript);
@@ -114,6 +140,7 @@ Remove-Item -LiteralPath $MyInvocation.MyCommand.Path -Force
public class Asset
{
public string name { get; set; }
public long size { get; set; }
public string browser_download_url { get; set; }
}
}