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
12 changes: 5 additions & 7 deletions Directory.Packages.props
Original file line number Diff line number Diff line change
@@ -1,8 +1,8 @@
<Project>
<PropertyGroup>
<ManagePackageVersionsCentrally>true</ManagePackageVersionsCentrally>
<MicrosoftExtensionsVersion>9.0.0</MicrosoftExtensionsVersion>
<MicrosoftExtensionsPatchVersion>9.0.20</MicrosoftExtensionsPatchVersion>
<MicrosoftExtensionsVersion>10.0.0</MicrosoftExtensionsVersion>
<MicrosoftExtensionsPatchVersion>10.0.12</MicrosoftExtensionsPatchVersion>
</PropertyGroup>

<ItemGroup>
Expand All @@ -12,11 +12,9 @@

<ItemGroup>
<!-- External Libraries -->
<PackageVersion Include="coverlet.collector" Version="6.0.4" />
<PackageVersion Include="coverlet.collector" Version="10.1.0" />
<PackageVersion Include="ClosedXML" Version="0.104.2" />
<PackageVersion Include="FluentAssertions" Version="8.8.0" />
<PackageVersion Include="MediatR" Version="12.5.0" />
<PackageVersion Include="MediatR.Extensions.Microsoft.DependencyInjection" Version="11.1.0" />
<PackageVersion Include="Microsoft.Extensions.DependencyInjection" Version="$(MicrosoftExtensionsPatchVersion)" />
<PackageVersion Include="Microsoft.Extensions.Hosting" Version="$(MicrosoftExtensionsPatchVersion)" />
<PackageVersion Include="Microsoft.Extensions.Hosting.WindowsServices" Version="$(MicrosoftExtensionsPatchVersion)" />
Expand All @@ -32,8 +30,8 @@
<PackageVersion Include="Moq.Contrib.HttpClient" Version="1.4.0" />
<PackageVersion Include="Newtonsoft.Json" Version="13.0.4" />
<PackageVersion Include="Serilog" Version="4.4.0" />
<PackageVersion Include="Serilog.Extensions.Hosting" Version="9.0.0" />
<PackageVersion Include="Serilog.Extensions.Logging" Version="9.0.2" />
<PackageVersion Include="Serilog.Extensions.Hosting" Version="10.0.0" />
<PackageVersion Include="Serilog.Extensions.Logging" Version="10.0.0" />
<PackageVersion Include="Serilog.Sinks.Console" Version="6.1.1" />
<PackageVersion Include="Serilog.Sinks.File" Version="7.0.0" />
<PackageVersion Include="SharpZipLib" Version="1.4.2" />
Expand Down
4 changes: 2 additions & 2 deletions global.json
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
{
"sdk": {
"version": "9.0.0",
"version": "10.0.0",
"rollForward": "latestFeature",
"allowPrerelease": false
}
}
}
2 changes: 1 addition & 1 deletion publish/publish-agent.ps1
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,7 @@ $buildVersion
New-Item -ItemType Directory -Force -Path ".\published"
# Remove files/folders inside the published folder
Remove-Item ".\published\*" -Recurse -Force
dotnet publish ../src/QF.BySoft.Integration -r win-x64 -p:PublishSingleFile=true /p:Version=$buildVersion -f net9.0 -p:IncludeAllContentForSelfExtract=true -c Release /p:DebugType=none /p:DebugSymbols=false -o ./published/Integration
dotnet publish ../src/QF.BySoft.Integration -r win-x64 -p:PublishSingleFile=true /p:Version=$buildVersion -f net10.0 -p:IncludeAllContentForSelfExtract=true -c Release /p:DebugType=none /p:DebugSymbols=false -o ./published/Integration
Copy-Item "../src/QF.BySoft.Integration/appsettings.json" -Destination ".\published\Integration"
Copy-Item ".\data" -Destination ".\published\Integration" -Recurse
# Copy the manual
Expand Down
2 changes: 1 addition & 1 deletion publish/published-latest-version.txt
Original file line number Diff line number Diff line change
@@ -1 +1 @@
2026.03.25.18195
2026.10.01.24738
2 changes: 1 addition & 1 deletion src/QF.BySoft.Entities/QF.BySoft.Entities.csproj
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
<Project Sdk="Microsoft.NET.Sdk">

