From 22ded9029cdb948f57d37ddedee338df47dd8326 Mon Sep 17 00:00:00 2001 From: Tomi Eckert Date: Wed, 10 Dec 2025 13:15:05 +0100 Subject: [PATCH] update installer to use git releases --- BlueberryUpdater/BlueberryUpdater.csproj | 18 ++- BlueberryUpdater/Program.cs | 139 +++++++++++++++-------- 2 files changed, 101 insertions(+), 56 deletions(-) diff --git a/BlueberryUpdater/BlueberryUpdater.csproj b/BlueberryUpdater/BlueberryUpdater.csproj index 82ce019..e76b315 100644 --- a/BlueberryUpdater/BlueberryUpdater.csproj +++ b/BlueberryUpdater/BlueberryUpdater.csproj @@ -9,16 +9,22 @@ bb.ico + + true + + true + true + true + + true + false + false + + PreserveNewest - - - false - - - diff --git a/BlueberryUpdater/Program.cs b/BlueberryUpdater/Program.cs index 4672b43..d7c3b95 100644 --- a/BlueberryUpdater/Program.cs +++ b/BlueberryUpdater/Program.cs @@ -1,6 +1,9 @@ using System.Diagnostics; using System.IO.Compression; +using System.Net.Http.Json; using System.Reflection; +using System.Text.Json; +using Windows.Media.Protection.PlayReady; namespace BlueberryUpdater { @@ -65,7 +68,23 @@ namespace BlueberryUpdater { try { - string resourceName = "BlueberryUpdater.AppPayload.zip"; // Format: Namespace.Filename + var client = new HttpClient(); + client.DefaultRequestHeaders.UserAgent.ParseAdd("BlueberryUpdater"); + + string releaseUrl = "https://git.technopunk.space/api/v1/repos/tomi/Blueberry/releases/latest"; + + // 1. Fetch JSON + var root = client.GetFromJsonAsync(releaseUrl).ConfigureAwait(false).GetAwaiter().GetResult(); + + // 2. Find URL for "payload.zip" + string downloadUrl = root.GetProperty("assets") + .EnumerateArray() + .First(x => x.GetProperty("name").GetString() == "payload.zip") + .GetProperty("browser_download_url") + .GetString() ?? throw new NullReferenceException(); + + Console.WriteLine("Downloading Blueberry..."); + var stream = DownloadFileWithProgressAsync(client, downloadUrl).GetAwaiter().GetResult(); Console.WriteLine($"Installing to {installPath}..."); @@ -76,56 +95,9 @@ namespace BlueberryUpdater } Directory.CreateDirectory(installPath); - // 2. Extract Embedded Resource - var assembly = Assembly.GetExecutingAssembly(); - - using (Stream stream = assembly.GetManifestResourceStream(resourceName)) - { - if (stream == null) - { - throw new Exception($"Resource '{resourceName}' not found. Check Embedded Resource settings."); - } - - using (ZipArchive archive = new(stream)) - { - int totalEntries = archive.Entries.Count; - int currentEntry = 0; - - foreach (ZipArchiveEntry entry in archive.Entries) - { - currentEntry++; - - // Calculate percentage - int percent = (int)((double)currentEntry / totalEntries * 100); - - // Draw Progress Bar - DrawProgressBar(percent, entry.Name); - - // Create the full path - string destinationPath = Path.GetFullPath(Path.Combine(installPath, entry.FullName)); - - // Security check: prevent ZipSlip (writing outside target folder) - if (!destinationPath.StartsWith(installPath, StringComparison.OrdinalIgnoreCase)) - continue; - - // Handle folders vs files - if (string.IsNullOrEmpty(entry.Name)) // It's a directory - { - Directory.CreateDirectory(destinationPath); - } - else // It's a file - { - // Ensure the directory exists (zipped files might not list their dir first) - Directory.CreateDirectory(Path.GetDirectoryName(destinationPath)); - entry.ExtractToFile(destinationPath, overwrite: true); - } - } - } - } - + ExtractWithProgress(stream); MoveUpdater(); - DrawProgressBar(100, "Done"); Console.WriteLine(); Console.WriteLine("Installation Complete!"); @@ -181,7 +153,7 @@ namespace BlueberryUpdater string shortName = filename.Length > 20 ? filename.Substring(0, 17) + "..." : filename.PadRight(20); Console.Write("["); - int width = Console.WindowWidth - 1; // Width of the bar + int width = Console.WindowWidth - 21; // Width of the bar int progress = (int)((percent / 100.0) * width); // Draw filled part @@ -191,5 +163,72 @@ namespace BlueberryUpdater Console.Write($"] {percent}% {shortName}"); } + + static async Task DownloadFileWithProgressAsync(HttpClient client, string url) + { + // 1. Get headers only first to check size + using var response = await client.GetAsync(url, HttpCompletionOption.ResponseHeadersRead); + response.EnsureSuccessStatusCode(); + + var totalBytes = response.Content.Headers.ContentLength ?? -1L; + var canReportProgress = totalBytes != -1; + + using var contentStream = await response.Content.ReadAsStreamAsync(); + using var memoryStream = new MemoryStream(); + + var buffer = new byte[8192]; + long totalRead = 0; + int bytesRead; + + while ((bytesRead = await contentStream.ReadAsync(buffer, 0, buffer.Length)) > 0) + { + await memoryStream.WriteAsync(buffer, 0, bytesRead); + totalRead += bytesRead; + + if (canReportProgress) + DrawProgressBar((int)((double)totalRead / totalBytes * 100), "Downloading..."); + } + DrawProgressBar(100, "Done"); + return memoryStream; + } + static void ExtractWithProgress(MemoryStream stream) + { + using (ZipArchive archive = new(stream)) + { + int totalEntries = archive.Entries.Count; + int currentEntry = 0; + + foreach (ZipArchiveEntry entry in archive.Entries) + { + currentEntry++; + + // Calculate percentage + int percent = (int)((double)currentEntry / totalEntries * 100); + + // Draw Progress Bar + DrawProgressBar(percent, entry.Name); + + // Create the full path + string destinationPath = Path.GetFullPath(Path.Combine(installPath, entry.FullName)); + + // Security check: prevent ZipSlip (writing outside target folder) + if (!destinationPath.StartsWith(installPath, StringComparison.OrdinalIgnoreCase)) + continue; + + // Handle folders vs files + if (string.IsNullOrEmpty(entry.Name)) // It's a directory + { + Directory.CreateDirectory(destinationPath); + } + else // It's a file + { + // Ensure the directory exists (zipped files might not list their dir first) + Directory.CreateDirectory(Path.GetDirectoryName(destinationPath)); + entry.ExtractToFile(destinationPath, overwrite: true); + } + } + } + DrawProgressBar(100, "Done"); + } } }