// Copyright (C) 2025, The Duplicati Team // https://duplicati.com, hello@duplicati.com // // Permission is hereby granted, free of charge, to any person obtaining a // copy of this software and associated documentation files (the "Software"), // to deal in the Software without restriction, including without limitation // the rights to use, copy, modify, merge, publish, distribute, sublicense, // and/or sell copies of the Software, and to permit persons to whom the // Software is furnished to do so, subject to the following conditions: // // The above copyright notice and this permission notice shall be included in // all copies or substantial portions of the Software. // // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS // OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, // FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE // AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER // LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING // FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER // DEALINGS IN THE SOFTWARE. namespace Duplicati.Library.RemoteControl; /// /// Helper class to support period refreshes, with signal support /// public class PeriodicRefresher : IDisposable { /// /// The last time the refresh was done /// private DateTime _lastRefresh = DateTime.MinValue; /// /// The interval between refreshes /// private readonly TimeSpan _refreshInterval; /// /// The minimum interval between refreshes (debounce) /// private readonly TimeSpan _minimumRefreshInterval; /// /// The action to run on refresh /// private readonly Func _refreshAction; /// /// The cancellation token source /// private readonly CancellationTokenSource _cts; /// /// The task completion source used to signal a refresh /// private TaskCompletionSource _tcs; /// /// Creates a new instance of the class /// /// The interval between refreshes /// The minimum interval between refreshes (debounce) /// The action to run on refresh /// The cancellation token to use public PeriodicRefresher(TimeSpan refreshInterval, TimeSpan minimumRefresh, Func refreshAction, CancellationToken cancellationToken) { _refreshInterval = refreshInterval; _minimumRefreshInterval = minimumRefresh; _refreshAction = refreshAction; _cts = CancellationTokenSource.CreateLinkedTokenSource(cancellationToken); _tcs = new TaskCompletionSource(); } /// /// Signals a refresh /// public void Signal() { _tcs.TrySetResult(true); } /// /// Runs the refresh loop /// /// The task to await public async Task RunLoopAsync() { while (!_cts.Token.IsCancellationRequested) { var t = await Task.WhenAny(_tcs.Task, Task.Delay(_refreshInterval, _cts.Token)); if (_cts.Token.IsCancellationRequested) return; if (t == _tcs.Task) Interlocked.Exchange(ref _tcs, new TaskCompletionSource()); if ((_lastRefresh + _minimumRefreshInterval) < DateTime.Now) { _lastRefresh = DateTime.Now; await _refreshAction(_cts.Token); } } } /// public void Dispose() { _cts.Cancel(); _cts.Dispose(); } }