Fix upgrade bug when Libation's working dir isn't program files dir
Add MockUpgrader for testing the Upgrade process. Fixes issue #1302
This commit is contained in:
parent
d8e9b9c505
commit
9280b29512
@ -166,20 +166,23 @@ namespace LibationAvalonia.Views
|
||||
private void Configure_Upgrade()
|
||||
{
|
||||
setProgressVisible(false);
|
||||
#if !DEBUG
|
||||
async System.Threading.Tasks.Task upgradeAvailable(LibationUiBase.UpgradeEventArgs e)
|
||||
#pragma warning disable CS8321 // Local function is declared but never used
|
||||
async Task upgradeAvailable(LibationUiBase.UpgradeEventArgs e)
|
||||
{
|
||||
var notificationResult = await new Dialogs.UpgradeNotificationDialog(e.UpgradeProperties, e.CapUpgrade).ShowDialogAsync(this);
|
||||
var notificationResult = await new UpgradeNotificationDialog(e.UpgradeProperties, e.CapUpgrade).ShowDialogAsync(this);
|
||||
|
||||
e.Ignore = notificationResult == DialogResult.Ignore;
|
||||
e.InstallUpgrade = notificationResult == DialogResult.OK;
|
||||
}
|
||||
#pragma warning restore CS8321 // Local function is declared but never used
|
||||
|
||||
var upgrader = new LibationUiBase.Upgrader();
|
||||
upgrader.DownloadProgress += async (_, e) => await Avalonia.Threading.Dispatcher.UIThread.InvokeAsync(() => ViewModel.DownloadProgress = e.ProgressPercentage);
|
||||
upgrader.DownloadBegin += async (_, _) => await Avalonia.Threading.Dispatcher.UIThread.InvokeAsync(() => setProgressVisible(true));
|
||||
upgrader.DownloadCompleted += async (_, _) => await Avalonia.Threading.Dispatcher.UIThread.InvokeAsync(() => setProgressVisible(false));
|
||||
upgrader.DownloadProgress += async (_, e) => await Dispatcher.UIThread.InvokeAsync(() => ViewModel.DownloadProgress = e.ProgressPercentage);
|
||||
upgrader.DownloadBegin += async (_, _) => await Dispatcher.UIThread.InvokeAsync(() => setProgressVisible(true));
|
||||
upgrader.DownloadCompleted += async (_, _) => await Dispatcher.UIThread.InvokeAsync(() => setProgressVisible(false));
|
||||
upgrader.UpgradeFailed += async (_, message) => await Dispatcher.UIThread.InvokeAsync(() => { setProgressVisible(false); MessageBox.Show(this, message, "Upgrade Failed", MessageBoxButtons.OK, MessageBoxIcon.Error); });
|
||||
|
||||
#if !DEBUG
|
||||
Opened += async (_, _) => await upgrader.CheckForUpgradeAsync(upgradeAvailable);
|
||||
#endif
|
||||
}
|
||||
|
||||
@ -6,11 +6,12 @@ using System.IO;
|
||||
using System.Net.Http;
|
||||
using System.Threading.Tasks;
|
||||
|
||||
#nullable enable
|
||||
namespace LibationUiBase
|
||||
{
|
||||
public class UpgradeEventArgs
|
||||
{
|
||||
public UpgradeProperties UpgradeProperties { get; internal init; }
|
||||
public required UpgradeProperties UpgradeProperties { get; init; }
|
||||
public bool CapUpgrade { get; internal init; }
|
||||
private bool _ignore = false;
|
||||
private bool _installUpgrade = true;
|
||||
@ -34,18 +35,147 @@ namespace LibationUiBase
|
||||
}
|
||||
}
|
||||
|
||||
public class Upgrader
|
||||
public class Upgrader : UpgraderBase
|
||||
{
|
||||
public event EventHandler DownloadBegin;
|
||||
public event EventHandler<DownloadProgress> DownloadProgress;
|
||||
public event EventHandler<bool> DownloadCompleted;
|
||||
protected override async Task<UpgradeProperties?> CheckForUpgradeAsync()
|
||||
{
|
||||
try
|
||||
{
|
||||
return await Task.Run(LibationScaffolding.GetLatestRelease);
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
string message = "An error occurred while checking for app upgrades.";
|
||||
Serilog.Log.Logger.Error(ex, message);
|
||||
OnUpgradeFailed(message, ex);
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
protected override async Task<string?> DownloadUpgradeAsync(UpgradeProperties upgradeProperties)
|
||||
{
|
||||
if (upgradeProperties.ZipUrl is null)
|
||||
{
|
||||
string message = "Download link for new version not found.";
|
||||
Serilog.Log.Logger.Warning(message);
|
||||
OnUpgradeFailed(message, null);
|
||||
return null;
|
||||
}
|
||||
|
||||
//Silently download the upgrade in the background, save it to a temp file.
|
||||
|
||||
var zipFile = Path.Combine(Path.GetTempPath(), Path.GetFileName(upgradeProperties.ZipUrl));
|
||||
|
||||
Serilog.Log.Logger.Information($"Downloading {zipFile}");
|
||||
|
||||
try
|
||||
{
|
||||
using var dlClient = new HttpClient();
|
||||
using var response = await dlClient.GetAsync(upgradeProperties.ZipUrl, HttpCompletionOption.ResponseHeadersRead);
|
||||
using var dlStream = await response.Content.ReadAsStreamAsync();
|
||||
using var tempFile = File.OpenWrite(zipFile);
|
||||
|
||||
int read;
|
||||
long totalRead = 0;
|
||||
Memory<byte> buffer = new byte[128 * 1024];
|
||||
long contentLength = response.Content.Headers.ContentLength ?? 0;
|
||||
|
||||
while ((read = await dlStream.ReadAsync(buffer)) > 0)
|
||||
{
|
||||
await tempFile.WriteAsync(buffer[..read]);
|
||||
totalRead += read;
|
||||
|
||||
OnDownloadProgress(
|
||||
new DownloadProgress
|
||||
{
|
||||
BytesReceived = totalRead,
|
||||
TotalBytesToReceive = contentLength,
|
||||
ProgressPercentage = contentLength > 0 ? 100d * totalRead / contentLength : 0
|
||||
});
|
||||
}
|
||||
|
||||
return zipFile;
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
var message = $"Failed to download the upgrade: {upgradeProperties.ZipUrl}";
|
||||
Serilog.Log.Logger.Error(ex, message);
|
||||
OnUpgradeFailed(message, ex);
|
||||
return null;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public class MockUpgrader : UpgraderBase
|
||||
{
|
||||
public int DownloadTimeMs { get; set; } = 3000;
|
||||
public int DownloadSizeInBytes { get; set; } = 150 * 1024 * 1024;
|
||||
public bool CheckForUpgradeSucceeds { get; set; } = true;
|
||||
public bool DownloadUpgradeSucceeds { get; set; } = true;
|
||||
public string? MockUpgradeBundle { get; set; }
|
||||
|
||||
protected override Task<UpgradeProperties?> CheckForUpgradeAsync()
|
||||
{
|
||||
if (!CheckForUpgradeSucceeds)
|
||||
{
|
||||
OnUpgradeFailed("Mock Check For Upgrade Failed", null);
|
||||
return Task.FromResult<UpgradeProperties?>(null);
|
||||
}
|
||||
return Task.FromResult<UpgradeProperties?>(new UpgradeProperties(
|
||||
"http://fake.url/to/bundle.zip",
|
||||
"",
|
||||
Path.GetFileName(MockUpgradeBundle),
|
||||
LibationScaffolding.BuildVersion,
|
||||
"<RELEASE NOTES>"));
|
||||
}
|
||||
|
||||
protected override async Task<string?> DownloadUpgradeAsync(UpgradeProperties upgradeProperties)
|
||||
{
|
||||
if (!File.Exists(MockUpgradeBundle))
|
||||
{
|
||||
OnUpgradeFailed("Mock Download bundle file not found", null);
|
||||
return null;
|
||||
}
|
||||
|
||||
for (int i = 1; i <= 100; i++)
|
||||
{
|
||||
await Task.Delay(DownloadTimeMs / 100);
|
||||
OnDownloadProgress(new()
|
||||
{
|
||||
BytesReceived = DownloadSizeInBytes / 100,
|
||||
ProgressPercentage = i,
|
||||
TotalBytesToReceive = DownloadSizeInBytes * i / 100
|
||||
});
|
||||
}
|
||||
if (!DownloadUpgradeSucceeds)
|
||||
{
|
||||
OnUpgradeFailed("Mock Download Upgrade Failed", null);
|
||||
return null;
|
||||
}
|
||||
|
||||
return MockUpgradeBundle;
|
||||
}
|
||||
}
|
||||
|
||||
public abstract class UpgraderBase
|
||||
{
|
||||
public event EventHandler? DownloadBegin;
|
||||
public event EventHandler<DownloadProgress>? DownloadProgress;
|
||||
public event EventHandler<bool>? DownloadCompleted;
|
||||
public event EventHandler<string>? UpgradeFailed;
|
||||
|
||||
protected void OnDownloadProgress(DownloadProgress args) => DownloadProgress?.Invoke(this, args);
|
||||
protected void OnUpgradeFailed(string message, Exception? ex)
|
||||
=> UpgradeFailed?.Invoke(this, (message + Environment.NewLine + Environment.NewLine + ex?.Message).Trim());
|
||||
protected abstract Task<UpgradeProperties?> CheckForUpgradeAsync();
|
||||
protected abstract Task<string?> DownloadUpgradeAsync(UpgradeProperties upgradeProperties);
|
||||
|
||||
public async Task CheckForUpgradeAsync(Func<UpgradeEventArgs, Task> upgradeAvailableHandler)
|
||||
{
|
||||
try
|
||||
{
|
||||
var upgradeProperties = await Task.Run(LibationScaffolding.GetLatestRelease);
|
||||
if (upgradeProperties is null) return;
|
||||
if (await CheckForUpgradeAsync() is not UpgradeProperties upgradeProperties)
|
||||
return;
|
||||
|
||||
const string ignoreUpgrade = "IgnoreUpgrade";
|
||||
var config = Configuration.Instance;
|
||||
@ -73,7 +203,7 @@ namespace LibationUiBase
|
||||
|
||||
//Download the upgrade file in the background,
|
||||
DownloadBegin?.Invoke(this, EventArgs.Empty);
|
||||
string upgradeBundle = await DownloadUpgradeAsync(upgradeProperties);
|
||||
string? upgradeBundle = await DownloadUpgradeAsync(upgradeProperties);
|
||||
|
||||
if (string.IsNullOrEmpty(upgradeBundle) || !File.Exists(upgradeBundle))
|
||||
{
|
||||
@ -91,57 +221,9 @@ namespace LibationUiBase
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
Serilog.Log.Logger.Error(ex, "An error occured while checking for app upgrades.");
|
||||
}
|
||||
}
|
||||
|
||||
private async Task<string> DownloadUpgradeAsync(UpgradeProperties upgradeProperties)
|
||||
{
|
||||
if (upgradeProperties.ZipUrl is null)
|
||||
{
|
||||
Serilog.Log.Logger.Warning("Download link for new version not found");
|
||||
return null;
|
||||
}
|
||||
|
||||
//Silently download the upgrade in the background, save it to a temp file.
|
||||
|
||||
var zipFile = Path.Combine(Path.GetTempPath(), Path.GetFileName(upgradeProperties.ZipUrl));
|
||||
|
||||
Serilog.Log.Logger.Information($"Downloading {zipFile}");
|
||||
|
||||
try
|
||||
{
|
||||
using var dlClient = new HttpClient();
|
||||
using var response = await dlClient.GetAsync(upgradeProperties.ZipUrl, HttpCompletionOption.ResponseHeadersRead);
|
||||
using var dlStream = await response.Content.ReadAsStreamAsync();
|
||||
using var tempFile = File.OpenWrite(zipFile);
|
||||
|
||||
int read;
|
||||
long totalRead = 0;
|
||||
Memory<byte> buffer = new byte[128 * 1024];
|
||||
long contentLength = response.Content.Headers.ContentLength ?? 0;
|
||||
|
||||
while ((read = await dlStream.ReadAsync(buffer)) > 0)
|
||||
{
|
||||
await tempFile.WriteAsync(buffer[..read]);
|
||||
totalRead += read;
|
||||
|
||||
DownloadProgress?.Invoke(
|
||||
this,
|
||||
new DownloadProgress
|
||||
{
|
||||
BytesReceived = totalRead,
|
||||
TotalBytesToReceive = contentLength,
|
||||
ProgressPercentage = contentLength > 0 ? 100d * totalRead / contentLength : 0
|
||||
});
|
||||
}
|
||||
|
||||
return zipFile;
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
Serilog.Log.Logger.Error(ex, "Failed to download the upgrade: {bundle}", upgradeProperties.ZipUrl);
|
||||
return null;
|
||||
var message = "An error occurred while checking for app upgrades.";
|
||||
Serilog.Log.Logger.Error(ex, message);
|
||||
OnUpgradeFailed(message, ex);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@ -1,6 +1,7 @@
|
||||
using LibationUiBase;
|
||||
using LibationWinForms.Dialogs;
|
||||
using System.Threading.Tasks;
|
||||
using System.Windows.Forms;
|
||||
|
||||
namespace LibationWinForms
|
||||
{
|
||||
@ -9,22 +10,25 @@ namespace LibationWinForms
|
||||
private void Configure_Upgrade()
|
||||
{
|
||||
setProgressVisible(false);
|
||||
#if !DEBUG
|
||||
#pragma warning disable CS8321 // Local function is declared but never used
|
||||
Task upgradeAvailable(UpgradeEventArgs e)
|
||||
{
|
||||
var notificationResult = new UpgradeNotificationDialog(e.UpgradeProperties).ShowDialog(this);
|
||||
|
||||
e.Ignore = notificationResult == System.Windows.Forms.DialogResult.Ignore;
|
||||
e.InstallUpgrade = notificationResult == System.Windows.Forms.DialogResult.Yes;
|
||||
e.Ignore = notificationResult == DialogResult.Ignore;
|
||||
e.InstallUpgrade = notificationResult == DialogResult.Yes;
|
||||
|
||||
return Task.CompletedTask;
|
||||
}
|
||||
#pragma warning restore CS8321 // Local function is declared but never used
|
||||
|
||||
var upgrader = new Upgrader();
|
||||
upgrader.DownloadProgress += (_, e) => Invoke(() => upgradePb.Value = int.Max(0, int.Min(100, (int)(e.ProgressPercentage ?? 0))));
|
||||
upgrader.DownloadBegin += (_, _) => Invoke(() => setProgressVisible(true));
|
||||
upgrader.DownloadCompleted += (_, _) => Invoke(() => setProgressVisible(false));
|
||||
upgrader.UpgradeFailed += (_, message) => Invoke(() => { setProgressVisible(false); MessageBox.Show(this, message, "Upgrade Failed", MessageBoxButtons.OK, MessageBoxIcon.Error); });
|
||||
|
||||
#if !DEBUG
|
||||
Shown += async (_, _) => await upgrader.CheckForUpgradeAsync(upgradeAvailable);
|
||||
#endif
|
||||
}
|
||||
|
||||
@ -30,11 +30,12 @@ namespace WindowsConfigApp
|
||||
|
||||
public void InstallUpgrade(string upgradeBundle)
|
||||
{
|
||||
const string ExtractorExeName = "ZipExtractor.exe";
|
||||
var thisExe = Environment.ProcessPath;
|
||||
var thisDir = Path.GetDirectoryName(thisExe);
|
||||
var zipExtractor = Path.Combine(Path.GetTempPath(), "ZipExtractor.exe");
|
||||
var zipExtractor = Path.Combine(Path.GetTempPath(), ExtractorExeName);
|
||||
|
||||
File.Copy("ZipExtractor.exe", zipExtractor, overwrite: true);
|
||||
File.Copy(Path.Combine(thisDir, ExtractorExeName), zipExtractor, overwrite: true);
|
||||
|
||||
RunAsRoot(zipExtractor,
|
||||
$"--input {upgradeBundle.SurroundWithQuotes()} " +
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user