Iceshrimp.NET/Iceshrimp.Frontend/Core/Services/UpdateService.cs

166 lines
4.4 KiB
C#

using System.Diagnostics;
using Iceshrimp.Shared.Helpers;
using Iceshrimp.Shared.Schemas.Web;
using Microsoft.JSInterop;
namespace Iceshrimp.Frontend.Core.Services;
internal class UpdateService
{
private readonly ApiService _api;
private readonly ILogger<UpdateService> _logger;
private readonly Lazy<Task<IJSObjectReference>> _moduleTask;
private UpdateStates _updateState;
public EventHandler<UpdateStates>? UpdateStatusEvent { get; set; }
public UpdateStates UpdateState
{
get => _updateState;
private set
{
UpdateStatusEvent?.Invoke(this, value);
_logger.LogInformation($"Invoked Update Status Event: {value}");
_updateState = value;
}
}
private VersionInfo FrontendVersion { get; } = VersionHelpers.VersionInfo.Value;
public VersionResponse? BackendVersion { get; private set; }
// ReSharper disable once UnusedAutoPropertyAccessor.Local
private Timer CheckUpdateTimer { get; set; }
private Timer? CheckWaitingTimer { get; set; }
public UpdateService(
ApiService api, ILogger<UpdateService> logger, IJSRuntime js
)
{
_api = api;
_logger = logger;
_moduleTask = new Lazy<Task<IJSObjectReference>>(() => js.InvokeAsync<IJSObjectReference>(
"import",
"./Core/Services/UpdateService.cs.js")
.AsTask());
CheckUpdateTimer = new Timer(UpdateCheckCallback, null, TimeSpan.Zero, TimeSpan.FromSeconds(60));
_ = RegisterSwUpdateCallbackAsync();
}
private async Task RegisterSwUpdateCallbackAsync()
{
var module = await _moduleTask.Value;
var objRef = DotNetObjectReference.Create(this);
await module.InvokeAsync<string>("RegisterSWUpdateCallback", objRef);
}
[JSInvokable]
public void NewServiceWorker()
{
UpdateState = UpdateStates.UpdateInstalling;
CheckWaitingTimer = new Timer(CheckWaitingCallback, null, TimeSpan.Zero, TimeSpan.FromSeconds(2));
}
private async Task ServiceWorkerCheckWaitingAsync()
{
var res = await ServiceWorkerCheckStateAsync();
if (res == ServiceWorkerState.Waiting)
{
CheckWaitingTimer?.Change(Timeout.Infinite, Timeout.Infinite);
CheckWaitingTimer?.Dispose();
UpdateState = UpdateStates.UpdateInstalled;
}
}
private void UpdateCheckCallback(object? caller)
{
_ = CheckVersionAsync();
}
private void CheckWaitingCallback(object? caller)
{
_ = ServiceWorkerCheckWaitingAsync();
}
public async Task ServiceWorkerUpdateAsync()
{
var module = await _moduleTask.Value;
var res = await module.InvokeAsync<string?>("ServiceWorkerUpdate");
if (res is null) return;
UpdateState = res switch
{
"installing" => UpdateStates.UpdateInstalling,
"waiting" => UpdateStates.UpdateInstalled,
"active" => UpdateStates.NoUpdate,
null => UpdateStates.Error,
_ => throw new UnreachableException()
};
}
private async Task<ServiceWorkerState> ServiceWorkerCheckStateAsync()
{
var module = await _moduleTask.Value;
var res = await module.InvokeAsync<string>("ServiceWorkerCheckRegistration");
_logger.LogTrace($"aService worker state: {res}");
return res switch
{
"installing" => ServiceWorkerState.Installing,
"waiting" => ServiceWorkerState.Waiting,
"active" => ServiceWorkerState.Active,
null => ServiceWorkerState.Error,
_ => throw new UnreachableException()
};
}
public async Task<bool> ServiceWorkerSkipWaitingAsync()
{
var module = await _moduleTask.Value;
var res = await module.InvokeAsync<bool?>("ServiceWorkerSkipWaiting");
if (res is null) throw new Exception("Error occured while updating service worker.");
return (bool)res;
}
private async Task<VersionResponse?> GetVersionAsync()
{
try
{
var backendVersion = await _api.Version.GetVersionAsync();
_logger.LogInformation("Successfully fetched backend version.");
return backendVersion;
}
catch (Exception e)
{
_logger.LogError(e, "Failed to fetch backend version.");
return null;
}
}
private async Task CheckVersionAsync()
{
var version = await GetVersionAsync();
if (version is null) return;
BackendVersion = version;
if (version.Version != FrontendVersion.Version)
{
UpdateState = UpdateStates.UpdateAvailable;
await ServiceWorkerUpdateAsync();
}
}
internal enum UpdateStates
{
NoUpdate,
UpdateAvailable,
UpdateInstalling,
UpdateInstalled,
Error
}
private enum ServiceWorkerState
{
Installing,
Waiting,
Active,
Error
}
}