Files
Umbraco-CMS/src/Umbraco.Core/TimedScope.cs
Ronald Barendse 839b6816f2 Merge commit from fork
* Add TimedScope

* Use TimedScope in login endpoint

* Use seperate default duration and only calculate average of actual successful responses

* Only return detailed error responses if credentials are valid

* Cancel timed scope when credentials are valid

* Add UserDefaultFailedLoginDuration and UserMinimumFailedLoginDuration settings
2025-01-20 14:54:14 +01:00

163 lines
6.4 KiB
C#

namespace Umbraco.Cms.Core;
/// <summary>
/// Makes a code block timed (take at least a certain amount of time). This class cannot be inherited.
/// </summary>
public sealed class TimedScope : IDisposable, IAsyncDisposable
{
private readonly TimeSpan _duration;
private readonly TimeProvider _timeProvider;
private readonly CancellationTokenSource _cancellationTokenSource;
private readonly long _startingTimestamp;
/// <summary>
/// Gets the elapsed time.
/// </summary>
/// <value>
/// The elapsed time.
/// </value>
public TimeSpan Elapsed
=> _timeProvider.GetElapsedTime(_startingTimestamp);
/// <summary>
/// Gets the remaining time.
/// </summary>
/// <value>
/// The remaining time.
/// </value>
public TimeSpan Remaining
=> TryGetRemaining(out TimeSpan remaining) ? remaining : TimeSpan.Zero;
/// <summary>
/// Initializes a new instance of the <see cref="TimedScope" /> class.
/// </summary>
/// <param name="millisecondsDuration">The number of milliseconds the scope should at least take.</param>
public TimedScope(long millisecondsDuration)
: this(TimeSpan.FromMilliseconds(millisecondsDuration))
{ }
/// <summary>
/// Initializes a new instance of the <see cref="TimedScope" /> class.
/// </summary>
/// <param name="millisecondsDuration">The number of milliseconds the scope should at least take.</param>
/// <param name="cancellationToken">The cancellation token.</param>
public TimedScope(long millisecondsDuration, CancellationToken cancellationToken)
: this(TimeSpan.FromMilliseconds(millisecondsDuration), cancellationToken)
{ }
/// <summary>
/// Initializes a new instance of the <see cref="TimedScope" /> class.
/// </summary>
/// <param name="millisecondsDuration">The number of milliseconds the scope should at least take.</param>
/// <param name="timeProvider">The time provider.</param>
public TimedScope(long millisecondsDuration, TimeProvider timeProvider)
: this(TimeSpan.FromMilliseconds(millisecondsDuration), timeProvider)
{ }
/// <summary>
/// Initializes a new instance of the <see cref="TimedScope" /> class.
/// </summary>
/// <param name="millisecondsDuration">The number of milliseconds the scope should at least take.</param>
/// <param name="timeProvider">The time provider.</param>
/// <param name="cancellationToken">The cancellation token.</param>
public TimedScope(long millisecondsDuration, TimeProvider timeProvider, CancellationToken cancellationToken)
: this(TimeSpan.FromMilliseconds(millisecondsDuration), timeProvider, cancellationToken)
{ }
/// <summary>
/// Initializes a new instance of the <see cref="TimedScope"/> class.
/// </summary>
/// <param name="duration">The duration the scope should at least take.</param>
public TimedScope(TimeSpan duration)
: this(duration, TimeProvider.System)
{ }
/// <summary>
/// Initializes a new instance of the <see cref="TimedScope" /> class.
/// </summary>
/// <param name="duration">The duration the scope should at least take.</param>
/// <param name="timeProvider">The time provider.</param>
public TimedScope(TimeSpan duration, TimeProvider timeProvider)
: this(duration, timeProvider, new CancellationTokenSource())
{ }
/// <summary>
/// Initializes a new instance of the <see cref="TimedScope" /> class.
/// </summary>
/// <param name="duration">The duration the scope should at least take.</param>
/// <param name="cancellationToken">The cancellation token.</param>
public TimedScope(TimeSpan duration, CancellationToken cancellationToken)
: this(duration, TimeProvider.System, cancellationToken)
{ }
/// <summary>
/// Initializes a new instance of the <see cref="TimedScope" /> class.
/// </summary>
/// <param name="duration">The duration the scope should at least take.</param>
/// <param name="timeProvider">The time provider.</param>
/// <param name="cancellationToken">The cancellation token.</param>
public TimedScope(TimeSpan duration, TimeProvider timeProvider, CancellationToken cancellationToken)
: this(duration, timeProvider, CancellationTokenSource.CreateLinkedTokenSource(cancellationToken))
{ }
private TimedScope(TimeSpan duration, TimeProvider timeProvider, CancellationTokenSource cancellationTokenSource)
{
_duration = duration;
_timeProvider = timeProvider;
_cancellationTokenSource = cancellationTokenSource;
_startingTimestamp = timeProvider.GetTimestamp();
}
/// <summary>
/// Cancels the timed scope.
/// </summary>
public void Cancel()
=> _cancellationTokenSource.Cancel();
/// <summary>
/// Cancels the timed scope asynchronously.
/// </summary>
public async Task CancelAsync()
=> await _cancellationTokenSource.CancelAsync().ConfigureAwait(false);
/// <summary>
/// Performs application-defined tasks associated with freeing, releasing, or resetting unmanaged resources.
/// </summary>
/// <remarks>
/// This will block using <see cref="Thread.Sleep(TimeSpan)" /> until the remaining time has elapsed, if not cancelled.
/// </remarks>
public void Dispose()
{
if (_cancellationTokenSource.IsCancellationRequested is false &&
TryGetRemaining(out TimeSpan remaining))
{
Thread.Sleep(remaining);
}
}
/// <summary>
/// Performs application-defined tasks associated with freeing, releasing, or resetting unmanaged resources asynchronously.
/// </summary>
/// <returns>
/// A task that represents the asynchronous dispose operation.
/// </returns>
/// <remarks>
/// This will delay using <see cref="Task.Delay(TimeSpan, TimeProvider, CancellationToken)" /> until the remaining time has elapsed, if not cancelled.
/// </remarks>
public async ValueTask DisposeAsync()
{
if (_cancellationTokenSource.IsCancellationRequested is false &&
TryGetRemaining(out TimeSpan remaining))
{
await Task.Delay(remaining, _timeProvider, _cancellationTokenSource.Token).ConfigureAwait(false);
}
}
private bool TryGetRemaining(out TimeSpan remaining)
{
remaining = _duration.Subtract(Elapsed);
return remaining > TimeSpan.Zero;
}
}