From c6097ab6dc680bd3dbf3ca3734d7736138c3b359 Mon Sep 17 00:00:00 2001 From: Tomi Eckert Date: Mon, 15 Dec 2025 11:03:30 +0100 Subject: [PATCH] add partial background downloading and update prompt --- Blueberry/App.xaml.cs | 4 -- Blueberry/Constants.cs | 36 ++++++++++ Blueberry/MainWindow.xaml | 11 ++- Blueberry/MainWindow.xaml.cs | 26 +++++++ Blueberry/UpdateManager.cs | 135 +++++++++++++++++++++-------------- 5 files changed, 153 insertions(+), 59 deletions(-) diff --git a/Blueberry/App.xaml.cs b/Blueberry/App.xaml.cs index 62539c3..757209c 100644 --- a/Blueberry/App.xaml.cs +++ b/Blueberry/App.xaml.cs @@ -53,10 +53,6 @@ namespace BlueMine /// 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.Show(); diff --git a/Blueberry/Constants.cs b/Blueberry/Constants.cs index 90f70b9..3deae45 100644 --- a/Blueberry/Constants.cs +++ b/Blueberry/Constants.cs @@ -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"; } } diff --git a/Blueberry/MainWindow.xaml b/Blueberry/MainWindow.xaml index ca0edf9..1959f3c 100644 --- a/Blueberry/MainWindow.xaml +++ b/Blueberry/MainWindow.xaml @@ -256,6 +256,15 @@ - + + + + + + + + + diff --git a/Blueberry/MainWindow.xaml.cs b/Blueberry/MainWindow.xaml.cs index 0d16b31..8fb9bac 100644 --- a/Blueberry/MainWindow.xaml.cs +++ b/Blueberry/MainWindow.xaml.cs @@ -45,9 +45,35 @@ 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) { flyoutCalendar.IsOpen = true; diff --git a/Blueberry/UpdateManager.cs b/Blueberry/UpdateManager.cs index b84f911..1302e73 100644 --- a/Blueberry/UpdateManager.cs +++ b/Blueberry/UpdateManager.cs @@ -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 IsUpdateAvailable() { - using var client = new HttpClient(); + var json = await client.GetStringAsync(releaseUrl); + var release = JsonSerializer.Deserialize(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(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; } } }