Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
17 changes: 12 additions & 5 deletions Pointframe.Tests/Services/AutoUpdateServiceTests.cs
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
using System.Net.Http;
using Microsoft.Extensions.Hosting;
using Microsoft.Extensions.Logging.Abstractions;
using Moq;
using Pointframe.Models;
Expand All @@ -21,7 +22,8 @@ private static AutoUpdateService CreateService(
Mock<IUpdateService> updateService,
Mock<IUserSettingsService> userSettings,
Mock<IUpdateDownloadService> downloadService,
Mock<IMessageBoxService> messageBox)
Mock<IMessageBoxService> messageBox,
TimeSpan? settingsPollInterval = null)
{
return new AutoUpdateService(
eventAggregator,
Expand All @@ -30,7 +32,8 @@ private static AutoUpdateService CreateService(
downloadService.Object,
messageBox.Object,
NullLogger<AutoUpdateService>.Instance,
Mock.Of<ITelemetryService>());
Mock.Of<ITelemetryService>(),
settingsPollInterval);
}

private static Mock<IUserSettingsService> SettingsMock(UpdateCheckInterval interval = UpdateCheckInterval.EveryTwoHours)
Expand Down Expand Up @@ -308,9 +311,10 @@ public async Task ExecuteAsync_IntervalNever_DoesNotUpdateLastCheckTimestamp()
}

[Fact]
public async Task ExecuteAsync_RuntimeIntervalChangeToNever_StopsFurtherChecks()
public async Task ExecuteAsync_RuntimeIntervalChangeToNever_StopsPeriodicLoopPromptly()
{
var callCount = 0;
var startupCheckCompleted = new TaskCompletionSource(TaskCreationOptions.RunContinuationsAsynchronously);
var settings = new UserSettings
{
AutoUpdateCheckInterval = UpdateCheckInterval.EveryTwoHours,
Expand All @@ -330,6 +334,7 @@ public async Task ExecuteAsync_RuntimeIntervalChangeToNever_StopsFurtherChecks()
if (callCount == 1)
{
settings.AutoUpdateCheckInterval = UpdateCheckInterval.Never;
startupCheckCompleted.TrySetResult();
}

return NoUpdate;
Expand All @@ -340,10 +345,12 @@ public async Task ExecuteAsync_RuntimeIntervalChangeToNever_StopsFurtherChecks()
updateService,
settingsMock,
new Mock<IUpdateDownloadService>(),
new Mock<IMessageBoxService>());
new Mock<IMessageBoxService>(),
TimeSpan.FromMilliseconds(10));

await sut.StartAsync(CancellationToken.None);
await Task.Delay(100);
await startupCheckCompleted.Task.WaitAsync(TimeSpan.FromSeconds(5));
await sut.ExecuteTask!.WaitAsync(TimeSpan.FromSeconds(5));
await sut.StopAsync(CancellationToken.None);

Assert.Equal(1, callCount);
Expand Down
72 changes: 58 additions & 14 deletions Pointframe/Services/Update/AutoUpdateService.cs
Original file line number Diff line number Diff line change
Expand Up @@ -5,13 +5,15 @@ namespace Pointframe.Services;