<PropertyGroup>
<TargetFramework>net9.0</TargetFramework>
<TargetFramework>net10.0</TargetFramework>
</PropertyGroup>

</Project>
Original file line number Diff line number Diff line change
Expand Up @@ -7,11 +7,10 @@ public static class DependencyInjectionExtensions
{
public static void AddFileWatchFeature(this IServiceCollection services)
{
// register agent output file created handler
services.AddTransient<AgentOutputFileCreatedHandler>();

// register agent output file watcher service
services.AddHostedService<AgentOutputFileWatcherService>();

// register MediatR with current assembly
services.AddMediatR(cfg => cfg.RegisterServicesFromAssembly(typeof(AgentOutputFileWatcherService).Assembly));
}
}
Original file line number Diff line number Diff line change
@@ -1,8 +1,6 @@
using MediatR;
namespace QF.BySoft.Integration.Features.AgentOutputFile;

namespace QF.BySoft.Integration.Features.AgentOutputFile;

public class AgentOutputFileCreated : INotification
public class AgentOutputFileCreated
{
public AgentOutputFileCreated(string filePath)
{
Expand Down
Original file line number Diff line number Diff line change
@@ -1,12 +1,11 @@
using System.Threading;
using System.Threading.Tasks;
using MediatR;
using Microsoft.Extensions.Logging;
using QF.BySoft.Integration.Features.BySoftIntegration;

namespace QF.BySoft.Integration.Features.AgentOutputFile;

public class AgentOutputFileCreatedHandler : INotificationHandler<AgentOutputFileCreated>
public class AgentOutputFileCreatedHandler
{
private readonly IBySoftIntegration _bySoftIntegration;
private readonly ILogger<AgentOutputFileCreatedHandler> _logger;
Expand All @@ -19,7 +18,7 @@ public AgentOutputFileCreatedHandler(
_bySoftIntegration = bySoftIntegration;
}

public async Task Handle(AgentOutputFileCreated notification, CancellationToken cancellationToken)
public async Task HandleAsync(AgentOutputFileCreated notification, CancellationToken cancellationToken)
{
_logger.LogInformation("File created: {FilePath}", notification.FilePath);

Expand Down
Original file line number Diff line number Diff line change
@@ -1,7 +1,8 @@
using System;
using System.IO;
using System.Threading;
using MediatR;
using System.Threading.Tasks;
using Microsoft.Extensions.DependencyInjection;
using Microsoft.Extensions.Logging;
using Microsoft.Extensions.Options;
using QF.BySoft.Entities;
Expand All @@ -11,20 +12,20 @@ namespace QF.BySoft.Integration.Features.AgentOutputFile;

/// <summary>
/// Service that watches on the output directory of the agent for *.json files
/// Publishes an AgentOutputFileCreated notification if a file is created
/// Calls AgentOutputFileCreatedHandler if a file is created
/// </summary>
public class AgentOutputFileWatcherService : FileWatcherService
{
private readonly ILogger<AgentOutputFileWatcherService> _logger;
private readonly IMediator _mediator;
private readonly IServiceScopeFactory _serviceScopeFactory;
private readonly SemaphoreSlim _semaphore = new(1, 1);

public AgentOutputFileWatcherService(
IMediator mediator,
IServiceScopeFactory serviceScopeFactory,
IOptions<BySoftIntegrationSettings> options,
ILogger<AgentOutputFileWatcherService> logger)
{
_mediator = mediator;
_serviceScopeFactory = serviceScopeFactory;
_logger = logger;

// add file watcher to the agent output directory
Expand All @@ -49,7 +50,7 @@ protected override async void OnAllChanges(object sender, FileSystemEventArgs e)
switch (e.ChangeType)
{
case WatcherChangeTypes.Created:
await _mediator.Publish(new AgentOutputFileCreated(e.FullPath));
await HandleCreatedFileAsync(e.FullPath, CancellationToken.None);
break;
case WatcherChangeTypes.Deleted:
case WatcherChangeTypes.Changed:
Expand Down Expand Up @@ -85,7 +86,7 @@ protected override async void OnExistingFile(object sender, FileSystemEventArgs
case WatcherChangeTypes.Renamed:
break;
case WatcherChangeTypes.All:
await _mediator.Publish(new AgentOutputFileCreated(e.FullPath));
await HandleCreatedFileAsync(e.FullPath, CancellationToken.None);
break;
default:
throw new ArgumentOutOfRangeException();
Expand All @@ -100,4 +101,11 @@ protected override async void OnExistingFile(object sender, FileSystemEventArgs
_semaphore.Release();
}
}

private async Task HandleCreatedFileAsync(string filePath, CancellationToken cancellationToken)
{
using var scope = _serviceScopeFactory.CreateScope();
var handler = scope.ServiceProvider.GetRequiredService<AgentOutputFileCreatedHandler>();
await handler.HandleAsync(new AgentOutputFileCreated(filePath), cancellationToken);
}
}
3 changes: 1 addition & 2 deletions src/QF.BySoft.Integration/QF.BySoft.Integration.csproj
Original file line number Diff line number Diff line change
Expand Up @@ -2,12 +2,11 @@

<PropertyGroup>
<OutputType>Exe</OutputType>
<TargetFramework>net9.0</TargetFramework>
<TargetFramework>net10.0</TargetFramework>
<UserSecretsId>eb167adc-6dc9-410b-968d-79e97bdcacb8</UserSecretsId>
</PropertyGroup>

<ItemGroup>
<PackageReference Include="MediatR" />
<PackageReference Include="Microsoft.Extensions.DependencyInjection" />
<PackageReference Include="Microsoft.Extensions.Hosting" />
<PackageReference Include="Microsoft.Extensions.Hosting.WindowsServices" />
Expand Down
2 changes: 1 addition & 1 deletion src/QF.BySoft.LocalData/QF.BySoft.LocalData.csproj
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
<Project Sdk="Microsoft.NET.Sdk">

<PropertyGroup>
<TargetFramework>net9.0</TargetFramework>
<TargetFramework>net10.0</TargetFramework>
</PropertyGroup>

<ItemGroup>
Expand Down
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
<Project Sdk="Microsoft.NET.Sdk">

<PropertyGroup>
<TargetFramework>net9.0</TargetFramework>
<TargetFramework>net10.0</TargetFramework>
</PropertyGroup>

<ItemGroup>
Expand Down
108 changes: 108 additions & 0 deletions src/QF.BySoft.Tests/AgentOutputFileWatcherServiceTests.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,108 @@
using System;
using System.IO;
using System.Threading;
using System.Threading.Tasks;
using FluentAssertions;
using Microsoft.Extensions.DependencyInjection;
using Microsoft.Extensions.Logging;
using Microsoft.Extensions.Options;
using Moq;
using QF.BySoft.Entities;
using QF.BySoft.Integration.Features.AgentOutputFile;
using QF.BySoft.Integration.Features.BySoftIntegration;
using QF.BySoft.Tests.Util;
using Xunit;

namespace QF.BySoft.Tests;

public class AgentOutputFileWatcherServiceTests
{
[Fact]
public async Task OnAllChangesShouldResolveNewBySoftIntegrationPerCreatedFile()
{
var rootDirectory = Path.Combine(Path.GetTempPath(), $"{nameof(AgentOutputFileWatcherServiceTests)}-{Guid.NewGuid():N}");
Directory.CreateDirectory(rootDirectory);

var state = new TestState(expectedHandledCount: 2);
var settings = SettingsBuilder.GetBySoftIntegrationSettings();
settings.RootDirectory = rootDirectory;

var serviceProvider = BuildServiceProvider(settings, state);
var sut = new TestAgentOutputFileWatcherService(
serviceProvider.GetRequiredService<IServiceScopeFactory>(),
serviceProvider.GetRequiredService<IOptions<BySoftIntegrationSettings>>(),
serviceProvider.GetRequiredService<ILogger<AgentOutputFileWatcherService>>()
);

try
{
sut.RaiseAllChanges(new FileSystemEventArgs(WatcherChangeTypes.Created, rootDirectory, "first.json"));
sut.RaiseAllChanges(new FileSystemEventArgs(WatcherChangeTypes.Created, rootDirectory, "second.json"));

await state.AllHandled.Task.WaitAsync(TimeSpan.FromSeconds(10));

state.HandledCount.Should().Be(2);
state.ResolutionCount.Should().Be(2, "a fresh scoped handler dependency graph should be resolved for each event");
}
finally
{
await sut.StopAsync(CancellationToken.None);
await serviceProvider.DisposeAsync();
Directory.Delete(rootDirectory, true);
}
}

private static ServiceProvider BuildServiceProvider(BySoftIntegrationSettings settings, TestState state)
{
var services = new ServiceCollection();
services.AddSingleton<IOptions<BySoftIntegrationSettings>>(Options.Create(settings));
services.AddSingleton(Mock.Of<ILogger<AgentOutputFileCreatedHandler>>());
services.AddSingleton(Mock.Of<ILogger<AgentOutputFileWatcherService>>());
services.AddTransient<IBySoftIntegration>(_ =>
{
Interlocked.Increment(ref state.ResolutionCount);
var bySoftIntegrationMock = new Mock<IBySoftIntegration>();
bySoftIntegrationMock
.Setup(x => x.HandleManufacturabilityCheckRequestAsync(It.IsAny<string>()))
.Returns(() =>
{
if (Interlocked.Increment(ref state.HandledCount) == state.ExpectedHandledCount)
{
state.AllHandled.TrySetResult(true);
}

return Task.CompletedTask;
});
return bySoftIntegrationMock.Object;
});
services.AddTransient<AgentOutputFileCreatedHandler>();

return services.BuildServiceProvider();
}

private sealed class TestAgentOutputFileWatcherService : AgentOutputFileWatcherService
{
public TestAgentOutputFileWatcherService(
IServiceScopeFactory serviceScopeFactory,
IOptions<BySoftIntegrationSettings> options,
ILogger<AgentOutputFileWatcherService> logger)
: base(serviceScopeFactory, options, logger)
{
}

public void RaiseAllChanges(FileSystemEventArgs fileSystemEventArgs)
{
OnAllChanges(this, fileSystemEventArgs);
}
}

private sealed class TestState(int expectedHandledCount)
{
public TaskCompletionSource<bool> AllHandled { get; } = new(TaskCreationOptions.RunContinuationsAsynchronously);
public int ExpectedHandledCount { get; } = expectedHandledCount;
public int HandledCount;
public int ResolutionCount;
}
}


6 changes: 5 additions & 1 deletion src/QF.BySoft.Tests/QF.BySoft.Tests.csproj
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
<Project Sdk="Microsoft.NET.Sdk">

<PropertyGroup>
<TargetFramework>net9.0</TargetFramework>
<TargetFramework>net10.0</TargetFramework>
<IsPackable>false</IsPackable>
<NoWarn>$(NoWarn);VSTHRD200</NoWarn>
<!--
Expand All @@ -11,7 +11,9 @@

<ItemGroup>
<PackageReference Include="FluentAssertions" />
<PackageReference Include="Microsoft.Extensions.DependencyInjection" />
<PackageReference Include="Microsoft.NET.Test.Sdk" />
<PackageReference Include="Microsoft.Extensions.Options" />
<PackageReference Include="Moq" />
<PackageReference Include="Moq.Contrib.HttpClient" />
<PackageReference Include="xunit" />
Expand All @@ -22,7 +24,9 @@
</ItemGroup>

<ItemGroup>
<ProjectReference Include="..\QF.BySoft.Integration\QF.BySoft.Integration.csproj" />
<ProjectReference Include="..\QF.BySoft.Manufacturability\QF.BySoft.Manufacturability.csproj" />
<ProjectReference Include="..\QF.Integration.Common\QF.Integration.Common.csproj" />
</ItemGroup>

<ItemGroup>
Expand Down
3 changes: 1 addition & 2 deletions src/QF.Integration.Common/QF.Integration.Common.csproj
Original file line number Diff line number Diff line change
@@ -1,13 +1,12 @@
<Project Sdk="Microsoft.NET.Sdk">

<PropertyGroup>
<TargetFramework>net9.0</TargetFramework>
<TargetFramework>net10.0</TargetFramework>
<ImplicitUsings>enable</ImplicitUsings>
<Nullable>enable</Nullable>
</PropertyGroup>

<ItemGroup>
<PackageReference Include="MediatR"/>
<PackageReference Include="Microsoft.Extensions.Hosting.Abstractions" />
<PackageReference Include="Microsoft.Extensions.Options" />
<PackageReference Include="Newtonsoft.Json" />
Expand Down
Loading