184 lines
6.6 KiB
C#
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; }
|
|
}
|
|
}
|
|
}
|