Skip to content

Commit

Permalink
Version 9.0 Impl (#134)
Browse files Browse the repository at this point in the history
* project udpates

* start new file system package.

* make fs static extensions

* update filesystem stuff

* remove vanara and udpate windows fs

* some new lang features

* first changes so that it works with 9.0

* update verification

* fix linux ci restore

* remove dependency

* try other drive

* custom progress for test

* implement new hashing service

* add docs

* fix sha3 tests

* seal some classes

* make packable

* add frugallist

* some corrections and tests

* fix some downloader beahvior and rename tests

* fix linux tests (again...)

* update deps
  • Loading branch information
AnakinRaW authored Mar 11, 2024
1 parent 2cd7acd commit 0ed546e
Show file tree
Hide file tree
Showing 196 changed files with 9,739 additions and 4,106 deletions.
Binary file removed AnakinOSS.snk
Binary file not shown.
6 changes: 6 additions & 0 deletions CommonUtilities.sln
Original file line number Diff line number Diff line change
Expand Up @@ -49,6 +49,8 @@ Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "CommonUtilities.SimplePipel
EndProject
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "CommonUtilities.SimplePipeline.Test", "src\CommonUtilities.SimplePipeline\test\CommonUtilities.SimplePipeline.Test.csproj", "{C8BF3F01-B1D5-4C29-9164-6DC7B9744589}"
EndProject
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "CommonUtilities.TestingUtilities", "src\CommonUtilities.TestingUtilities\CommonUtilities.TestingUtilities.csproj", "{99A3B9B4-6482-410A-A001-9D62F4B259CC}"
EndProject
Global
GlobalSection(SolutionConfigurationPlatforms) = preSolution
Debug|Any CPU = Debug|Any CPU
Expand Down Expand Up @@ -115,6 +117,10 @@ Global
{C8BF3F01-B1D5-4C29-9164-6DC7B9744589}.Debug|Any CPU.Build.0 = Debug|Any CPU
{C8BF3F01-B1D5-4C29-9164-6DC7B9744589}.Release|Any CPU.ActiveCfg = Release|Any CPU
{C8BF3F01-B1D5-4C29-9164-6DC7B9744589}.Release|Any CPU.Build.0 = Release|Any CPU
{99A3B9B4-6482-410A-A001-9D62F4B259CC}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{99A3B9B4-6482-410A-A001-9D62F4B259CC}.Debug|Any CPU.Build.0 = Debug|Any CPU
{99A3B9B4-6482-410A-A001-9D62F4B259CC}.Release|Any CPU.ActiveCfg = Release|Any CPU
{99A3B9B4-6482-410A-A001-9D62F4B259CC}.Release|Any CPU.Build.0 = Release|Any CPU
EndGlobalSection
GlobalSection(SolutionProperties) = preSolution
HideSolutionNode = FALSE
Expand Down
16 changes: 6 additions & 10 deletions Directory.Build.props
Original file line number Diff line number Diff line change
Expand Up @@ -10,27 +10,23 @@
</PropertyGroup>
<PropertyGroup>
<Product>.NET Common Utilities</Product>
<Copyright>Copyright © AnakinRaW 2023</Copyright>
<Copyright>Copyright © AnakinRaW 2024</Copyright>
<Authors>AnakinRaW</Authors>
<Owners>AnakinRaW</Owners>
<PackageProjectUrl>https://github.com/AnakinRaW/CommonUtilities</PackageProjectUrl>
<PackageLicenseExpression>MIT</PackageLicenseExpression>
<PackageReadmeFile>README.md</PackageReadmeFile>
</PropertyGroup>
<PropertyGroup>
<SignAssembly Condition="'$(Configuration)' == 'Release'">True</SignAssembly>
<AssemblyOriginatorKeyFile>$(MSBuildThisFileDirectory)AnakinOSS.snk</AssemblyOriginatorKeyFile>
</PropertyGroup>
<ItemGroup>
<PackageReference Include="Nerdbank.GitVersioning" Condition="!Exists('packages.config')">
<PrivateAssets>all</PrivateAssets>
<Version>3.6.133</Version>
</PackageReference>
<PackageReference Include="SauceControl.InheritDoc" Version="2.0.0" PrivateAssets="all" />
<PackageReference Include="SauceControl.InheritDoc" Version="2.0.1" PrivateAssets="all" />
<PackageReference Include="Microsoft.SourceLink.GitHub" Version="8.0.0">
<IncludeAssets>runtime; build; native; contentfiles; analyzers; buildtransitive</IncludeAssets>
<PrivateAssets>all</PrivateAssets>
</PackageReference>
<PackageReference Include="Nerdbank.GitVersioning" Condition="!Exists('packages.config')">
<PrivateAssets>all</PrivateAssets>
<Version>3.6.133</Version>
</PackageReference>
<None Include="$(MSBuildThisFileDirectory)README.md" Pack="true" PackagePath="" />
</ItemGroup>
</Project>
7 changes: 0 additions & 7 deletions src/CommonUtilities.DownloadManager/src/AssemblyInfo.cs

This file was deleted.

Original file line number Diff line number Diff line change
Expand Up @@ -33,11 +33,10 @@
<PackageReference Include="Microsoft.Extensions.DependencyInjection.Abstractions" Version="8.0.0" />
<PackageReference Include="Microsoft.Extensions.Logging.Abstractions" Version="8.0.0" />
<PackageReference Include="System.Net.Http" Version="4.3.4" />
<PackageReference Include="Validation" Version="2.5.51" PrivateAssets="compile" />
</ItemGroup>

<ItemGroup>
<ProjectReference Include="..\..\CommonUtilities\src\CommonUtilities.csproj" />
</ItemGroup>

</Project>
Original file line number Diff line number Diff line change
Expand Up @@ -16,7 +16,7 @@ public record DownloadManagerConfiguration : IDownloadManagerConfiguration
public bool AllowEmptyFileDownload { get; init; }

/// <inheritdoc/>
public VerificationPolicy VerificationPolicy { get; init; }
public ValidationPolicy ValidationPolicy { get; init; }

/// <inheritdoc/>
public InternetClient InternetClient { get; init; }
Expand Down
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
using System.Threading;
using Validation;
using System;
using System.Threading;

namespace AnakinRaW.CommonUtilities.DownloadManager.Configuration;

Expand All @@ -14,7 +14,8 @@ public abstract class DownloadManagerConfigurationProviderBase : IDownloadManage
public IDownloadManagerConfiguration GetConfiguration()
{
var configuration = LazyInitializer.EnsureInitialized(ref _configuration, CreateConfiguration);
Assumes.NotNull(configuration);
if (configuration is null)
throw new InvalidOperationException();
return configuration;
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -22,7 +22,7 @@ public interface IDownloadManagerConfiguration
/// <summary>
/// Specifies how verification after the download shall be handled.
/// </summary>
VerificationPolicy VerificationPolicy { get; }
ValidationPolicy ValidationPolicy { get; }

/// <summary>
/// The <see cref="Configuration.InternetClient"/> implementation which shall get used.
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,20 @@
namespace AnakinRaW.CommonUtilities.DownloadManager.Configuration;

/// <summary>
/// Options how validation at the end of a download shall be handled.
/// </summary>
public enum ValidationPolicy
{
/// <summary>
/// Validation will always be skipped.
/// </summary>
NoValidation,
/// <summary>
/// Validation is optional.
/// </summary>
Optional,
/// <summary>
/// Validation is required.
/// </summary>
Required,
}

This file was deleted.

Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,7 @@ namespace AnakinRaW.CommonUtilities.DownloadManager;
/// <summary>
/// Aggregated exception which holds all <see cref="DownloadFailureInformation"/> of a file download operation.
/// </summary>
public class DownloadFailedException : Exception
public sealed class DownloadFailedException : Exception
{
/// <summary>
/// All failures during a file download operation.
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@ namespace AnakinRaW.CommonUtilities.DownloadManager;
/// <summary>
/// Contains information about a failed download
/// </summary>
public class DownloadFailureInformation
public sealed class DownloadFailureInformation
{
/// <summary>
/// The exception of the download failure.
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@ namespace AnakinRaW.CommonUtilities.DownloadManager;
/// <summary>
/// The supported source file location of an <see cref="IDownloadProvider"/>.
/// </summary>
public enum DownloadSource
public enum DownloadKind
{
/// <summary>
/// The provider supports downloading files from the local file system or local network.
Expand Down
85 changes: 52 additions & 33 deletions src/CommonUtilities.DownloadManager/src/DownloadManager.cs
Original file line number Diff line number Diff line change
Expand Up @@ -6,24 +6,23 @@
using System.Threading.Tasks;
using AnakinRaW.CommonUtilities.DownloadManager.Configuration;
using AnakinRaW.CommonUtilities.DownloadManager.Providers;
using AnakinRaW.CommonUtilities.Verification;
using AnakinRaW.CommonUtilities.DownloadManager.Validation;
using Microsoft.Extensions.DependencyInjection;
using Microsoft.Extensions.Logging;
using Validation;

namespace AnakinRaW.CommonUtilities.DownloadManager;

/// <summary>
/// Download manager which supports local file system and HTTP downloads by default.
/// </summary>
public class DownloadManager : IDownloadManager {
public sealed class DownloadManager : IDownloadManager
{

private readonly ILogger? _logger;
private readonly IDownloadManagerConfiguration _configuration;

private readonly List<IDownloadProvider> _allProviders = new();
private readonly PreferredDownloadProviders _preferredDownloadProviders = new();
private readonly IVerificationManager _verifier;

/// <inheritdoc/>
public IEnumerable<string> Providers => _allProviders.Select(e => e.Name);
Expand All @@ -34,11 +33,11 @@ public class DownloadManager : IDownloadManager {
/// <param name="serviceProvider">The service provider of this instance.</param>
public DownloadManager(IServiceProvider serviceProvider)
{
Requires.NotNull(serviceProvider, nameof(serviceProvider));
if (serviceProvider == null)
throw new ArgumentNullException(nameof(serviceProvider));
_logger = serviceProvider.GetService<ILoggerFactory>()?.CreateLogger(GetType());
_configuration = serviceProvider.GetService<IDownloadManagerConfigurationProvider>()?.GetConfiguration() ??
DownloadManagerConfiguration.Default;
_verifier = serviceProvider.GetRequiredService<IVerificationManager>();
switch (_configuration.InternetClient)
{
case InternetClient.HttpClient:
Expand All @@ -58,21 +57,24 @@ public DownloadManager(IServiceProvider serviceProvider)
/// <inheritdoc/>
public void AddDownloadProvider(IDownloadProvider provider)
{
Requires.NotNull(provider, nameof(provider));
if (provider == null)
throw new ArgumentNullException(nameof(provider));
if (_allProviders.Any(e => string.Equals(e.Name, provider.Name, StringComparison.OrdinalIgnoreCase)))
throw new InvalidOperationException("Provider " + provider.Name + " already exists.");
_allProviders.Add(provider);
}

/// <inheritdoc/>
public Task<DownloadSummary> DownloadAsync(Uri uri, Stream outputStream, ProgressUpdateCallback? progress,
IVerificationContext? verificationContext = null, CancellationToken cancellationToken = default)
public Task<DownloadResult> DownloadAsync(Uri uri, Stream outputStream, ProgressUpdateCallback? progress,
IDownloadValidator? validator = null, CancellationToken cancellationToken = default)
{
_logger?.LogTrace($"Download requested: {uri.AbsoluteUri}");
if (outputStream == null)
throw new ArgumentNullException(nameof(outputStream));
if (!outputStream.CanWrite)
throw new InvalidOperationException("Input stream must be writable.");
throw new NotSupportedException("Input stream must be writable.");

_logger?.LogTrace($"Download requested: {uri.AbsoluteUri}");

if (!uri.IsFile && !uri.IsUnc)
{
if (!string.Equals(uri.Scheme, "http", StringComparison.OrdinalIgnoreCase) &&
Expand All @@ -95,7 +97,7 @@ public Task<DownloadSummary> DownloadAsync(Uri uri, Stream outputStream, Progres
{
var providers = GetSuitableProvider(uri);
return Task.Run(async () =>
await DownloadWithRetry(providers, uri, outputStream, progress, verificationContext, cancellationToken)
await DownloadWithRetry(providers, uri, outputStream, progress, validator, cancellationToken)
.ConfigureAwait(false), cancellationToken);
}
catch (Exception ex)
Expand All @@ -110,12 +112,12 @@ internal void RemoveAllEngines()
_allProviders.Clear();
}

private async Task<DownloadSummary> DownloadWithRetry(IList<IDownloadProvider> providers, Uri uri, Stream outputStream,
ProgressUpdateCallback? progress, IVerificationContext? verificationContext, CancellationToken cancellationToken)
private async Task<DownloadResult> DownloadWithRetry(IList<IDownloadProvider> providers, Uri uri, Stream outputStream,
ProgressUpdateCallback? progress, IDownloadValidator? validator, CancellationToken cancellationToken)
{
if (_configuration.VerificationPolicy == VerificationPolicy.Enforce && verificationContext is null)
if (_configuration.ValidationPolicy == ValidationPolicy.Required && validator is null)
{
var exception = new VerificationFailedException("No verification context available to verify the download.");
var exception = new NotSupportedException("A validation callback is required for this download.");
_logger?.LogError(exception, exception.Message);
throw exception;
}
Expand All @@ -133,40 +135,56 @@ private async Task<DownloadSummary> DownloadWithRetry(IList<IDownloadProvider> p
{
progress?.Invoke(new ProgressUpdateStatus(provider.Name, status.BytesRead, status.TotalBytes, status.BitRate));
}, cancellationToken).ConfigureAwait(false);

if (outputStream.Length == 0 && !_configuration.AllowEmptyFileDownload)
{
var exception = new Exception($"Empty file downloaded on '{uri}'.");
var exception = new InvalidOperationException($"Empty file downloaded on '{uri}'.");
_logger?.LogError(exception, exception.Message);
throw exception;
}

if (_configuration.VerificationPolicy != VerificationPolicy.Skip && verificationContext is not null)

if (_configuration.ValidationPolicy == ValidationPolicy.NoValidation)
{
_logger?.LogTrace("Skipping validation because verification context of is not valid.");
}
else
{
var valid = verificationContext.Verify();
if (valid)
if (validator is null)
{
_logger?.LogTrace("Skipping validation because verification context of is not valid.");
}
else
{
var verificationResult = _verifier.Verify(outputStream, verificationContext);
summary.ValidationResult = verificationResult;
if (verificationResult.Status != VerificationResultStatus.Success)
bool validationSuccess;
try
{
validationSuccess = await validator.Validate(outputStream, summary.DownloadedSize, cancellationToken)
.ConfigureAwait(false);
}
catch (Exception e)
{
var exception = new VerificationFailedException(
$"Verification on downloaded file '{uri.AbsoluteUri}' was not successful: {verificationResult.Status}");
var exception = new DownloadValidationFailedException(
$"Validation of '{uri.AbsoluteUri}' failed with exception: {e.Message}", e);
_logger?.LogError(exception, exception.Message);
throw exception;
}

if (!validationSuccess)
{
var exception = new DownloadValidationFailedException(
$"Downloaded file '{uri.AbsoluteUri}' is not valid.");
_logger?.LogError(exception, exception.Message);
throw exception;
}
}
else
{
if (_configuration.VerificationPolicy is VerificationPolicy.Optional or VerificationPolicy.Enforce)
throw new VerificationFailedException("Download is missing or has an invalid VerificationContext");
_logger?.LogTrace("Skipping validation because verification context of is not valid.");
}
}

_logger?.LogInformation($"Download of '{uri.AbsoluteUri}' succeeded using provider '{provider.Name}'");
_preferredDownloadProviders.LastSuccessfulProviderName = provider.Name;

summary.DownloadProvider = provider.Name;

return summary;
}
catch (OperationCanceledException)
Expand All @@ -192,7 +210,8 @@ private async Task<DownloadSummary> DownloadWithRetry(IList<IDownloadProvider> p
continue;

_logger?.LogTrace($"Sleeping {millisecondsTimeout} before retrying download.");
Thread.Sleep(millisecondsTimeout);

await Task.Delay(TimeSpan.FromMilliseconds(millisecondsTimeout), cancellationToken);
}
}

Expand All @@ -201,7 +220,7 @@ private async Task<DownloadSummary> DownloadWithRetry(IList<IDownloadProvider> p

private IList<IDownloadProvider> GetSuitableProvider(Uri uri)
{
var source = uri.IsFile || uri.IsUnc ? DownloadSource.File : DownloadSource.Internet;
var source = uri.IsFile || uri.IsUnc ? DownloadKind.File : DownloadKind.Internet;
var supportedProviders = _allProviders.Where(e => e.IsSupported(source)).ToList();
if (!supportedProviders.Any())
{
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -4,12 +4,12 @@
namespace AnakinRaW.CommonUtilities.DownloadManager;

/// <summary>
/// Get's thrown if there could be no <see cref="IDownloadProvider"/> found for a download operation.
/// Thrown if there could be no <see cref="IDownloadProvider"/> found for a download operation.
/// </summary>
public class DownloadProviderNotFoundException : InvalidOperationException
public sealed class DownloadProviderNotFoundException : InvalidOperationException
{
/// <summary>
/// Creates the exception
/// Initializes a new instance of the DownloadProviderNotFoundException class.
/// </summary>
/// <param name="message">The message of the exception</param>
public DownloadProviderNotFoundException(string message)
Expand Down
Loading

0 comments on commit 0ed546e

Please sign in to comment.