Files
Filtration/Filtration/Services/UpdateService.cs

289 lines
9.0 KiB
C#

using System;
using System.Linq;
using System.Text.RegularExpressions;
using System.Threading.Tasks;
using Filtration.Enums;
using Filtration.Properties;
using NLog;
using Squirrel;
namespace Filtration.Services
{
public enum UpdateStatus
{
NoUpdateAvailable,
CheckingForUpdate,
UpdateAvailable,
Downloading,
ReadyToApplyUpdate,
Updating,
UpdateComplete,
Error
}
internal class UpdateStatusChangedEventArgs : EventArgs
{
public UpdateStatusChangedEventArgs(UpdateStatus updateStatus)
{
UpdateStatus = updateStatus;
}
public UpdateStatus UpdateStatus { get; }
}
internal class UpdateProgressChangedEventArgs : EventArgs
{
public UpdateProgressChangedEventArgs(int progress)
{
Progress = progress;
}
public int Progress { get; }
}
internal interface IUpdateService
{
event EventHandler<UpdateStatusChangedEventArgs> UpdateStatusChanged;
event EventHandler<UpdateProgressChangedEventArgs> UpdateProgressChanged;
UpdateStatus UpdateStatus { get; }
string LatestReleaseNotes { get; }
string LatestReleaseVersion { get; }
Task CheckForUpdatesAsync();
Task DownloadUpdatesAsync();
Task ApplyUpdatesAsync();
void RestartAfterUpdate();
}
internal class UpdateService : IUpdateService
{
private static readonly Logger Logger = LogManager.GetCurrentClassLogger();
private const string _localUpdatePath = @"C:\Repos\Filtration\Releases";
private readonly ISettingsService _settingsService;
private readonly UpdateSource _updateSource = UpdateSource.GitHub;
private ReleaseEntry _latestRelease;
private UpdateInfo _updates;
private bool _downloadPrereleaseUpdates;
private UpdateStatus _updateStatus;
public UpdateService(ISettingsService settingsService)
{
_settingsService = settingsService;
UpdateStatus = UpdateStatus.NoUpdateAvailable;
}
public event EventHandler<UpdateStatusChangedEventArgs> UpdateStatusChanged;
public event EventHandler<UpdateProgressChangedEventArgs> UpdateProgressChanged;
public UpdateStatus UpdateStatus
{
get => _updateStatus;
private set
{
_updateStatus = value;
UpdateStatusChanged?.Invoke(this, new UpdateStatusChangedEventArgs(value));
}
}
public string LatestReleaseNotes { get; private set; }
public string LatestReleaseVersion { get; private set; }
public async Task CheckForUpdatesAsync()
{
if (UpdateStatus != UpdateStatus.NoUpdateAvailable)
{
throw new InvalidOperationException();
}
Logger.Debug("Checking for update...");
UpdateStatus = UpdateStatus.CheckingForUpdate;
try
{
_downloadPrereleaseUpdates = Settings.Default.DownloadPrereleaseUpdates;
#if DEBUG
_downloadPrereleaseUpdates = true;
#endif
async Task CheckForUpdatesAsync(IUpdateManager updateManager)
{
_updates = await updateManager.CheckForUpdate(progress: progress => UpdateProgressChanged?.Invoke(this, new UpdateProgressChangedEventArgs(progress)));
}
if (_updateSource == UpdateSource.GitHub)
{
using (var updateManager = await UpdateManager.GitHubUpdateManager("https://github.com/ben-wallis/Filtration", prerelease: _downloadPrereleaseUpdates))
{
await CheckForUpdatesAsync(updateManager);
}
}
else
{
using (var updateManager = new UpdateManager(_localUpdatePath))
{
await CheckForUpdatesAsync(updateManager);
}
}
}
catch (Exception e)
{
Logger.Error(e);
UpdateStatus = UpdateStatus.Error;
return;
}
if (_updates.ReleasesToApply.Any())
{
_latestRelease = _updates.ReleasesToApply.OrderBy(x => x.Version).Last();
LatestReleaseVersion = _latestRelease.Version.ToString();
Logger.Debug($"Update found ({LatestReleaseVersion})");
UpdateStatus = UpdateStatus.UpdateAvailable;
}
else
{
UpdateStatus = UpdateStatus.NoUpdateAvailable;
Logger.Debug("No update available");
}
}
private static string ProcessReleaseNotes(string rawReleaseNotes)
{
var regex = new Regex(@"<!\[CDATA\[(.*)]]>", RegexOptions.Singleline);
var matches = regex.Match(rawReleaseNotes);
if (matches.Success)
{
var releaseNotes = matches.Groups[1].Value;
return "<font face=\"Segoe UI\" size=\"2\">" + releaseNotes + "</font>";
}
return string.Empty;
}
public async Task DownloadUpdatesAsync()
{
if (_updates == null || UpdateStatus != UpdateStatus.UpdateAvailable)
{
throw new InvalidOperationException();
}
UpdateStatus = UpdateStatus.Downloading;
async Task DownloadUpdatesAsync(IUpdateManager updateManager)
{
Logger.Debug("Downloading update...");
await updateManager.DownloadReleases(_updates.ReleasesToApply, OnProgressChanged);
Logger.Debug("Fetching release notes...");
var releaseNotes = _updates.FetchReleaseNotes();
LatestReleaseNotes = ProcessReleaseNotes(releaseNotes[_latestRelease]);
}
try
{
if (_updateSource == UpdateSource.GitHub)
{
using (var updateManager = await UpdateManager.GitHubUpdateManager("https://github.com/ben-wallis/Filtration", prerelease: _downloadPrereleaseUpdates))
{
await DownloadUpdatesAsync(updateManager);
}
}
else
{
using (var updateManager = new UpdateManager(_localUpdatePath))
{
await DownloadUpdatesAsync(updateManager);
}
}
}
catch (Exception e)
{
Logger.Error(e);
UpdateStatus = UpdateStatus.Error;
return;
}
UpdateStatus = UpdateStatus.ReadyToApplyUpdate;
}
public async Task ApplyUpdatesAsync()
{
if (UpdateStatus != UpdateStatus.ReadyToApplyUpdate)
{
throw new InvalidOperationException();
}
UpdateStatus = UpdateStatus.Updating;
// Back up the current user settings before updating as updating using Squirrel
// wipes out user settings due to the application directory changing with each update
_settingsService.BackupSettings();
Logger.Debug("Applying update...");
try
{
if (_updateSource == UpdateSource.GitHub)
{
using (var mgr = await UpdateManager.GitHubUpdateManager("https://github.com/ben-wallis/Filtration", prerelease: _downloadPrereleaseUpdates))
{
await mgr.ApplyReleases(_updates, OnProgressChanged);
}
}
else
{
using (var updateManager = new UpdateManager(_localUpdatePath))
{
await updateManager.ApplyReleases(_updates, OnProgressChanged);
}
}
}
catch (Exception e)
{
Logger.Error(e);
UpdateStatus = UpdateStatus.Error;
return;
}
Logger.Debug("Update complete");
UpdateStatus = UpdateStatus.UpdateComplete;
}
public void RestartAfterUpdate()
{
if (UpdateStatus != UpdateStatus.UpdateComplete)
{
throw new InvalidOperationException();
}
try
{
UpdateManager.RestartApp();
}
catch (Exception e)
{
Logger.Error(e);
UpdateStatus = UpdateStatus.Error;
}
}
private void OnProgressChanged(int progress)
{
UpdateProgressChanged?.Invoke(this, new UpdateProgressChangedEventArgs(progress));
}
}
}