diff --git a/src/common/Ark.Tools.FtpClient.FluentFtp/FluentFtpClientConnection.cs b/src/common/Ark.Tools.FtpClient.FluentFtp/FluentFtpClientConnection.cs index c87051ef1..cadba3a04 100644 --- a/src/common/Ark.Tools.FtpClient.FluentFtp/FluentFtpClientConnection.cs +++ b/src/common/Ark.Tools.FtpClient.FluentFtp/FluentFtpClientConnection.cs @@ -12,12 +12,30 @@ namespace Ark.Tools.FtpClient.FluentFtp; public sealed class FluentFtpClientConnection : FtpClientConnectionBase { private readonly IAsyncFtpClient _client; + private readonly Action? _configureClient; private bool _isDisposed; + /// + /// Initializes a new instance of the class. + /// + /// The FTP connection settings. public FluentFtpClientConnection(FtpConfig ftpConfig) + : this(ftpConfig, null) + { + } + + /// + /// Initializes a new instance of the class. + /// + /// The FTP connection settings. + /// + /// Callback used to customize the FluentFTP client configuration for the requested host before the client is created. + /// + public FluentFtpClientConnection(FtpConfig ftpConfig, Action? configureClient) : base(ftpConfig) { + _configureClient = configureClient; _client = _getClient(); } @@ -89,13 +107,13 @@ protected override void Dispose(bool disposing) private IAsyncFtpClient _getClient() { - AsyncFtpClient client; - - client = new AsyncFtpClient(Uri.Host, Credentials, Uri.Port, new FluentFTP.FtpConfig + var clientConfig = new FluentFTP.FtpConfig { SocketKeepAlive = true, - }); + }; + + _configureClient?.Invoke(Uri.Host, clientConfig); - return client; + return new AsyncFtpClient(Uri.Host, Credentials, Uri.Port, clientConfig); } } \ No newline at end of file diff --git a/src/common/Ark.Tools.FtpClient.FluentFtp/FluentFtpClientConnectionFactory.cs b/src/common/Ark.Tools.FtpClient.FluentFtp/FluentFtpClientConnectionFactory.cs index e7d28f217..d1019c7f1 100644 --- a/src/common/Ark.Tools.FtpClient.FluentFtp/FluentFtpClientConnectionFactory.cs +++ b/src/common/Ark.Tools.FtpClient.FluentFtp/FluentFtpClientConnectionFactory.cs @@ -8,12 +8,33 @@ namespace Ark.Tools.FtpClient.FluentFtp; public class FluentFtpClientConnectionFactory : IFtpClientConnectionFactory { + private readonly Action? _configureClient; + + /// + /// Initializes a new instance of the class. + /// + public FluentFtpClientConnectionFactory() + { + } + + /// + /// Initializes a new instance of the class. + /// + /// + /// Callback used to customize the FluentFTP client configuration for the requested host before the client is created. + /// + public FluentFtpClientConnectionFactory(Action configureClient) + { + _configureClient = configureClient ?? throw new ArgumentNullException(nameof(configureClient)); + } + + /// public IFtpClientConnection Create(FtpConfig ftpConfig) { ArgumentNullException.ThrowIfNull(ftpConfig); ArgumentNullException.ThrowIfNull(ftpConfig.Uri); ArgumentNullException.ThrowIfNull(ftpConfig.Credentials); - return new FluentFtpClientConnection(ftpConfig); + return new FluentFtpClientConnection(ftpConfig, _configureClient); } } \ No newline at end of file diff --git a/src/common/Ark.Tools.FtpClient.FluentFtp/FluentFtpClientFactory.cs b/src/common/Ark.Tools.FtpClient.FluentFtp/FluentFtpClientFactory.cs index 2d5084519..85cb39261 100644 --- a/src/common/Ark.Tools.FtpClient.FluentFtp/FluentFtpClientFactory.cs +++ b/src/common/Ark.Tools.FtpClient.FluentFtp/FluentFtpClientFactory.cs @@ -6,8 +6,22 @@ namespace Ark.Tools.FtpClient.FluentFtp; public sealed class FluentFtpClientFactory : DefaultFtpClientFactory { + /// + /// Initializes a new instance of the class. + /// public FluentFtpClientFactory() : base(new FluentFtpClientConnectionFactory()) { } + + /// + /// Initializes a new instance of the class. + /// + /// + /// Callback used to customize the FluentFTP client configuration for the requested host before the client is created. + /// + public FluentFtpClientFactory(Action configureClient) + : base(new FluentFtpClientConnectionFactory(configureClient)) + { + } } \ No newline at end of file diff --git a/src/common/Ark.Tools.FtpClient.FluentFtp/FluentFtpClientPoolFactory.cs b/src/common/Ark.Tools.FtpClient.FluentFtp/FluentFtpClientPoolFactory.cs index 4c78e6bd2..d9ae50775 100644 --- a/src/common/Ark.Tools.FtpClient.FluentFtp/FluentFtpClientPoolFactory.cs +++ b/src/common/Ark.Tools.FtpClient.FluentFtp/FluentFtpClientPoolFactory.cs @@ -6,8 +6,22 @@ namespace Ark.Tools.FtpClient.FluentFtp; public sealed class FluentFtpClientPoolFactory : DefaultFtpClientPoolFactory { + /// + /// Initializes a new instance of the class. + /// public FluentFtpClientPoolFactory() : base(new FluentFtpClientConnectionFactory()) { } + + /// + /// Initializes a new instance of the class. + /// + /// + /// Callback used to customize the FluentFTP client configuration for the requested host before the client is created. + /// + public FluentFtpClientPoolFactory(Action configureClient) + : base(new FluentFtpClientConnectionFactory(configureClient)) + { + } } \ No newline at end of file diff --git a/tests/Ark.Tools.FtpClient.FluentFtp.Tests/Ark.Tools.FtpClient.FluentFtp.Tests.csproj b/tests/Ark.Tools.FtpClient.FluentFtp.Tests/Ark.Tools.FtpClient.FluentFtp.Tests.csproj new file mode 100644 index 000000000..0bbf7de22 --- /dev/null +++ b/tests/Ark.Tools.FtpClient.FluentFtp.Tests/Ark.Tools.FtpClient.FluentFtp.Tests.csproj @@ -0,0 +1,12 @@ + + + + net10.0 + false + + + + + + + diff --git a/tests/Ark.Tools.FtpClient.FluentFtp.Tests/FluentFtpClientFactoryTests.cs b/tests/Ark.Tools.FtpClient.FluentFtp.Tests/FluentFtpClientFactoryTests.cs new file mode 100644 index 000000000..9e05b3a79 --- /dev/null +++ b/tests/Ark.Tools.FtpClient.FluentFtp.Tests/FluentFtpClientFactoryTests.cs @@ -0,0 +1,117 @@ +// Copyright (C) 2024 Ark Energy S.r.l. All rights reserved. +// Licensed under the MIT License. See LICENSE file for license information. + +using Ark.Tools.FtpClient.Core; + +using AwesomeAssertions; + +using System.Net; +using System.Reflection; + +namespace Ark.Tools.FtpClient.FluentFtp.Tests; + +[TestClass] +public class FluentFtpClientFactoryTests +{ + [TestMethod] + public void ClientFactory_ShouldInvokeHostAwareConfigCallback() + { + var callbackHost = string.Empty; + var ftpClientFactory = new FluentFtpClientFactory((host, config) => + { + callbackHost = host; + config.SocketKeepAlive = false; + }); + + using var setup = CreateConnectionFromFactory(ftpClientFactory, "ftp://client-factory.example.com"); + var clientConfig = GetFluentFtpConfig(setup.Connection); + + callbackHost.Should().Be("client-factory.example.com"); + clientConfig.SocketKeepAlive.Should().BeFalse(); + } + + [TestMethod] + public void PoolFactory_ShouldInvokeHostAwareConfigCallback() + { + var callbackHost = string.Empty; + var ftpClientPoolFactory = new FluentFtpClientPoolFactory((host, config) => + { + callbackHost = host; + config.SocketKeepAlive = false; + }); + + using var setup = CreateConnectionFromFactory(ftpClientPoolFactory, "ftp://pool-factory.example.com"); + var clientConfig = GetFluentFtpConfig(setup.Connection); + + callbackHost.Should().Be("pool-factory.example.com"); + clientConfig.SocketKeepAlive.Should().BeFalse(); + } + + private static FluentFTP.FtpConfig GetFluentFtpConfig(FluentFtpClientConnection connection) + { + var clientField = typeof(FluentFtpClientConnection).GetField("_client", BindingFlags.Instance | BindingFlags.NonPublic); + clientField.Should().NotBeNull(); + + var client = clientField!.GetValue(connection); + client.Should().NotBeNull(); + + var configProperty = client!.GetType().GetProperty("Config", BindingFlags.Instance | BindingFlags.Public); + configProperty.Should().NotBeNull(); + + var config = configProperty!.GetValue(client); + config.Should().BeOfType(); + if (config is not FluentFTP.FtpConfig ftpConfig) + { + throw new InvalidOperationException("Expected Config to be a non-null FluentFTP.FtpConfig."); + } + + return ftpConfig; + } + + private static ConnectionSetup CreateConnectionFromFactory(FluentFtpClientFactory ftpClientFactory, string uri) + { + var connectionFactoryField = typeof(DefaultFtpClientFactory).GetField("_connectionFactory", BindingFlags.Instance | BindingFlags.NonPublic); + connectionFactoryField.Should().NotBeNull(); + + var connectionFactory = connectionFactoryField!.GetValue(ftpClientFactory) as IFtpClientConnectionFactory; + connectionFactory.Should().NotBeNull(); + + var ftpConfig = new FtpConfig(new Uri(uri), new NetworkCredential("user", "password")); + var connection = connectionFactory!.Create(ftpConfig); + return new ConnectionSetup(connection.Should().BeOfType().Subject, ftpConfig); + } + + private static ConnectionSetup CreateConnectionFromFactory(FluentFtpClientPoolFactory ftpClientPoolFactory, string uri) + { + var connectionFactoryField = typeof(DefaultFtpClientPoolFactory).GetField("_connectionFactory", BindingFlags.Instance | BindingFlags.NonPublic); + connectionFactoryField.Should().NotBeNull(); + + if (connectionFactoryField!.GetValue(ftpClientPoolFactory) is not IFtpClientConnectionFactory connectionFactory) + { + throw new InvalidOperationException("Expected _connectionFactory to be an IFtpClientConnectionFactory."); + } + + var ftpConfig = new FtpConfig(new Uri(uri), new NetworkCredential("user", "password")); + var connection = connectionFactory.Create(ftpConfig); + return new ConnectionSetup(connection.Should().BeOfType().Subject, ftpConfig); + } + + private sealed class ConnectionSetup : IDisposable + { + public ConnectionSetup(FluentFtpClientConnection connection, FtpConfig ftpConfig) + { + Connection = connection; + _ftpConfig = ftpConfig; + } + + public FluentFtpClientConnection Connection { get; } + + private readonly FtpConfig _ftpConfig; + + public void Dispose() + { + Connection.Dispose(); + _ftpConfig.Dispose(); + } + } +} diff --git a/tests/Ark.Tools.FtpClient.FluentFtp.Tests/packages.lock.json b/tests/Ark.Tools.FtpClient.FluentFtp.Tests/packages.lock.json new file mode 100644 index 000000000..fb0c47d2c --- /dev/null +++ b/tests/Ark.Tools.FtpClient.FluentFtp.Tests/packages.lock.json @@ -0,0 +1,655 @@ +{ + "version": 2, + "dependencies": { + "net10.0": { + "AwesomeAssertions": { + "type": "Direct", + "requested": "[9.4.0, )", + "resolved": "9.4.0", + "contentHash": "dJxkWiQ8D+xT6Gr2sSL83+Mar+Vpy2JTcUPxFcckpPJ8VYBfSgnk+zqpS6t7kcGnjz8NLyF14qfuoL4bKzzoew==" + }, + "ErrorProne.NET.CoreAnalyzers": { + "type": "Direct", + "requested": "[0.1.2, )", + "resolved": "0.1.2", + "contentHash": "RQdzUtFVhwLwisP2Du7Ugm7ldwAzc+QJrlZWNIwHXcgq4b5fLwZgdN1203RdYiHS8OOALophC6yWekxu3UgN2g==" + }, + "Meziantou.Analyzer": { + "type": "Direct", + "requested": "[3.0.117, )", + "resolved": "3.0.117", + "contentHash": "22L/6jjjhTwOpGqN+3A4EMCWc8Kc0dAd49zINAlxjLYDxERnD1qPKDkE9Z6cZPjiOs32J9nsieyya1FO+kXHCw==" + }, + "Microsoft.CodeAnalysis.BannedApiAnalyzers": { + "type": "Direct", + "requested": "[4.14.0, )", + "resolved": "4.14.0", + "contentHash": "gSWJlDWwmDhtbrEJGiHqvEjz9KthIiFD0qYB8zZ6a7z+xpMSPEtM9yTYELSa58iFWYlzSRqP9FXO6KoT3+ZMtg==" + }, + "Microsoft.CodeAnalysis.NetAnalyzers": { + "type": "Direct", + "requested": "[10.0.301, )", + "resolved": "10.0.301", + "contentHash": "QAHyooyQN0JB95pI8q/N/eAH7Bb0OWSDSSNFGlmuxC+b/fXUVQJJChOBE6ygJFNkRj42u7rCHCSCPQC5n30tNg==" + }, + "Microsoft.NET.Test.Sdk": { + "type": "Direct", + "requested": "[18.7.0, )", + "resolved": "18.7.0", + "contentHash": "49xH9j4UzCh2hMohJp53g3wUTvyycECw7CtVht4gfCz5ykudB1uBcF6D0TtgJPjCtP76UPW53bQElKdCeX+dUg==", + "dependencies": { + "Microsoft.CodeCoverage": "18.7.0", + "Microsoft.TestPlatform.TestHost": "18.7.0" + } + }, + "Microsoft.Sbom.Targets": { + "type": "Direct", + "requested": "[4.1.5, )", + "resolved": "4.1.5", + "contentHash": "i5z+cNu/cOcdO0AgFB8aXk8w6In2H+haaDfSgd9ImvQIK+rSHavHZIogVoAZLL8jLwYx4bAcs5b7EyuMMG4mQQ==" + }, + "Microsoft.Testing.Extensions.AzureDevOpsReport": { + "type": "Direct", + "requested": "[2.2.3, )", + "resolved": "2.2.3", + "contentHash": "08SCwk+ZeWzmqdXOmatulRKrc07hZKXGs3ODPTDS988Jcg8uSRiB8AR5AAZPnAky2XHL8xMuSJF42F//p+Yc2Q==", + "dependencies": { + "Microsoft.Testing.Platform": "2.2.3" + } + }, + "Microsoft.Testing.Extensions.CodeCoverage": { + "type": "Direct", + "requested": "[18.8.0, )", + "resolved": "18.8.0", + "contentHash": "euA4tpkGAkfHznVQrPzXFLNaUhcRCIKPkDmJJB+A2XU9d5ymHLhQ2Do0fGc/Z2y+VFUaNnM6vHhIrb4FW+qhtg==", + "dependencies": { + "Microsoft.DiaSymReader": "2.2.3", + "Microsoft.Extensions.DependencyModel": "8.0.2", + "Microsoft.Testing.Platform": "2.2.3" + } + }, + "Microsoft.Testing.Extensions.CrashDump": { + "type": "Direct", + "requested": "[2.2.3, )", + "resolved": "2.2.3", + "contentHash": "wop2zrjwNhHLZfo0UJbTOBe5h9kwQsi2O5+ZPRi41KkFzM5obxWYLIpQYw6p/iBv81/9rT7Jn/IeInMFtkYShg==", + "dependencies": { + "Microsoft.Testing.Extensions.TrxReport.Abstractions": "2.2.3", + "Microsoft.Testing.Platform": "2.2.3" + } + }, + "Microsoft.Testing.Extensions.HangDump": { + "type": "Direct", + "requested": "[2.2.3, )", + "resolved": "2.2.3", + "contentHash": "VR3pTDWbBPZ15pZeXzlQzcsGiQ22sd7+M3OJzaOgLLKojMySnS45F5PPSJ4RHxfge2h52yN9m2hNn5U8YiGm2w==", + "dependencies": { + "Microsoft.Diagnostics.NETCore.Client": "0.2.607501", + "Microsoft.Testing.Platform": "2.2.3" + } + }, + "Microsoft.Testing.Extensions.HotReload": { + "type": "Direct", + "requested": "[2.2.3, )", + "resolved": "2.2.3", + "contentHash": "2ojTkDg/UyVDyCRzOdD28Qckb4Ds6tWPqk+6+c+vfmIE+nnr9bIVZHJ5ZjnXd2LNT3iUqGtt/4nEAwDPyPLI8Q==", + "dependencies": { + "Microsoft.Testing.Platform": "2.2.3" + } + }, + "Microsoft.Testing.Extensions.Retry": { + "type": "Direct", + "requested": "[2.2.3, )", + "resolved": "2.2.3", + "contentHash": "YYhoLdZOksV5S+poW4b5Kx9hCcO41YpBcExPgpHlKLf7B+LFyjWryF9zPmKCWV/W7LBpo+HgDZXa5o2WVi5/Eg==", + "dependencies": { + "Microsoft.Testing.Platform": "2.2.3" + } + }, + "Microsoft.Testing.Extensions.TrxReport": { + "type": "Direct", + "requested": "[2.2.3, )", + "resolved": "2.2.3", + "contentHash": "9Hot3ty5ZVWHrW40k2NPfD0dCaPwIxj7j7VjujNYwpYkYw9AdbejPHjGNkL/gvUWorauJf5IkeDoUeIbS7LuUg==", + "dependencies": { + "Microsoft.Testing.Extensions.TrxReport.Abstractions": "2.2.3", + "Microsoft.Testing.Platform": "2.2.3" + } + }, + "Microsoft.VisualStudio.Threading.Analyzers": { + "type": "Direct", + "requested": "[18.7.23, )", + "resolved": "18.7.23", + "contentHash": "dyj6z8m+LFjpeH69hdnciCcRCmD0YgMlvuQEywiEshjqj/blgQ+PuWX8Vd5gkWQFoBN4e+TFxx6DvGNSv/gIKg==" + }, + "MSTest.Analyzers": { + "type": "Direct", + "requested": "[4.2.3, )", + "resolved": "4.2.3", + "contentHash": "dxOZt8/LWuiox7rugInJoIa5Mmu3pBmXdfaoZOx/mxx8+sUFFpjBXPlWXQXGeWzpkVPNC3x1Jf7rt2h2Zjyvvg==" + }, + "MSTest.TestAdapter": { + "type": "Direct", + "requested": "[4.2.3, )", + "resolved": "4.2.3", + "contentHash": "oVV/luk0bBghnVvLaw8MwFlD7It0Cx9P2nKobeqIafmTQqFFWY69Wo801dxjeNaLzO/o9WQ84WSK84gXJuubhg==", + "dependencies": { + "MSTest.TestFramework": "4.2.3", + "Microsoft.Testing.Extensions.VSTestBridge": "2.2.3", + "Microsoft.Testing.Platform.MSBuild": "2.2.3" + } + }, + "MSTest.TestFramework": { + "type": "Direct", + "requested": "[4.2.3, )", + "resolved": "4.2.3", + "contentHash": "9zzij59YLh+tf+FRLNqhzHjmdspR91bol+jdQxLlxxTGMAML6LDbvuyXKMGdcrE84+QpKkk6KjTVBC5PBGrDeA==", + "dependencies": { + "MSTest.Analyzers": "4.2.3" + } + }, + "Polyfill": { + "type": "Direct", + "requested": "[10.11.2, )", + "resolved": "10.11.2", + "contentHash": "UpCgr4Fp/wUpmUCX+Lgr5k6OIWpayaze1dYGfnTLqfga9HtnE7qqxyKFf8MCNUWZ7XO+sG7mE5BY9N63Kyu/jQ==" + }, + "BouncyCastle.Cryptography": { + "type": "Transitive", + "resolved": "2.6.2", + "contentHash": "7oWOcvnntmMKNzDLsdxAYqApt+AjpRpP2CShjMfIa3umZ42UQMvH0tl1qAliYPNYO6vTdcGMqnRrCPmsfzTI1w==" + }, + "Microsoft.Bcl.Cryptography": { + "type": "Transitive", + "resolved": "10.0.2", + "contentHash": "LG9Yll3B5aNpxv0+D47g6LiOiKBIlodhcHdQwcYzo8VeexFLGqx5ymetmA2aBRyo9cCcWsQWrFsdbsr8LvmWDw==" + }, + "Microsoft.CodeCoverage": { + "type": "Transitive", + "resolved": "18.7.0", + "contentHash": "+wFfx9s7D9wegM0RziXMj2kvYDT4qcqXXtyjiQwSZOGQ2wwcOAJQcD6eQXk02jt0MvRNawtp8TJxTrV+wD8X1g==" + }, + "Microsoft.Data.SqlClient.Extensions.Abstractions": { + "type": "Transitive", + "resolved": "7.0.2", + "contentHash": "Zx7z61fG2Nc6LdDn4jA7b5Aj1ABrljkOPRE31RvVUdjF6IgbG60leDUhMCafsnWFgaheiK3iqpLe+kbf5Z5L8Q==", + "dependencies": { + "Microsoft.Data.SqlClient.Internal.Logging": "[7.0.2, 8.0.0)" + } + }, + "Microsoft.Data.SqlClient.Internal.Logging": { + "type": "Transitive", + "resolved": "7.0.2", + "contentHash": "iqgYBbGSVy/DIYWjzmQOa964Kn4rs4wW5vg2IamqVK0fQqfTiILVR5hMK27XWqoyONtAZs+VxH354cPJBtSnbg==" + }, + "Microsoft.Data.SqlClient.SNI.runtime": { + "type": "Transitive", + "resolved": "6.0.2", + "contentHash": "f+pRODTWX7Y67jXO3T5S2dIPZ9qMJNySjlZT/TKmWVNWe19N8jcWmHaqHnnchaq3gxEKv1SWVY5EFzOD06l41w==" + }, + "Microsoft.Diagnostics.NETCore.Client": { + "type": "Transitive", + "resolved": "0.2.607501", + "contentHash": "17Yxzao41A1oZZ5lCCAnnXOy9up5i/GVEGazBjJAUZ4UISsNAotUt6h7zvCDgfKIC46CD7jszgLzLZoscSIJQA==", + "dependencies": { + "Microsoft.Extensions.Logging.Abstractions": "6.0.4" + } + }, + "Microsoft.DiaSymReader": { + "type": "Transitive", + "resolved": "2.2.3", + "contentHash": "bhwzJfzyiJM0nXJyNB7Y9OfsEXyxLdDBHG99soIp5JjnPydwkOaBdRCtRtWgQh3noSLi2cSIZ/wpbHNNE9knxQ==" + }, + "Microsoft.Extensions.Caching.Memory": { + "type": "Transitive", + "resolved": "9.0.13", + "contentHash": "OdQmN8LYcUEu20Fxii9mk68nHJGL+JPXF3w0+hxenf0oDDdDBA+ZV/S92FmIgAWAElowIiFA/g0x+8YB1g80Hg==", + "dependencies": { + "Microsoft.Extensions.Caching.Abstractions": "9.0.13", + "Microsoft.Extensions.DependencyInjection.Abstractions": "9.0.13", + "Microsoft.Extensions.Logging.Abstractions": "9.0.13", + "Microsoft.Extensions.Options": "9.0.13", + "Microsoft.Extensions.Primitives": "9.0.13" + } + }, + "Microsoft.Extensions.DependencyInjection.Abstractions": { + "type": "Transitive", + "resolved": "10.0.9", + "contentHash": "g41l/30G3K4B/d/L8kjux0+30e27c8D0FVQ/PFCpbekgfDpj9mnDhieP67EqXWvl1EWNeZh2rpR4F5B/jcDOHA==" + }, + "Microsoft.Extensions.DependencyModel": { + "type": "Transitive", + "resolved": "8.0.2", + "contentHash": "mUBDZZRgZrSyFOsJ2qJJ9fXfqd/kXJwf3AiDoqLD9m6TjY5OO/vLNOb9fb4juC0487eq4hcGN/M2Rh/CKS7QYw==" + }, + "Microsoft.Extensions.Primitives": { + "type": "Transitive", + "resolved": "10.0.9", + "contentHash": "fmEbAUFsaIKirgLt/lYhuFRBwhcSJN31jjHgCdbQxJiWOum6EdLjkbgGuukSP9z/a+9LibaxII/kF+GwOXgC4g==" + }, + "Microsoft.IdentityModel.Abstractions": { + "type": "Transitive", + "resolved": "8.19.1", + "contentHash": "gFA8THIk23uNF/vMdOHnjIdXD1LyA2g12cHzMJ+Xag6WpgWLw6E/6uCXxvA0gp9d2yAvkRt3xzFzMUiO/hofnQ==" + }, + "Microsoft.IdentityModel.Logging": { + "type": "Transitive", + "resolved": "8.19.1", + "contentHash": "H+sMrMpdbWnwkQnpb/ESkQovtOgdefmj0ecGCcP40mDKzE5i4dUYkH6599M9mWYFNGNJnTp92l/9wLubYXWimw==", + "dependencies": { + "Microsoft.IdentityModel.Abstractions": "8.19.1" + } + }, + "Microsoft.IdentityModel.Protocols": { + "type": "Transitive", + "resolved": "8.16.0", + "contentHash": "UFrU7d46UTsPQTa2HIEIpB9H1uJe1BW9FLw5uhEJ2ZuKdur8bcUA/bO5caq5dlBt5gNJeRIB3QQXYNs5fCQCZA==", + "dependencies": { + "Microsoft.IdentityModel.Tokens": "8.16.0" + } + }, + "Microsoft.IdentityModel.Protocols.OpenIdConnect": { + "type": "Transitive", + "resolved": "8.16.0", + "contentHash": "h4yVXyJsEBBX5lg2G5ftMsi5JzcNEGAzrNphA6DQ6eOd8P0s+cDCOyPwVTYLePZvJL5unbPvYIvzrbTXzFjXnQ==", + "dependencies": { + "Microsoft.IdentityModel.Protocols": "8.16.0", + "System.IdentityModel.Tokens.Jwt": "8.16.0" + } + }, + "Microsoft.IdentityModel.Tokens": { + "type": "Transitive", + "resolved": "8.19.1", + "contentHash": "KDiuSLXud2AFVNAOottd8ztVysfPeHyr4r8gofU3/VKUXlI7oytzGTnPsNJ/B3nui17rgz8wAdWNJOtzPjkUxw==", + "dependencies": { + "Microsoft.Bcl.Cryptography": "10.0.2", + "Microsoft.Extensions.Logging.Abstractions": "8.0.0", + "Microsoft.IdentityModel.Logging": "8.19.1" + } + }, + "Microsoft.SqlServer.Server": { + "type": "Transitive", + "resolved": "1.0.0", + "contentHash": "N4KeF3cpcm1PUHym1RmakkzfkEv3GRMyofVv40uXsQhCQeglr2OHNcUk2WOG51AKpGO8ynGpo9M/kFXSzghwug==" + }, + "Microsoft.Testing.Extensions.Telemetry": { + "type": "Transitive", + "resolved": "2.2.3", + "contentHash": "mLdW+JOR3kXYGTdgR/qc/UZBA0r+eCR2k6bUxTcuDj5w9WdIQ7Lol5MBUU7YOSGd9bs9bvhSYWAptgz0YtQqCA==", + "dependencies": { + "Microsoft.ApplicationInsights": "2.23.0", + "Microsoft.Testing.Platform": "2.2.3" + } + }, + "Microsoft.Testing.Extensions.TrxReport.Abstractions": { + "type": "Transitive", + "resolved": "2.2.3", + "contentHash": "hntvxJEkmUAx6C2xXc/PO38DqEQl4rimzOgSvTR1hAMruMid7R4RcXOrzzF33J66gKaN7jRaQ0TMW/nNfaV9jw==", + "dependencies": { + "Microsoft.Testing.Platform": "2.2.3" + } + }, + "Microsoft.Testing.Extensions.VSTestBridge": { + "type": "Transitive", + "resolved": "2.2.3", + "contentHash": "7WlJISO8QKUK+d+WhgnANwy4ACwUrvICnviY/mthPwjZ2gVeDaSUAeBnMy2cxfzZgm8VATtGUDbYzUxsgV2CyQ==", + "dependencies": { + "Microsoft.TestPlatform.ObjectModel": "18.3.0", + "Microsoft.Testing.Extensions.Telemetry": "2.2.3", + "Microsoft.Testing.Extensions.TrxReport.Abstractions": "2.2.3", + "Microsoft.Testing.Platform": "2.2.3" + } + }, + "Microsoft.Testing.Platform.MSBuild": { + "type": "Transitive", + "resolved": "2.2.3", + "contentHash": "Q22jJYJLx4srTinsAuoCskqmzjrBJC8YeGJMHHIcrf1dQeHoEZ7wsqDzTlENkMoke2qfufF7U+9u58nlZunH/Q==", + "dependencies": { + "Microsoft.Testing.Platform": "2.2.3" + } + }, + "Microsoft.TestPlatform.ObjectModel": { + "type": "Transitive", + "resolved": "18.7.0", + "contentHash": "6rmgU4q3/WOpOPcncI0YW0Q/QpcQtwR2TTEXDR5+4TfSimPBAk6Z/BgKLeGgp1SOun0ROVUCCafXhRLwsHaPpA==" + }, + "Microsoft.TestPlatform.TestHost": { + "type": "Transitive", + "resolved": "18.7.0", + "contentHash": "kYwfmebCs8992zaxEDkvG7S+YEouTeKfYVKUFEkwh1W2dIoOaevBt80XSKVXCUFEhusjOIm1sFfHBnoJgygrRA==", + "dependencies": { + "Microsoft.TestPlatform.ObjectModel": "18.7.0", + "Newtonsoft.Json": "13.0.3" + } + }, + "MimeKit": { + "type": "Transitive", + "resolved": "4.17.0", + "contentHash": "h/KXsCreJf8RpR/PSAtlbvYtZFndp9N8Wn1Bs248AL7ITyhn+AiIY5bLTvt60tbN2mu6g8KadtBNWygTji2lqg==", + "dependencies": { + "BouncyCastle.Cryptography": "2.6.2", + "System.Security.Cryptography.Pkcs": "10.0.0" + } + }, + "Polly.Core": { + "type": "Transitive", + "resolved": "8.7.0", + "contentHash": "BS2t+nsBer16PIebCEPNBK5fgMisADQyRCd7K+BgkMWpFmSaiYE+rVVNpFhGRqUkJmNSLmw0uCNzHHWfgml28Q==" + }, + "System.Diagnostics.EventLog": { + "type": "Transitive", + "resolved": "10.0.9", + "contentHash": "s2PcxHK4IYQ6gmD3VSBkym9tWGkFisKjcjWBdl7a+n4Yy66ae4beJ1ZdjDp060SSll4W3Rt4H2LW87dWckv+QQ==" + }, + "System.IdentityModel.Tokens.Jwt": { + "type": "Transitive", + "resolved": "8.16.0", + "contentHash": "rrs2u7DRMXQG2yh0oVyF/vLwosfRv20Ld2iEpYcKwQWXHjfV+gFXNQsQ9p008kR9Ou4pxBs68Q6/9zC8Gi1wjg==", + "dependencies": { + "Microsoft.IdentityModel.JsonWebTokens": "8.16.0", + "Microsoft.IdentityModel.Tokens": "8.16.0" + } + }, + "System.IO.FileSystem.AccessControl": { + "type": "Transitive", + "resolved": "4.7.0", + "contentHash": "vMToiarpU81LR1/KZtnT7VDPvqAZfw9oOS5nY6pPP78nGYz3COLsQH3OfzbR+SjTgltd31R6KmKklz/zDpTmzw==" + }, + "System.Security.Cryptography.Pkcs": { + "type": "Transitive", + "resolved": "10.0.0", + "contentHash": "UPWqLSygJlFerRi9XNIuM0a1VC8gHUIufyP24xQ0sc+XimqUAEcjpOz9DhKpyDjH+5B/wO3RpC0KpkEeDj/ddg==" + }, + "System.Security.Cryptography.ProtectedData": { + "type": "Transitive", + "resolved": "10.0.9", + "contentHash": "vXp+vBQbTkMrDzUiPCxo1KvzfaAvZWuSRS2P7e5orhtV7ZuePKAB3Cqc9dH2YHUavRJyA2uWd2yrL6hl2VoJ5g==" + }, + "ark.tools.applicationinsights": { + "type": "Project", + "dependencies": { + "Microsoft.ApplicationInsights": "[2.23.0, )", + "Microsoft.ApplicationInsights.SnapshotCollector": "[1.4.6, )", + "Microsoft.ApplicationInsights.WindowsServer.TelemetryChannel": "[2.23.0, )", + "Microsoft.Data.SqlClient": "[7.0.2, )", + "Microsoft.Extensions.Options": "[10.0.9, )" + } + }, + "ark.tools.core": { + "type": "Project", + "dependencies": { + "NodaTime": "[3.3.2, )" + } + }, + "ark.tools.ftpclient.core": { + "type": "Project", + "dependencies": { + "Ark.Tools.Core": "[1.0.0, )", + "Ark.Tools.NLog": "[1.0.0, )", + "Microsoft.Extensions.ObjectPool": "[10.0.9, )", + "Polly": "[8.7.0, )" + } + }, + "ark.tools.ftpclient.fluentftp": { + "type": "Project", + "dependencies": { + "Ark.Tools.FtpClient.Core": "[1.0.0, )", + "Ark.Tools.NLog": "[1.0.0, )", + "FluentFTP": "[54.2.0, )" + } + }, + "ark.tools.nlog": { + "type": "Project", + "dependencies": { + "Ark.Tools.ApplicationInsights": "[1.0.0, )", + "Ark.Tools.Core": "[1.0.0, )", + "Ark.Tools.SystemTextJson": "[1.0.0, )", + "Ben.Demystifier": "[0.4.1, )", + "Microsoft.ApplicationInsights.NLogTarget": "[2.23.0, )", + "Microsoft.Data.SqlClient": "[7.0.2, )", + "NLog": "[6.1.3, )", + "NLog.Database": "[6.0.3, )", + "NLog.DiagnosticSource": "[6.0.3, )", + "NLog.MailKit": "[6.1.4, )", + "Newtonsoft.Json": "[13.0.4, )", + "Slack.Webhooks": "[1.1.6, )" + } + }, + "ark.tools.nodatime": { + "type": "Project", + "dependencies": { + "Ark.Tools.Core": "[1.0.0, )" + } + }, + "ark.tools.nodatime.systemtextjson": { + "type": "Project", + "dependencies": { + "Ark.Tools.Nodatime": "[1.0.0, )", + "NodaTime.Serialization.SystemTextJson": "[1.4.0, )" + } + }, + "ark.tools.systemtextjson": { + "type": "Project", + "dependencies": { + "Ark.Tools.Core": "[1.0.0, )", + "Ark.Tools.Nodatime.SystemTextJson": "[1.0.0, )", + "Macross.Json.Extensions": "[3.0.0, )" + } + }, + "Ben.Demystifier": { + "type": "CentralTransitive", + "requested": "[0.4.1, )", + "resolved": "0.4.1", + "contentHash": "axFeEMfmEORy3ipAzOXG/lE+KcNptRbei3F0C4kQCdeiQtW+qJW90K5iIovITGrdLt8AjhNCwk5qLSX9/rFpoA==" + }, + "FluentFTP": { + "type": "CentralTransitive", + "requested": "[54.2.0, )", + "resolved": "54.2.0", + "contentHash": "n+OyaqU80gnOltXo2xBPZklAqbtxJAfl2GfblOIe5OCAk4jr/3zYW9Jf1fG6eR/USZq4AKWQcUMmKE9neSk8og==" + }, + "Macross.Json.Extensions": { + "type": "CentralTransitive", + "requested": "[3.0.0, )", + "resolved": "3.0.0", + "contentHash": "AkNshs6dopj8FXsmkkJxvLivN2SyDJQDbjcds5lo9+Y6L4zpcoXdmzXQ3VVN+AIWQr0CTD5A7vkuHGAr2aypZg==" + }, + "MailKit": { + "type": "CentralTransitive", + "requested": "[4.17.0, )", + "resolved": "4.17.0", + "contentHash": "1nUAVLxM9fhT/78we6/AGsCesnpn5dRNLLeRqOfr52Wnk87pzVwo5YMTMyqnmoXrYc7piGhmayiMA/OgDluIjg==", + "dependencies": { + "MimeKit": "4.17.0" + } + }, + "Microsoft.ApplicationInsights": { + "type": "CentralTransitive", + "requested": "[2.23.0, )", + "resolved": "2.23.0", + "contentHash": "nWArUZTdU7iqZLycLKWe0TDms48KKGE6pONH2terYNa8REXiqixrMOkf1sk5DHGMaUTqONU2YkS4SAXBhLStgw==" + }, + "Microsoft.ApplicationInsights.NLogTarget": { + "type": "CentralTransitive", + "requested": "[2.23.0, )", + "resolved": "2.23.0", + "contentHash": "rjUqSw8SLCB7timNT/Brz92mumWv4audq2diIj67xKibdcK93kh+QaMj/G+OiNrsqfi2K+0CxmIZCALTi19Obg==", + "dependencies": { + "Microsoft.ApplicationInsights": "2.23.0", + "NLog": "4.5.11" + } + }, + "Microsoft.ApplicationInsights.SnapshotCollector": { + "type": "CentralTransitive", + "requested": "[1.4.6, )", + "resolved": "1.4.6", + "contentHash": "UGXpUjW3YFSFq+u4CXwJrU3Rf7Hc3dMrMVTBJ8E3LB0eV2MF8lOHnXc+kHEmLKO4gxHhvulaVIld7U5aDzLZ8A==", + "dependencies": { + "Microsoft.ApplicationInsights": "2.15.0", + "Microsoft.Extensions.Options": "2.1.1", + "System.IO.FileSystem.AccessControl": "4.7.0" + } + }, + "Microsoft.ApplicationInsights.WindowsServer.TelemetryChannel": { + "type": "CentralTransitive", + "requested": "[2.23.0, )", + "resolved": "2.23.0", + "contentHash": "798Dudr4tkujslk1w+XcXOcCErmVsk+nhp+QCHLa3lcgi25vkAxBmzPUeQlRJVCNL/1f4x/YF+vQZ8RSuTXWCw==", + "dependencies": { + "Microsoft.ApplicationInsights": "2.23.0", + "System.IO.FileSystem.AccessControl": "4.7.0" + } + }, + "Microsoft.Data.SqlClient": { + "type": "CentralTransitive", + "requested": "[7.0.2, )", + "resolved": "7.0.2", + "contentHash": "zwv76lANFQQI6Gmp6ntkzMWIWVqm8Wf4Mz00AeGCk1n8HCi5afi6bNynSe18uI0xeL0n6J+Myjk9AiIsL5oSqw==", + "dependencies": { + "Microsoft.Bcl.Cryptography": "9.0.13", + "Microsoft.Data.SqlClient.Extensions.Abstractions": "[7.0.2, 8.0.0)", + "Microsoft.Data.SqlClient.Internal.Logging": "[7.0.2, 8.0.0)", + "Microsoft.Data.SqlClient.SNI.runtime": "[6.0.2, 7.0.0)", + "Microsoft.Extensions.Caching.Memory": "9.0.13", + "Microsoft.IdentityModel.JsonWebTokens": "8.16.0", + "Microsoft.IdentityModel.Protocols.OpenIdConnect": "8.16.0", + "Microsoft.SqlServer.Server": "[1.0.0, 2.0.0)", + "System.Configuration.ConfigurationManager": "9.0.13", + "System.Security.Cryptography.Pkcs": "9.0.13" + } + }, + "Microsoft.Extensions.Caching.Abstractions": { + "type": "CentralTransitive", + "requested": "[10.0.9, )", + "resolved": "10.0.9", + "contentHash": "5fGxcw2vuYp8s0wio9H1ECiuk4iKSdTIlNuigdLIrkhg+5XAwgFVDB/5Ots3pfN/QhABLYXutA79JFtnUKDSHA==", + "dependencies": { + "Microsoft.Extensions.Primitives": "10.0.9" + } + }, + "Microsoft.Extensions.Logging.Abstractions": { + "type": "CentralTransitive", + "requested": "[10.0.9, )", + "resolved": "10.0.9", + "contentHash": "9S/DFt4cohlMPpzIxjG6kk0L8MuN2vDm9pbMCulxtJzzk82oJHVLBd8vuQxaPskaYQwKqmFmbannf5eoChgjYg==", + "dependencies": { + "Microsoft.Extensions.DependencyInjection.Abstractions": "10.0.9" + } + }, + "Microsoft.Extensions.ObjectPool": { + "type": "CentralTransitive", + "requested": "[10.0.9, )", + "resolved": "10.0.9", + "contentHash": "KZENCkfqO7Ciax6goUWQHDSxKH+x763hkBWMz9KpE87EyKW+EKEas9EFe9i1KgtQShG8KwKxaeJ5gd9sj6TuTQ==" + }, + "Microsoft.Extensions.Options": { + "type": "CentralTransitive", + "requested": "[10.0.9, )", + "resolved": "10.0.9", + "contentHash": "hyNdX4c2UwkRkzb9byw0H2DQkRzwBM3mzY2sCM9egwzTyg8dvQJmp5noQHGEaaCORQrNK3DD2gREBsc2DlXS4A==", + "dependencies": { + "Microsoft.Extensions.DependencyInjection.Abstractions": "10.0.9", + "Microsoft.Extensions.Primitives": "10.0.9" + } + }, + "Microsoft.IdentityModel.JsonWebTokens": { + "type": "CentralTransitive", + "requested": "[8.19.1, )", + "resolved": "8.19.1", + "contentHash": "6eeY+y2QFyjj3XnCz/8gJdoP5smYHTS9ow1bw2nsZzDIPjPhBZlackYTIduSMipVpxnoT/B62LkrXX2jPggOXg==", + "dependencies": { + "Microsoft.IdentityModel.Tokens": "8.19.1" + } + }, + "Microsoft.Testing.Platform": { + "type": "CentralTransitive", + "requested": "[2.2.3, )", + "resolved": "2.2.3", + "contentHash": "LhM1/Qoi8Ams5QcD4r3f09CSOono9iQr3NEJQItFtyzWB55nWTgEOsVqXqMWWWIwk3nkPqc+XfnlJmp8xUI5fg==" + }, + "Newtonsoft.Json": { + "type": "CentralTransitive", + "requested": "[13.0.4, )", + "resolved": "13.0.4", + "contentHash": "pdgNNMai3zv51W5aq268sujXUyx7SNdE2bj1wZcWjAQrKMFZV260lbqYop1d2GM67JI1huLRwxo9ZqnfF/lC6A==" + }, + "NLog": { + "type": "CentralTransitive", + "requested": "[6.1.3, )", + "resolved": "6.1.3", + "contentHash": "w4JFIUN3cjQobFOc6wuXtHTyMNaWfdAxOhMvCR0vHBNpl2kKSjNYYxzx1KsTtePufQeokFMEx9UbWTAqlICKjA==" + }, + "NLog.Database": { + "type": "CentralTransitive", + "requested": "[6.0.3, )", + "resolved": "6.0.3", + "contentHash": "uMzSeSQbjHjAG+zF6E7DudXyLhyxNObBtHkyQE+IoLudqadKR1xojGsk9z02fWNlcyvCb3odkPh7Cf71eBi0/Q==", + "dependencies": { + "NLog": "6.0.3" + } + }, + "NLog.DiagnosticSource": { + "type": "CentralTransitive", + "requested": "[6.0.3, )", + "resolved": "6.0.3", + "contentHash": "C4bO3cmzrbk965manOfikSr0f+q87/sNvvpeetVLHriEYU8+y2kVDwCvuFzlYMG1bZPtOFmHhiDdQ8EBpre1MA==", + "dependencies": { + "NLog": "6.0.3" + } + }, + "NLog.MailKit": { + "type": "CentralTransitive", + "requested": "[6.1.4, )", + "resolved": "6.1.4", + "contentHash": "lFsI61wqxD+U110oFn6dUTXbwR8IzGF/mgnRWzAKss4lGZ+XlIlBJJfdpUSPJWzYOSRsHh8pUdJOFMl9SLp2Fg==", + "dependencies": { + "MailKit": "4.16.0", + "NLog": "6.1.3" + } + }, + "NodaTime": { + "type": "CentralTransitive", + "requested": "[3.3.2, )", + "resolved": "3.3.2", + "contentHash": "8hI5b1ENTKQCaPyU6YHpYiMwj5aJKZ4Mnv0bLbhk65Dd44gQsXenUohMzyiIphANa8LdW6vcOvpY/l1urvx4dw==" + }, + "NodaTime.Serialization.SystemTextJson": { + "type": "CentralTransitive", + "requested": "[1.4.0, )", + "resolved": "1.4.0", + "contentHash": "jGe0WLNAoRb3efOgsa5iecmMuzP+LNdtw2Rs4N9+2pS4YN+kg8e9Z48Jk47WWebcpqF81YdWT2KNInbXZXTS7w==", + "dependencies": { + "NodaTime": "[3.0.0, 4.0.0)" + } + }, + "Polly": { + "type": "CentralTransitive", + "requested": "[8.7.0, )", + "resolved": "8.7.0", + "contentHash": "0qR4f0OR8FeCAfLWcfwzAM7w6EmpUgwa22PgxKjcL25dEAto7sKpQQXbtxp38vVvK+V3RFkh/TeI7g/iUdoYIQ==", + "dependencies": { + "Polly.Core": "8.7.0" + } + }, + "Slack.Webhooks": { + "type": "CentralTransitive", + "requested": "[1.1.6, )", + "resolved": "1.1.6", + "contentHash": "Xl16kn26BwU71QEv9kn+TVpnIr3QiwJJL/JyQkUdIgAN6l0fubMtK4eU5OR3g/BcPlXSKWWScH3gNrbagK/Dww==", + "dependencies": { + "Newtonsoft.Json": "13.0.4" + } + }, + "System.Configuration.ConfigurationManager": { + "type": "CentralTransitive", + "requested": "[10.0.9, )", + "resolved": "10.0.9", + "contentHash": "k7kYSTmNjwFYKdUju+rjYyigbcUBj1sElR3ayqszcatPXL9YJoBANJN48xctdXTopYf3ceFX2T4m1xvJRM+CGA==", + "dependencies": { + "System.Diagnostics.EventLog": "10.0.9", + "System.Security.Cryptography.ProtectedData": "10.0.9" + } + } + } + } +} \ No newline at end of file