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
5 changes: 5 additions & 0 deletions analyzers/disabled.editorconfig
Original file line number Diff line number Diff line change
@@ -1,5 +1,10 @@
is_global = true

# CA1033: Interface methods should be callable by child types
# Some childs should not have the parent methods
# https://learn.microsoft.com/en-us/dotnet/fundamentals/code-analysis/quality-rules/ca1033
dotnet_diagnostic.CA1033.severity = none

# CA1031: Do not catch general exception types
# Sometimes we don't know the exception.
# https://learn.microsoft.com/en-us/dotnet/fundamentals/code-analysis/quality-rules/ca1031
Expand Down
4 changes: 4 additions & 0 deletions analyzers/sonar.editorconfig
Original file line number Diff line number Diff line change
Expand Up @@ -7,3 +7,7 @@ dotnet_diagnostic.S101.severity = none
# S4200: Make this wrapper for native method less trivial
# Some method are trivial and should be kept as is
dotnet_diagnostic.S4200.severity = none

# S6966: Use async method instead.
# Is a duplicate of CA1849: https://learn.microsoft.com/en-us/dotnet/fundamentals/code-analysis/quality-rules/ca1849
dotnet_diagnostic.S6966.severity = none
6 changes: 6 additions & 0 deletions analyzers/vsthrd.editorconfig
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
is_global = true

# VSTHRD103: Call async methods when in an async method
# Is a duplicate of CA1849: https://learn.microsoft.com/en-us/dotnet/fundamentals/code-analysis/quality-rules/ca1849
# https://microsoft.github.io/vs-threading/analyzers/VSTHRD103.html
dotnet_diagnostic.VSTHRD103.severity = none
1 change: 1 addition & 0 deletions quack.slnx
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,7 @@
<File Path="analyzers/roslynator.editorconfig" />
<File Path="analyzers/sonar.editorconfig" />
<File Path="analyzers/underscore.editorconfig" />
<File Path="analyzers/vsthrd.editorconfig" />
</Folder>
<Folder Name="/configuration/src/">
<File Path="src/Directory.Packages.props" />
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,67 @@
// Copyright (c) KappaDuck.
// Licensed under the MIT license.

namespace KappaDuck.Quack.Progress;

/// <summary>
/// Asynchronous indeterminate progress reporter handed to
/// <see cref="ProgressBar.StartIndeterminateAsync(Func{AsyncIndeterminateProgressReporter, ValueTask})"/>.
/// </summary>
public sealed class AsyncIndeterminateProgressReporter : IDisposable
{
private readonly CancellationTokenSource _cts = new();

internal AsyncIndeterminateProgressReporter()
{
}

/// <summary>
/// Gets the token to observe for cancellation during the reporting.
/// </summary>
public CancellationToken CancellationToken => _cts.Token;

/// <summary>
/// Requests cancellation of the progress operation.
/// </summary>
/// <remarks>
/// Stops any further reporting, triggers <see cref="ProgressBar.Cancelled"/> and resets the bar.
/// </remarks>
public void Cancel()
{
if (CancellationToken.IsCancellationRequested)
return;

_cts.Cancel();
}

/// <summary>
/// Requests cancellation of the progress operation after a delay.
/// </summary>
/// <param name="delay">The delay after which to cancel.</param>
public void CancelAfter(TimeSpan delay)
{
if (CancellationToken.IsCancellationRequested)
return;

_cts.CancelAfter(delay);
}

/// <inheritdoc cref="CancelAfter(TimeSpan)"/>
public void CancelAfter(int millisecondsDelay) => CancelAfter(TimeSpan.FromMilliseconds(millisecondsDelay));

/// <summary>
/// Asynchronously requests cancellation of the progress operation.
/// </summary>
/// <returns>A task that represents the asynchronous cancellation.</returns>
public Task CancelAsync()
{
if (CancellationToken.IsCancellationRequested)
return Task.CompletedTask;

return _cts.CancelAsync();
}

/// <inheritdoc/>
public void Dispose() => _cts.Dispose();
}

117 changes: 117 additions & 0 deletions src/KappaDuck.Quack/Progress/AsyncProgressReporter.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,117 @@
// Copyright (c) KappaDuck.
// Licensed under the MIT license.

namespace KappaDuck.Quack.Progress;

