Files
EchoHub/src/EchoHub.Client/Services/UpdateChecker.cs
T

190 lines
5.9 KiB
C#

using AlwaysUpToDate;
using EchoHub.Client.UI.Dialogs;
using Serilog;
using Terminal.Gui.App;
using Terminal.Gui.Views;
namespace EchoHub.Client.Services;
public sealed class UpdateChecker : IDisposable
{
private const string ManifestUrl = "https://echohub.voidcube.cloud/api/app/version";
private readonly Updater _updater;
private readonly IApplication _app;
private UpdateProgressDialog? _progressDialog;
private bool _manualCheck;
public static string CurrentVersion => typeof(UpdateChecker).Assembly.GetName().Version?.ToString(3) ?? "0.0.0";
public UpdateChecker(IApplication app)
{
_app = app;
_updater = new Updater(TimeSpan.FromHours(1), ManifestUrl, false);
_updater.UpdateAvailable += OnUpdateAvailable;
_updater.ProgressChanged += OnProgressChanged;
_updater.UpdateStarted += OnUpdateStarted;
_updater.NoUpdateAvailable += OnNoUpdateAvailable;
_updater.OnException += OnException;
}
public void Start()
{
#if RELEASE
_updater.Start();
#endif
}
public async Task CheckNowAsync()
{
_manualCheck = true;
try
{
await _updater.CheckForUpdateAsync();
}
finally
{
_manualCheck = false;
}
}
private async void OnUpdateAvailable(string version, string changelogUrl)
{
Log.Information("Update available: v{Version}", version);
_app.Invoke(() =>
{
var confirmed = UpdateConfirmDialog.Show(_app, CurrentVersion, version);
if (confirmed)
{
_progressDialog = new UpdateProgressDialog(_app, version);
_ = Task.Run(async () =>
{
// Create backup before the update starts
try
{
_app.Invoke(() => _progressDialog?.UpdateProgress(0f, "Creating backup..."));
UpdateBackupService.CreateBackup();
}
catch (Exception ex)
{
Log.Error(ex, "Failed to create pre-update backup");
var proceed = false;
_app.Invoke(() =>
{
proceed = MessageBox.Query(
_app,
"Backup Warning",
$"Could not create backup: {ex.Message}\n\nContinue update without backup?",
"Continue", "Cancel") == 0;
});
if (!proceed)
{
_app.Invoke(() =>
{
_progressDialog?.Close();
_progressDialog = null;
});
return;
}
}
_app.Invoke(() => _progressDialog?.UpdateProgress(0f, "Downloading update..."));
await _updater.UpdateAsync();
});
_progressDialog.Show();
}
});
}
private void OnProgressChanged(UpdateStep step, long itemsProcessed, long? totalItems, double? progressPercentage)
{
var fraction = progressPercentage.HasValue ? (float)(progressPercentage.Value / 100.0) : 0f;
var statusText = $"{step}: {itemsProcessed}/{totalItems ?? 0} ({progressPercentage ?? 0:F0}%)";
if (!progressPercentage.HasValue)
{
statusText = $"{step}...";
}
_progressDialog?.UpdateProgress(fraction, statusText);
}
private void OnUpdateStarted(string version)
{
Log.Information("Update started: v{Version}", version);
}
private void OnNoUpdateAvailable()
{
Log.Debug("No update available");
if (_manualCheck)
{
_app.Invoke(() =>
{
MessageBox.Query(_app, "Check for Updates", $"You are already on the latest version (v{CurrentVersion}).", "OK");
});
}
}
private void OnException(Exception exception)
{
Log.Error(exception, "Update failed");
_app.Invoke(() =>
{
_progressDialog?.Close();
_progressDialog = null;
if (UpdateBackupService.BackupExists())
{
var restore = MessageBox.Query(
_app,
"Update Failed",
$"The update failed: {exception.Message}\n\n"
+ "A backup of the previous version is available.\nRestore now? (The app will restart.)",
"Restore", "Cancel");
if (restore == 0)
{
try
{
UpdateBackupService.RestoreBackup();
// RestoreBackup calls Environment.Exit(0)
}
catch (Exception restoreEx)
{
Log.Error(restoreEx, "Backup restoration failed");
MessageBox.ErrorQuery(_app, "Restore Failed",
$"Could not restore backup: {restoreEx.Message}\n\nYou may need to re-download the application.", "OK");
}
}
}
else
{
MessageBox.ErrorQuery(_app, "Update Failed",
$"The update failed: {exception.Message}\n\nYou may need to re-download the application.", "OK");
}
});
}
public void Dispose()
{
_updater.UpdateAvailable -= OnUpdateAvailable;
_updater.ProgressChanged -= OnProgressChanged;
_updater.UpdateStarted -= OnUpdateStarted;
_updater.NoUpdateAvailable -= OnNoUpdateAvailable;
_updater.OnException -= OnException;
}
}