public sealed class AutoUpdateService : BackgroundService, IAutoUpdateService
{
private static readonly TimeSpan DefaultSettingsPollInterval = TimeSpan.FromSeconds(1);
private readonly IEventAggregator _eventAggregator;
private readonly IUpdateService _updateService;
private readonly IUserSettingsService _userSettings;
private readonly IUpdateDownloadService _downloadService;
private readonly IMessageBoxService _messageBox;
private readonly ILogger<AutoUpdateService> _logger;
private readonly ITelemetryService _telemetry;
private readonly TimeSpan _settingsPollInterval;

public AutoUpdateService(
IEventAggregator eventAggregator,
Expand All @@ -20,7 +22,8 @@ public AutoUpdateService(
IUpdateDownloadService downloadService,
IMessageBoxService messageBox,
ILogger<AutoUpdateService> logger,
ITelemetryService telemetry)
ITelemetryService telemetry,
TimeSpan? settingsPollInterval = null)
{
_eventAggregator = eventAggregator;
_updateService = updateService;
Expand All @@ -29,6 +32,7 @@ public AutoUpdateService(
_messageBox = messageBox;
_logger = logger;
_telemetry = telemetry;
_settingsPollInterval = settingsPollInterval.GetValueOrDefault(DefaultSettingsPollInterval);
}

protected override async Task ExecuteAsync(CancellationToken stoppingToken)
Expand All @@ -52,23 +56,12 @@ protected override async Task ExecuteAsync(CancellationToken stoppingToken)

UpdateLastCheckedUtc();

var timerInterval = GetTimerInterval(interval);
_logger.LogInformation(
"Auto-update: periodic loop started (interval = {Interval}, every {IntervalTime})",
interval,
timerInterval);
_logger.LogInformation("Auto-update: periodic loop started");

try
{
using var timer = new PeriodicTimer(timerInterval);
while (await timer.WaitForNextTickAsync(stoppingToken).ConfigureAwait(false))
while (await WaitForNextCheckAsync(stoppingToken).ConfigureAwait(false))
{
if (_userSettings.Current.AutoUpdateCheckInterval == UpdateCheckInterval.Never)
{
_logger.LogInformation("Auto-update: periodic loop stopped (interval changed to Never)");
break;
}

_logger.LogInformation("Auto-update: running periodic check");
try
{
Expand Down Expand Up @@ -134,6 +127,57 @@ private void UpdateLastCheckedUtc()
});
}

private async Task<bool> WaitForNextCheckAsync(CancellationToken stoppingToken)
{
while (!stoppingToken.IsCancellationRequested)
{
var interval = _userSettings.Current.AutoUpdateCheckInterval;
if (interval == UpdateCheckInterval.Never)
{
_logger.LogInformation("Auto-update: periodic loop stopped (interval changed to Never)");
return false;
}

var timerInterval = GetTimerInterval(interval);
_logger.LogInformation(
"Auto-update: waiting for next check (interval = {Interval}, every {IntervalTime})",
interval,
timerInterval);

var remaining = timerInterval;
while (remaining > TimeSpan.Zero)
{
var delay = TimeSpan.FromTicks(Math.Min(remaining.Ticks, _settingsPollInterval.Ticks));
await Task.Delay(delay, stoppingToken).ConfigureAwait(false);

var currentInterval = _userSettings.Current.AutoUpdateCheckInterval;
if (currentInterval == UpdateCheckInterval.Never)
{
_logger.LogInformation("Auto-update: periodic loop stopped (interval changed to Never)");
return false;
}

if (currentInterval != interval)
{
_logger.LogInformation(
"Auto-update: periodic interval changed from {PreviousInterval} to {CurrentInterval}",
interval,
currentInterval);
break;
}

remaining -= delay;
}

if (remaining <= TimeSpan.Zero)
{
return true;
}
}

return false;
}

private static TimeSpan GetTimerInterval(UpdateCheckInterval interval) =>
interval switch
{
Expand Down
26 changes: 24 additions & 2 deletions Pointframe/Views/OverlayWindow.xaml.cs
Original file line number Diff line number Diff line change
Expand Up @@ -485,6 +485,17 @@ private async Task DoLassoOcr(Rect lassoRect)

private async void ShowOcrToast(string message)
{
if (Dispatcher.HasShutdownStarted || Dispatcher.HasShutdownFinished)
{
return;
}

if (!Dispatcher.CheckAccess())
{
await Dispatcher.InvokeAsync(() => ShowOcrToast(message));
return;
}
Comment on lines +493 to +497

OcrToastText.Text = message;
OcrToast.Measure(new Size(double.PositiveInfinity, double.PositiveInfinity));
var toastSize = OcrToast.DesiredSize;
Expand All @@ -500,10 +511,21 @@ private async void ShowOcrToast(string message)
Canvas.SetTop(OcrToast, selection.Y + ((selection.Height - toastSize.Height) / 2d));
}

if (Dispatcher.HasShutdownStarted || Dispatcher.HasShutdownFinished)
{
return;
}

OcrToast.Visibility = Visibility.Visible;

await Task.Delay(1500);
OcrToast.Visibility = Visibility.Collapsed;
await Task.Delay(1500).ConfigureAwait(false);

if (Dispatcher.HasShutdownStarted || Dispatcher.HasShutdownFinished)
{
return;
}

await Dispatcher.InvokeAsync(() => OcrToast.Visibility = Visibility.Collapsed, DispatcherPriority.Normal);
}

private void PositionOcrToastNearActionBar(Size toastSize)
Expand Down