/// <summary>
/// Asynchronous determinate progress reporter handed to
/// <see cref="ProgressBar.StartAsync(Func{AsyncProgressReporter, ValueTask}, int)"/>.
/// </summary>
public sealed class AsyncProgressReporter : IDisposable
{
private readonly IProgressOperation _operation;
private readonly CancellationTokenSource _cts = new();
private readonly int _total;

private int _current;

internal AsyncProgressReporter(IProgressOperation operation, int total)
{
_operation = operation;
_total = total;
}

/// <summary>
/// Gets the token to observe for cancellation during the reporting.
/// </summary>
public CancellationToken CancellationToken => _cts.Token;

/// <summary>
/// Reports progress by incrementing by <c>1</c>.
/// </summary>
/// <remarks>
/// This is a shorthand for <see cref="Increment(int)"/> with <c>steps</c> of <c>1</c>.
/// </remarks>
/// <exception cref="OperationCanceledException">Cancellation has been requested.</exception>
public void Advance() => Increment(1);

/// <summary>
/// Requests cancellation of the progress operation.
/// </summary>
/// <remarks>
/// The next call to <see cref="Increment(int)"/> or <see cref="Report(int)"/> throws
/// <see cref="OperationCanceledException"/>, which the base catches to trigger
/// <see cref="ProgressBar.Cancelled"/> and reset the bar.
/// </remarks>
public void Cancel()
{
if (CancellationToken.IsCancellationRequested)
return;

_cts.Cancel();
}

/// <summary>
/// Requests cancellation of the progress operation after a delay.
/// </summary>
/// <param name="delay">The delay after which to cancel.</param>
public void CancelAfter(TimeSpan delay)
{
if (CancellationToken.IsCancellationRequested)
return;

_cts.CancelAfter(delay);
}

/// <inheritdoc cref="CancelAfter(TimeSpan)"/>
public void CancelAfter(int millisecondsDelay) => CancelAfter(TimeSpan.FromMilliseconds(millisecondsDelay));

/// <summary>
/// Asynchronously requests cancellation of the progress operation.
/// </summary>
/// <returns>A task that represents the asynchronous cancellation.</returns>
public Task CancelAsync()
{
if (CancellationToken.IsCancellationRequested)
return Task.CompletedTask;

return _cts.CancelAsync();
}

/// <inheritdoc/>
public void Dispose() => _cts.Dispose();

/// <summary>
/// Reports progress by incrementing by a step.
/// </summary>
/// <param name="steps">The number of steps to increment.</param>
/// <exception cref="ArgumentOutOfRangeException"><paramref name="steps"/> is negative.</exception>
/// <exception cref="OperationCanceledException">Cancellation has been requested.</exception>
public void Increment(int steps)
{
CancellationToken.ThrowIfCancellationRequested();

ArgumentOutOfRangeException.ThrowIfNegative(steps);
Report(_current + steps);
}

/// <summary>
/// Reports the absolute current progress.
/// </summary>
/// <remarks>
/// The total provided to <see cref="ProgressBar.StartAsync(Func{AsyncProgressReporter, ValueTask}, int)"/> is
/// used as the maximum limit if <paramref name="current"/> is greater than the total.
/// </remarks>
/// <param name="current">The current progress.</param>
/// <exception cref="ArgumentOutOfRangeException"><paramref name="current"/> is negative.</exception>
/// <exception cref="OperationCanceledException">Cancellation has been requested.</exception>
public void Report(int current)
{
CancellationToken.ThrowIfCancellationRequested();

ArgumentOutOfRangeException.ThrowIfNegative(current);

_current = Math.Min(current, _total);
_operation.Report((float)_current / _total);
}
}
22 changes: 22 additions & 0 deletions src/KappaDuck.Quack/Progress/IProgressOperation.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,22 @@
// Copyright (c) KappaDuck.
// Licensed under the MIT license.

namespace KappaDuck.Quack.Progress;

/// <summary>
/// Represents a progress operation that can be reported or cancelled.
/// </summary>
public interface IProgressOperation
{
/// <summary>
/// Reports a normalized progress value between <c>0</c> and <c>1</c>.
/// </summary>
/// <param name="value">The normalized value. Values outside the range are clamped by the sink.</param>
void Report(float value);

/// <summary>
/// Requests cancellation of the current progress operation.
/// </summary>
void Cancel();

}
27 changes: 27 additions & 0 deletions src/KappaDuck.Quack/Progress/IndeterminateProgressReporter.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,27 @@
// Copyright (c) KappaDuck.
// Licensed under the MIT license.

namespace KappaDuck.Quack.Progress;

/// <summary>
/// Synchronous indeterminate progress reporter handed to
/// <see cref="ProgressBar.StartIndeterminate(Action{IndeterminateProgressReporter})"/>.
/// </summary>
public sealed class IndeterminateProgressReporter
{
internal IndeterminateProgressReporter()
{
}

/// <summary>
/// Requests cancellation of the progress operation.
/// </summary>
/// <remarks>
/// Throws <see cref="OperationCanceledException"/>, which the base catches to trigger
/// <see cref="ProgressBar.Cancelled"/> and reset the bar.
/// </remarks>
/// <exception cref="OperationCanceledException">Always thrown.</exception>
[DoesNotReturn]
[SuppressMessage("Performance", "CA1822:Mark members as static", Justification = "The method is part of the instance API.")]
public void Cancel() => throw new OperationCanceledException("The indeterminate progress operation has been cancelled.");
}
Loading