< Summary

Information
Class: SwitchBlade.Services.BackgroundPollingService
Assembly: SwitchBlade
File(s): D:\a\switchblade\switchblade\Services\BackgroundPollingService.cs
Tag: 203_23722840422
Line coverage
100%
Covered lines: 70
Uncovered lines: 0
Coverable lines: 70
Total lines: 133
Line coverage: 100%
Branch coverage
100%
Covered branches: 16
Total branches: 16
Branch coverage: 100%
Method coverage

Feature is only available for sponsors

Upgrade to PRO version

Metrics

MethodBranch coverage Crap Score Cyclomatic complexity Line coverage
.ctor(...)100%44100%
StartPolling()100%44100%
StopPolling()100%22100%
OnSettingsChanged()100%11100%
PollingLoop()100%44100%
<PollingLoop()100%11100%
Dispose()100%22100%

File(s)

D:\a\switchblade\switchblade\Services\BackgroundPollingService.cs

#LineLine coverage
 1using System;
 2using System.Threading;
 3using System.Threading.Tasks;
 4using SwitchBlade.Contracts;
 5using SwitchBlade.Core;
 6
 7namespace SwitchBlade.Services
 8{
 9    /// <summary>
 10    /// Service that polls all window providers in the background at a configurable interval.
 11    /// Uses Modern .NET 6+ PeriodicTimer for efficient async polling.
 12    /// </summary>
 13    public class BackgroundPollingService : IDisposable
 14    {
 15        private readonly ISettingsService _settingsService;
 16        private readonly IDispatcherService _dispatcherService;
 17        private readonly IWorkstationService _workstationService;
 18        private readonly Func<Task> _refreshAction;
 19        // Factory for creating timers, essential for unit testing to avoid real-time delays
 20        private readonly Func<TimeSpan, IPeriodicTimer> _periodicTimerFactory;
 21
 22        private CancellationTokenSource? _cts;
 23        private Task? _pollingTask;
 24        private bool _disposed;
 25
 926        public BackgroundPollingService(
 927            ISettingsService settingsService,
 928            IDispatcherService dispatcherService,
 929            Func<Task> refreshAction,
 930            IWorkstationService? workstationService = null,
 931            Func<TimeSpan, IPeriodicTimer>? periodicTimerFactory = null)
 932        {
 933            _settingsService = settingsService;
 934            _dispatcherService = dispatcherService;
 935            _refreshAction = refreshAction;
 936            _workstationService = workstationService ?? new WorkstationService();
 937            _periodicTimerFactory = periodicTimerFactory ?? (interval => new SystemPeriodicTimer(interval));
 38
 39            // Subscribe to settings changes to dynamically update timer
 940            _settingsService.SettingsChanged += OnSettingsChanged;
 41
 42            // Initialize timer based on current settings
 943            StartPolling();
 944        }
 45
 46        private void StartPolling()
 1147        {
 48            // Cancel previous polling if any
 1149            StopPolling();
 50
 1151            if (!_settingsService.Settings.EnableBackgroundPolling)
 352            {
 353                Logger.Log("BackgroundPollingService: Polling disabled.");
 354                return;
 55            }
 56
 857            int intervalMs = _settingsService.Settings.BackgroundPollingIntervalSeconds * 1000;
 958            if (intervalMs < 1000) intervalMs = 1000; // Minimum 1 second
 59
 860            _cts = new CancellationTokenSource();
 861            _pollingTask = PollingLoop(TimeSpan.FromMilliseconds(intervalMs), _cts.Token);
 62
 863            Logger.Log($"BackgroundPollingService: Polling enabled with interval {intervalMs}ms.");
 1164        }
 65
 66        private void StopPolling()
 2067        {
 2068            if (_cts != null)
 869            {
 870                _cts.Cancel();
 871                _cts.Dispose();
 872                _cts = null;
 873            }
 74            // We don't await _pollingTask here to avoid blocking properties/events,
 75            // but the loop will exit on cancellation.
 2076        }
 77
 78        private void OnSettingsChanged()
 279        {
 80            // Re-configure timer when settings change
 281            StartPolling();
 282        }
 83
 84        private async Task PollingLoop(TimeSpan interval, CancellationToken token)
 885        {
 86            // Modern "PeriodicTimer" pattern using factory for testability
 887            using var timer = _periodicTimerFactory(interval);
 88            try
 889            {
 1290                while (await timer.WaitForNextTickAsync(token))
 491                {
 92                    try
 493                    {
 94                        // Skip refresh when the workstation is locked.
 95                        // UIA/COM calls against locked desktops can hang for 10-15s,
 96                        // blocking the UI thread and making the app unresponsive on wake.
 497                        if (_workstationService.IsWorkstationLocked())
 198                        {
 199                            Logger.Log("BackgroundPollingService: Workstation locked, skipping refresh.");
 1100                            continue;
 101                        }
 102
 3103                        Logger.Log("BackgroundPollingService: Running background refresh.");
 104
 105                        // Dispatch to UI thread since RefreshWindows updates ObservableCollection
 3106                        await _dispatcherService.InvokeAsync(async () =>
 3107                        {
 3108                            await _refreshAction();
 5109                        });
 2110                    }
 1111                    catch (Exception ex)
 1112                    {
 113                        // Log but don't crash loop
 1114                        Logger.LogError("BackgroundPollingService: Error during refresh", ex);
 1115                    }
 3116                }
 7117            }
 1118            catch (OperationCanceledException)
 1119            {
 120                // Expected on stop
 1121            }
 8122        }
 123
 124        public void Dispose()
 11125        {
 13126            if (_disposed) return;
 9127            _disposed = true;
 128
 9129            _settingsService.SettingsChanged -= OnSettingsChanged;
 9130            StopPolling();
 11131        }
 132    }
 133}