Files
Blueberry/Blueberry/UpdateManager.cs
2025-12-16 06:03:51 +01:00

184 lines
6.6 KiB
C#

using System.Diagnostics;
using System.IO;
using System.Net.Http;
using System.Text.Json;
namespace Blueberry
{
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.7";
private static readonly string appDir = AppDomain.CurrentDomain.BaseDirectory;
private static readonly string zipPath = Path.Combine(appDir, "blueberry_update.zip");
private static readonly HttpClient client = new();
public delegate void DownloadCompletedEventArgs();
public static event DownloadCompletedEventArgs DownloadCompleted;
private static bool isDownloading = false;
public static async Task<bool> IsUpdateAvailable()
{
var json = await client.GetStringAsync(releaseUrl);
var release = JsonSerializer.Deserialize<Root>(json);
return release != null && release.tag_name != CurrentVersion;
}
public static async Task WaitUntilDownloadCompleteAndUpdate()
{
var json = await client.GetStringAsync(releaseUrl);
var release = JsonSerializer.Deserialize<Root>(json);
var file = release.assets.Find(x => x.name.Contains(".zip"));
if (!File.Exists(zipPath))
await Download(client, file.browser_download_url, 0);
long localSize = new FileInfo(zipPath).Length;
if (localSize != file.size)
{
if (!isDownloading)
{
await Download(client, file.browser_download_url, localSize);
if (localSize != file.size)
await PerformUpdate();
}
else
{
do
{
await Task.Delay(500);
localSize = new FileInfo(zipPath).Length;
} while (localSize != file.size);
localSize = new FileInfo(zipPath).Length;
if (localSize != file.size)
await PerformUpdate();
}
}
localSize = new FileInfo(zipPath).Length;
if (localSize != file.size)
return;
else
await PerformUpdate();
}
public static async Task DownloadUpdateAsync()
{
client.DefaultRequestHeaders.Add("User-Agent", "Blueberry-Updater");
try
{
// 1. Use await here, don't block
var json = await client.GetStringAsync(releaseUrl);
var release = JsonSerializer.Deserialize<Root>(json);
if (release == null) return;
if (release.tag_name != CurrentVersion)
{
var file = release.assets.Find(x => x.name.Contains(".zip"));
if (file == null) return;
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)
{
}
}
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;
using var contentStream = await response.Content.ReadAsStreamAsync();
using var fileStream = new FileStream(zipPath, fileMode, FileAccess.Write, FileShare.None);
await contentStream.CopyToAsync(fileStream);
isDownloading = false;
DownloadCompleted?.Invoke();
} catch (Exception)
{
isDownloading = false;
}
}
public static async Task PerformUpdate(bool restart = false)
{
string currentExe = Process.GetCurrentProcess().MainModule.FileName;
string psScript = (restart ? Constants.UpdateScriptRestart : Constants.UpdateScriptNoRestart)
.Replace("{currentExe}", $"{currentExe}")
.Replace("{tempZip}", $"{zipPath}")
.Replace("{appDir}", $"{appDir}");
string psPath = Path.Combine(Path.GetTempPath(), "blueberry_updater.ps1");
File.WriteAllText(psPath, psScript);
// 3. Execute the PowerShell script hidden
var startInfo = new ProcessStartInfo()
{
FileName = "powershell.exe",
Arguments = $"-NoProfile -ExecutionPolicy Bypass -File \"{psPath}\"",
UseShellExecute = true,
CreateNoWindow = false,
WindowStyle = ProcessWindowStyle.Normal
};
Process.Start(startInfo);
// 4. Kill the current app immediately so the script can delete it
System.Windows.Application.Current.Shutdown();
}
public class Root
{
public string tag_name { get; set; }
public List<Asset> assets { get; set; }
}
public class Asset
{
public string name { get; set; }
public long size { get; set; }
public string browser_download_url { get; set; }
}
}
}