Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Addressed tons of warnings, mostly related to lack of documentation in code. #1034

Merged
merged 1 commit into from
Jan 30, 2024
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
8 changes: 8 additions & 0 deletions src/app/dev/DevToys.Api/Core/DictionaryExtensions.cs
Original file line number Diff line number Diff line change
@@ -1,10 +1,18 @@
namespace DevToys.Api;

/// <summary>
/// Provides extension methods for dictionaries.
/// </summary>
public static class DictionaryExtensions
{
/// <summary>
/// Gets the value at the given key, or a default value.
/// </summary>
/// <typeparam name="TKey">The type of the keys in the dictionary.</typeparam>
/// <typeparam name="TValue">The type of the values in the dictionary.</typeparam>
/// <param name="dictionary">The dictionary to retrieve the value from.</param>
/// <param name="key">The key to retrieve the value for.</param>
/// <returns>The value associated with the specified key, or the default value if the key is not found.</returns>
public static TValue? GetValueOrDefault<TKey, TValue>(this IDictionary<TKey, TValue> dictionary, TKey key)
{
return dictionary.TryGetValue(key, out TValue? value) ? value : default;
Expand Down
1 change: 0 additions & 1 deletion src/app/dev/DevToys.Api/Core/IFileStorage.cs
Original file line number Diff line number Diff line change
Expand Up @@ -69,7 +69,6 @@ public interface IFileStorage
/// <summary>
/// Creates a new temporary file in <see cref="AppCacheDirectory"/> that will be deleted when the app stops, or the next time is starts.
/// </summary>
/// </summary>
/// <param name="desiredFileExtension">(optional) The extension the temporary file should use.</param>
/// <returns>Returns information to the file.</returns>
FileInfo CreateSelfDestroyingTempFile(string? desiredFileExtension = null);
Expand Down
8 changes: 8 additions & 0 deletions src/app/dev/DevToys.Api/Core/LoggingExtensions.cs
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,9 @@

namespace DevToys.Api;

/// <summary>
/// Provides extension methods for logging.
/// </summary>
public static class LoggingExtensions
{
private static ILoggerFactory _loggerFactory = default!;
Expand All @@ -22,12 +25,17 @@ public static ILoggerFactory LoggerFactory
/// <summary>
/// Creates an instance of <see cref="ILogger"/> for the specified type.
/// </summary>
/// <param name="forType">The type to create the logger for.</param>
/// <returns>An instance of <see cref="ILogger"/>.</returns>
public static ILogger Log(this Type forType)
=> LoggerFactory.CreateLogger(forType);

/// <summary>
/// Creates an instance of <see cref="ILogger"/> for the given object's type.
/// </summary>
/// <typeparam name="T">The type of the object.</typeparam>
/// <param name="instance">The object instance.</param>
/// <returns>An instance of <see cref="ILogger"/>.</returns>
public static ILogger Log<T>(this T instance)
{
Guard.IsNotNull(instance);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -2,11 +2,17 @@

namespace DevToys.Api;

/// <summary>
/// Provides extension methods for <see cref="ObservableCollection{T}"/>.
/// </summary>
public static class ObservableCollectionExtensions
{
/// <summary>
/// Adds the elements of the specified collection to the end of the ObservableCollection(Of T).
/// Adds the elements of the specified collection to the end of the <see cref="ObservableCollection{T}"/>.
/// </summary>
/// <typeparam name="T">The type of elements in the collection.</typeparam>
/// <param name="origin">The <see cref="ObservableCollection{T}"/> to add elements to.</param>
/// <param name="newItems">The collection whose elements should be added to the end of the <see cref="ObservableCollection{T}"/>.</param>
public static void AddRange<T>(this ObservableCollection<T> origin, IEnumerable<T> newItems)
{
Guard.IsNotNull(origin);
Expand Down
37 changes: 34 additions & 3 deletions src/app/dev/DevToys.Api/Core/OneOfExtensions.cs
Original file line number Diff line number Diff line change
Expand Up @@ -2,8 +2,18 @@

namespace DevToys.Api;

/// <summary>
/// Extension methods for working with OneOf types.
/// </summary>
public static class OneOfExtensions
{
/// <summary>
/// Gets a stream asynchronously based on the input OneOf type.
/// </summary>
/// <param name="input">The input OneOf type.</param>
/// <param name="fileStorage">The file storage implementation.</param>
/// <param name="cancellationToken">The cancellation token.</param>
/// <returns>A task that represents the asynchronous operation. The task result contains the stream and a flag indicating if the stream was successfully retrieved.</returns>
public static Task<ResultInfo<Stream>> GetStreamAsync(
this OneOf<string, FileInfo> input,
IFileStorage fileStorage,
Expand All @@ -17,21 +27,28 @@ public static Task<ResultInfo<Stream>> GetStreamAsync(
return GetStreamAsync(OneOf<FileInfo, string>.FromT1(input.AsT0), fileStorage, cancellationToken);
}

/// <summary>
/// Gets a stream asynchronously based on the input OneOf type.
/// </summary>
/// <param name="input">The input OneOf type.</param>
/// <param name="fileStorage">The file storage implementation.</param>
/// <param name="cancellationToken">The cancellation token.</param>
/// <returns>A task that represents the asynchronous operation. The task result contains the stream and a flag indicating if the stream was successfully retrieved.</returns>
public static async Task<ResultInfo<Stream>> GetStreamAsync(
this OneOf<FileInfo, string> input,
IFileStorage fileStorage,
CancellationToken cancellationToken)
{
return await
input.Match(
async inputFile =>
inputFile =>
{
if (!fileStorage.FileExists(inputFile.FullName))
{
return new ResultInfo<Stream>(Stream.Null, false);
return Task.FromResult(new ResultInfo<Stream>(Stream.Null, false));
}

return new ResultInfo<Stream>(fileStorage.OpenReadFile(inputFile.FullName), true);
return Task.FromResult(new ResultInfo<Stream>(fileStorage.OpenReadFile(inputFile.FullName), true));
},
async inputString =>
{
Expand All @@ -47,6 +64,13 @@ public static async Task<ResultInfo<Stream>> GetStreamAsync(
});
}

/// <summary>
/// Reads all text asynchronously based on the input OneOf type.
/// </summary>
/// <param name="input">The input OneOf type.</param>
/// <param name="fileStorage">The file storage implementation.</param>
/// <param name="cancellationToken">The cancellation token.</param>
/// <returns>A task that represents the asynchronous operation. The task result contains the text content and a flag indicating if the content was successfully read.</returns>
public static async Task<ResultInfo<string>> ReadAllTextAsync(
this OneOf<string, FileInfo> input,
IFileStorage fileStorage,
Expand All @@ -60,6 +84,13 @@ public static async Task<ResultInfo<string>> ReadAllTextAsync(
return await ReadAllTextAsync(OneOf<FileInfo, string>.FromT1(input.AsT0), fileStorage, cancellationToken);
}

/// <summary>
/// Reads all text asynchronously based on the input OneOf type.
/// </summary>
/// <param name="input">The input OneOf type.</param>
/// <param name="fileStorage">The file storage implementation.</param>
/// <param name="cancellationToken">The cancellation token.</param>
/// <returns>A task that represents the asynchronous operation. The task result contains the text content and a flag indicating if the content was successfully read.</returns>
public static async Task<ResultInfo<string>> ReadAllTextAsync(
this OneOf<FileInfo, string> input,
IFileStorage fileStorage,
Expand Down
26 changes: 24 additions & 2 deletions src/app/dev/DevToys.Api/Core/SandboxedFileReader.cs
Original file line number Diff line number Diff line change
Expand Up @@ -11,12 +11,20 @@
[DebuggerDisplay($"FileName = {{{nameof(FileName)}}}")]
public abstract class SandboxedFileReader : IDisposable
{
/// <summary>
/// The buffer size used for reading the file in chunks.
/// </summary>
public const int BufferSize = 4096; // 4 KB chunks

private readonly DisposableSemaphore _semaphore = new();
private bool _disposed;
private List<Stream>? _fileAccesses;

/// <summary>
/// Creates a new instance of the <see cref="SandboxedFileReader"/> class from a <see cref="FileInfo"/> object.
/// </summary>
/// <param name="fileInfo">The <see cref="FileInfo"/> object representing the file.</param>
/// <returns>A new instance of the <see cref="SandboxedFileReader"/> class.</returns>
public static SandboxedFileReader FromFileInfo(FileInfo fileInfo)
{
Guard.IsNotNull(fileInfo);
Expand All @@ -26,6 +34,7 @@ public static SandboxedFileReader FromFileInfo(FileInfo fileInfo)
/// <summary>
/// Initializes a new instance of the <see cref="SandboxedFileReader"/> class.
/// </summary>
/// <param name="fileName">The name of the file, including its extension.</param>
protected SandboxedFileReader(string fileName)
{
FileName = Path.GetFileName(fileName);
Expand All @@ -41,8 +50,16 @@ protected SandboxedFileReader(string fileName)
/// </summary>
public event EventHandler? Disposed;

/// <summary>
/// Opens the file for reading asynchronously.
/// </summary>
/// <param name="cancellationToken">The cancellation token.</param>
/// <returns>A task representing the asynchronous operation.</returns>
protected abstract ValueTask<Stream> OpenReadFileAsync(CancellationToken cancellationToken);

/// <summary>
/// Disposes the <see cref="SandboxedFileReader"/> and releases any resources used.
/// </summary>
public void Dispose()
{
lock (_semaphore)
Expand All @@ -56,11 +73,13 @@ public void Dispose()
}

/// <summary>
/// Get a new stream that can be used to read the file. The stream gets disposed automatically
/// Gets a new stream that can be used to read the file. The stream gets disposed automatically
/// when the <see cref="SandboxedFileReader"/> is disposed.
/// </summary>
/// <remarks>In some cases, the returned stream is non-seekable.</remarks>
/// <param name="cancellationToken">The cancellation token.</param>
/// <exception cref="ObjectDisposedException">The <see cref="SandboxedFileReader"/> has been disposed.</exception>"
/// <returns>A task representing the asynchronous operation.</returns>
public async Task<Stream> GetNewAccessToFileContentAsync(CancellationToken cancellationToken)
{
using (await _semaphore.WaitAsync(cancellationToken))
Expand All @@ -78,8 +97,11 @@ public async Task<Stream> GetNewAccessToFileContentAsync(CancellationToken cance
}

/// <summary>
/// Copy the content of the file to the given stream.
/// Copies the content of the file to the given stream.
/// </summary>
/// <param name="destinationStream">The destination stream.</param>
/// <param name="cancellationToken">The cancellation token.</param>
/// <returns>A <see cref="Task"/> representing the asynchronous operation.</returns>
/// <exception cref="ObjectDisposedException">The <see cref="SandboxedFileReader"/> has been disposed.</exception>"
public async Task CopyFileContentToAsync(Stream destinationStream, CancellationToken cancellationToken)
{
Expand Down
59 changes: 51 additions & 8 deletions src/app/dev/DevToys.Api/Core/Threading/AsyncLazy.cs
Original file line number Diff line number Diff line change
@@ -1,55 +1,98 @@
namespace DevToys.Api;

/// <summary>
/// Represents an asynchronous lazy initialization.
/// </summary>
/// <typeparam name="T">The type of the value being lazily initialized.</typeparam>
[DebuggerDisplay($"IsValueCreated = {{{nameof(IsValueCreated)}}}")]
public class AsyncLazy<T>
{
private static Func<Task<T>> FromFuncT(Func<T> valueFunc)
{
Guard.IsNotNull(valueFunc);
return () => Task.FromResult(valueFunc());
}

private readonly Lazy<Task<T>> _innerLazy;

public bool IsValueCreated => _innerLazy.IsValueCreated && _innerLazy.Value.IsCompletedSuccessfully;

/// <summary>
/// Creates a new instance of the <see cref="AsyncLazy{T}"/> class with the specified value.
/// </summary>
/// <param name="value">The value to initialize the <see cref="AsyncLazy{T}"/> instance with.</param>
public AsyncLazy(T value)
{
_innerLazy = new Lazy<Task<T>>(Task.FromResult(value));
}

/// <summary>
/// Creates a new instance of the <see cref="AsyncLazy{T}"/> class with the specified value factory.
/// </summary>
/// <param name="valueFactory">The delegate that represents the value factory.</param>
public AsyncLazy(Func<T> valueFactory)
{
_innerLazy = new Lazy<Task<T>>(FromFuncT(valueFactory));
}

/// <summary>
/// Creates a new instance of the <see cref="AsyncLazy{T}"/> class with the specified value factory and thread safety option.
/// </summary>
/// <param name="valueFactory">The delegate that represents the value factory.</param>
/// <param name="isThreadSafe">A boolean value indicating whether the lazy initialization is thread-safe.</param>
public AsyncLazy(Func<T> valueFactory, bool isThreadSafe)
{
_innerLazy = new Lazy<Task<T>>(FromFuncT(valueFactory), isThreadSafe);
}

/// <summary>
/// Creates a new instance of the <see cref="AsyncLazy{T}"/> class with the specified value factory and thread safety mode.
/// </summary>
/// <param name="valueFactory">The delegate that represents the value factory.</param>
/// <param name="mode">The thread safety mode for the lazy initialization.</param>
public AsyncLazy(Func<T> valueFactory, LazyThreadSafetyMode mode)
{
_innerLazy = new Lazy<Task<T>>(FromFuncT(valueFactory), mode);
}

/// <summary>
/// Creates a new instance of the <see cref="AsyncLazy{T}"/> class with the specified asynchronous value factory.
/// </summary>
/// <param name="valueFactory">The delegate that represents the asynchronous value factory.</param>
public AsyncLazy(Func<Task<T>> valueFactory)
{
_innerLazy = new Lazy<Task<T>>(valueFactory);
}

/// <summary>
/// Creates a new instance of the <see cref="AsyncLazy{T}"/> class with the specified asynchronous value factory and thread safety option.
/// </summary>
/// <param name="valueFactory">The delegate that represents the asynchronous value factory.</param>
/// <param name="isThreadSafe">A boolean value indicating whether the lazy initialization is thread-safe.</param>
public AsyncLazy(Func<Task<T>> valueFactory, bool isThreadSafe)
{
_innerLazy = new Lazy<Task<T>>(valueFactory, isThreadSafe);
}

/// <summary>
/// Creates a new instance of the <see cref="AsyncLazy{T}"/> class with the specified asynchronous value factory and thread safety mode.
/// </summary>
/// <param name="valueFactory">The delegate that represents the asynchronous value factory.</param>
/// <param name="mode">The thread safety mode for the lazy initialization.</param>
public AsyncLazy(Func<Task<T>> valueFactory, LazyThreadSafetyMode mode)
{
_innerLazy = new Lazy<Task<T>>(valueFactory, mode);
}

/// <summary>
/// Gets a task that represents the asynchronous initialization of the value.
/// </summary>
/// <returns>A task that represents the asynchronous initialization of the value.</returns>
public Task<T> GetValueAsync()
{
return IsValueCreated ? Task.FromResult(_innerLazy.Value.Result) : _innerLazy.Value;
}

/// <summary>
/// Determines whether the value has been created.
/// </summary>
public bool IsValueCreated => _innerLazy.IsValueCreated && _innerLazy.Value.IsCompletedSuccessfully;

private static Func<Task<T>> FromFuncT(Func<T> valueFunc)
{
Guard.IsNotNull(valueFunc);
return () => Task.FromResult(valueFunc());
}
}
23 changes: 22 additions & 1 deletion src/app/dev/DevToys.Api/Core/Threading/DisposableSemaphore.cs
Original file line number Diff line number Diff line change
@@ -1,23 +1,36 @@
namespace DevToys.Api;

/// <summary>
/// Represents a semaphore that free other threads when disposing the result of the <see cref="WaitAsync(CancellationToken)"/> method..
/// Represents a semaphore that free other threads when disposing the result of the <see cref="WaitAsync(CancellationToken)"/> method.
/// </summary>
[DebuggerDisplay($"IsBusy = {{{nameof(IsBusy)}}}, Disposed = {{{nameof(Disposed)}}}")]
public sealed class DisposableSemaphore : IDisposable
{
private readonly object _lockObject = new();
private readonly SemaphoreSlim _semaphore;

/// <summary>
/// Gets a value indicating whether the semaphore has been disposed.
/// </summary>
public bool Disposed { get; private set; }

/// <summary>
/// Gets a value indicating whether the semaphore is currently busy.
/// </summary>
public bool IsBusy => _semaphore.CurrentCount == 0;

/// <summary>
/// Initializes a new instance of the <see cref="DisposableSemaphore"/> class.
/// </summary>
/// <param name="maxTasksCount">The maximum number of concurrent tasks that can be executed.</param>
public DisposableSemaphore(int maxTasksCount = 1)
{
_semaphore = new SemaphoreSlim(maxTasksCount, maxTasksCount);
}

/// <summary>
/// Releases all resources used by the <see cref="DisposableSemaphore"/>.
/// </summary>
public void Dispose()
{
lock (_lockObject)
Expand All @@ -30,6 +43,11 @@ public void Dispose()
}
}

/// <summary>
/// Asynchronously waits for the semaphore to be available.
/// </summary>
/// <param name="cancellationToken">The cancellation token.</param>
/// <returns>An <see cref="IDisposable"/> object that should be disposed to release the semaphore.</returns>
public async Task<IDisposable> WaitAsync(CancellationToken cancellationToken)
{
await _semaphore.WaitAsync(cancellationToken).ConfigureAwait(false);
Expand All @@ -46,6 +64,9 @@ internal DummyDisposable(SemaphoreSlim semaphore)
_semaphore = semaphore;
}

/// <summary>
/// Releases the semaphore.
/// </summary>
public void Dispose()
{
_semaphore.Release();
Expand Down
Loading