Remote write

This commit is contained in:
2023-07-26 21:21:55 +02:00
parent 144745a558
commit d26401948a
25 changed files with 293 additions and 34 deletions

View File

@@ -5,6 +5,7 @@
</ItemGroup> </ItemGroup>
<ItemGroup> <ItemGroup>
<PackageReference Include="ByteSize" Version="2.1.1" />
<PackageReference Include="Microsoft.Extensions.Logging.Abstractions" Version="7.0.1" /> <PackageReference Include="Microsoft.Extensions.Logging.Abstractions" Version="7.0.1" />
</ItemGroup> </ItemGroup>

View File

@@ -21,7 +21,7 @@ public class App : Application
var configuration = Startup.CreateConfiguration(); var configuration = Startup.CreateConfiguration();
DI.ServiceProvider = DependencyInjection DI.ServiceProvider = DependencyInjection
.RegisterDefaultServices(configuration: configuration) .RegisterDefaultServices(configuration: configuration)
.AddRemoteServices() .AddServerCoreServices()
.AddFrequencyNavigation() .AddFrequencyNavigation()
.AddCommandPalette() .AddCommandPalette()
.AddSearch() .AddSearch()

View File

@@ -36,6 +36,7 @@
<PackageReference Include="Serilog.Extensions.Hosting" Version="7.0.0" /> <PackageReference Include="Serilog.Extensions.Hosting" Version="7.0.0" />
<PackageReference Include="Serilog.Extensions.Logging" Version="7.0.0" /> <PackageReference Include="Serilog.Extensions.Logging" Version="7.0.0" />
<PackageReference Include="Serilog.Settings.Configuration" Version="7.0.0" /> <PackageReference Include="Serilog.Settings.Configuration" Version="7.0.0" />
<PackageReference Include="Serilog.Sinks.Console" Version="4.1.0" />
<PackageReference Include="Serilog.Sinks.File" Version="5.0.0" /> <PackageReference Include="Serilog.Sinks.File" Version="5.0.0" />
</ItemGroup> </ItemGroup>
<ItemGroup> <ItemGroup>

View File

@@ -4,7 +4,9 @@
"Default": "Verbose", "Default": "Verbose",
"Override": { "Override": {
"Microsoft": "Information", "Microsoft": "Information",
"System": "Warning" "System": "Warning",
"Microsoft.AspNetCore.SignalR": "Verbose",
"Microsoft.AspNetCore.Http.Connections": "Verbose"
} }
} }
} }

View File

@@ -1,12 +1,34 @@
using FileTime.Core.ContentAccess; using FileTime.Core.ContentAccess;
using FileTime.Core.Models; using FileTime.Core.Models;
using FileTime.Providers.LocalAdmin;
using Microsoft.Extensions.Logging;
namespace FileTime.Providers.Local; namespace FileTime.Providers.Local;
public class LocalContentWriterFactory : IContentWriterFactory<ILocalContentProvider> public class LocalContentWriterFactory : IContentWriterFactory<ILocalContentProvider>
{ {
public Task<IContentWriter> CreateContentWriterAsync(IElement element) private readonly IAdminContentAccessorFactory _adminContentAccessorFactory;
private readonly ILogger<LocalContentWriterFactory> _logger;
public LocalContentWriterFactory(
IAdminContentAccessorFactory adminContentAccessorFactory,
ILogger<LocalContentWriterFactory> logger)
{ {
return Task.FromResult((IContentWriter) new LocalContentWriter(File.OpenWrite(element.NativePath!.Path))); _adminContentAccessorFactory = adminContentAccessorFactory;
_logger = logger;
}
public async Task<IContentWriter> CreateContentWriterAsync(IElement element)
{
try
{
return new LocalContentWriter(File.OpenWrite(element.NativePath!.Path));
}
catch (UnauthorizedAccessException e)
{
_logger.LogDebug(e, "Failed to write to element with path {Path}", element.NativePath);
var adminContentWriter = await _adminContentAccessorFactory.CreateContentWriterAsync(element.NativePath!);
return adminContentWriter;
}
} }
} }

View File

@@ -1,4 +1,5 @@
using FileTime.Providers.Remote; using FileTime.Core.Models;
using FileTime.Providers.Remote;
namespace FileTime.Providers.LocalAdmin; namespace FileTime.Providers.LocalAdmin;
@@ -8,4 +9,5 @@ public interface IAdminContentAccessorFactory
Task<IRemoteItemCreator> CreateAdminItemCreatorAsync(); Task<IRemoteItemCreator> CreateAdminItemCreatorAsync();
Task<IRemoteItemDeleter> CreateAdminItemDeleterAsync(); Task<IRemoteItemDeleter> CreateAdminItemDeleterAsync();
Task<IRemoteItemMover> CreateAdminItemMoverAsync(); Task<IRemoteItemMover> CreateAdminItemMoverAsync();
Task<IRemoteContentWriter> CreateContentWriterAsync(NativePath nativePath);
} }

View File

@@ -1,7 +1,9 @@
using System.Diagnostics; using System.Diagnostics;
using FileTime.Core.Models;
using FileTime.Providers.Remote; using FileTime.Providers.Remote;
using FileTime.Server.Common; using FileTime.Server.Common;
using InitableService; using InitableService;
using Microsoft.Extensions.DependencyInjection;
namespace FileTime.Providers.LocalAdmin; namespace FileTime.Providers.LocalAdmin;
@@ -13,7 +15,7 @@ public class AdminContentAccessorFactory : IAdminContentAccessorFactory
public AdminContentAccessorFactory( public AdminContentAccessorFactory(
IAdminElevationManager adminElevationManager, IAdminElevationManager adminElevationManager,
IServiceProvider serviceProvider IServiceProvider serviceProvider
) )
{ {
_adminElevationManager = adminElevationManager; _adminElevationManager = adminElevationManager;
_serviceProvider = serviceProvider; _serviceProvider = serviceProvider;
@@ -27,19 +29,33 @@ public class AdminContentAccessorFactory : IAdminContentAccessorFactory
public async Task<IRemoteItemDeleter> CreateAdminItemDeleterAsync() public async Task<IRemoteItemDeleter> CreateAdminItemDeleterAsync()
=> await CreateHelperAsync<IRemoteItemDeleter>(); => await CreateHelperAsync<IRemoteItemDeleter>();
public async Task<IRemoteItemMover> CreateAdminItemMoverAsync() public async Task<IRemoteItemMover> CreateAdminItemMoverAsync()
=> await CreateHelperAsync<IRemoteItemMover>(); => await CreateHelperAsync<IRemoteItemMover>();
public async Task<IRemoteContentWriter> CreateContentWriterAsync(NativePath nativePath)
{
await _adminElevationManager.CreateAdminInstanceIfNecessaryAsync();
var connection = await _adminElevationManager.CreateConnectionAsync();
var contentWriter = _serviceProvider.GetInitableResolver(
connection,
_adminElevationManager.ProviderName,
nativePath,
Guid.NewGuid()
).GetRequiredService<IRemoteContentWriter>();
return contentWriter;
}
private async Task<T> CreateHelperAsync<T>() private async Task<T> CreateHelperAsync<T>()
where T : class, IInitable<IRemoteConnection, string> where T : class, IInitable<IRemoteConnection, string>
{ {
await _adminElevationManager.CreateAdminInstanceIfNecessaryAsync(); await _adminElevationManager.CreateAdminInstanceIfNecessaryAsync();
var connection = await _adminElevationManager.CreateConnectionAsync(); var connection = await _adminElevationManager.CreateConnectionAsync();
Debug.Assert(connection != null); Debug.Assert(connection != null);
var helper = _serviceProvider.GetInitableResolver( var helper = _serviceProvider.GetInitableResolver(
connection, connection,
_adminElevationManager.ProviderName) _adminElevationManager.ProviderName)
.GetRequiredService<T>(); .GetRequiredService<T>();
return helper; return helper;

View File

@@ -89,8 +89,6 @@ public class AdminElevationManager : IAdminElevationManager, INotifyPropertyChan
process.Start(); process.Start();
_adminProcess = process; _adminProcess = process;
IsAdminInstanceRunning = true;
//TODO: timeout //TODO: timeout
while (!File.Exists(portFileName) || new FileInfo(portFileName).Length == 0) while (!File.Exists(portFileName) || new FileInfo(portFileName).Length == 0)
await Task.Delay(10); await Task.Delay(10);
@@ -109,6 +107,8 @@ public class AdminElevationManager : IAdminElevationManager, INotifyPropertyChan
} }
} }
IsAdminInstanceRunning = true;
var connectionInfo = new ConnectionInfo var connectionInfo = new ConnectionInfo
{ {
SignalRBaseUrl = $"http://localhost:{port}/RemoteHub" SignalRBaseUrl = $"http://localhost:{port}/RemoteHub"

View File

@@ -0,0 +1,12 @@
using FileTime.Core.ContentAccess;
using FileTime.Core.Models;
using FileTime.Server.Common;
using InitableService;
namespace FileTime.Providers.Remote;
public interface IRemoteContentWriter :
IContentWriter,
IInitable<IRemoteConnection, string, NativePath, Guid>
{
}

View File

@@ -0,0 +1,51 @@
using FileTime.Core.Models;
using FileTime.Server.Common;
namespace FileTime.Providers.Remote;
public class RemoteContentWriter : IRemoteContentWriter
{
private IRemoteConnection _remoteConnection = null!;
private string _remoteContentProviderId = null!;
private NativePath _nativePath = null!;
private string _transactionId = null!;
private bool _isRemoteWriterInitialized;
public void Init(
IRemoteConnection remoteConnection,
string remoteContentProviderId,
NativePath nativePath,
Guid transactionId)
{
_remoteConnection = remoteConnection;
_remoteContentProviderId = remoteContentProviderId;
_nativePath = nativePath;
_transactionId = transactionId.ToString();
}
public void Dispose()
{
if (!_isRemoteWriterInitialized) return;
_remoteConnection.CloseWriterAsync(_transactionId);
}
public int PreferredBufferSize => 10 * 1024;
public async Task WriteBytesAsync(byte[] data, int? index = null)
{
if (!_isRemoteWriterInitialized) await InitializeRemoteWriter(_nativePath);
await _remoteConnection.WriteBytesAsync(_transactionId, data, index);
}
public async Task FlushAsync()
{
if (!_isRemoteWriterInitialized) return;
await _remoteConnection.FlushWriterAsync(_transactionId);
}
private async Task InitializeRemoteWriter(NativePath nativePath)
{
_isRemoteWriterInitialized = true;
await _remoteConnection.InitializeRemoteWriter(_remoteContentProviderId, _transactionId, nativePath);
}
}

View File

@@ -11,6 +11,7 @@ public static class Startup
serviceCollection.TryAddTransient<IRemoteItemCreator, RemoteItemCreator>(); serviceCollection.TryAddTransient<IRemoteItemCreator, RemoteItemCreator>();
serviceCollection.TryAddTransient<IRemoteItemDeleter, RemoteItemDeleter>(); serviceCollection.TryAddTransient<IRemoteItemDeleter, RemoteItemDeleter>();
serviceCollection.TryAddTransient<IRemoteItemMover, RemoteItemMover>(); serviceCollection.TryAddTransient<IRemoteItemMover, RemoteItemMover>();
serviceCollection.TryAddTransient<IRemoteContentWriter, RemoteContentWriter>();
return serviceCollection; return serviceCollection;
} }
} }

View File

@@ -0,0 +1,11 @@
using Autofac;
using Microsoft.Extensions.Configuration;
namespace FileTime.Server.Common;
public record ConnectionHandlerParameters(
string[] Args,
IContainer RootContainer,
IConfigurationRoot ConfigurationRoot,
CancellationToken ApplicationExit
);

View File

@@ -0,0 +1,10 @@
using FileTime.Core.ContentAccess;
namespace FileTime.Server.Common.ContentAccess;
public interface IContentAccessManager
{
void AddContentWriter(string transactionId, IContentWriter contentWriter);
IContentWriter GetContentWriter(string transactionId);
void RemoveContentWriter(string transactionId);
}

View File

@@ -11,4 +11,9 @@
<ProjectReference Include="..\..\Core\FileTime.Core.Abstraction\FileTime.Core.Abstraction.csproj" /> <ProjectReference Include="..\..\Core\FileTime.Core.Abstraction\FileTime.Core.Abstraction.csproj" />
</ItemGroup> </ItemGroup>
<ItemGroup>
<PackageReference Include="Autofac" Version="6.4.0" />
<PackageReference Include="Microsoft.Extensions.Configuration.Abstractions" Version="7.0.0" />
</ItemGroup>
</Project> </Project>

View File

@@ -9,4 +9,8 @@ public interface IRemoteConnection
Task CreateElementAsync(string contentProviderId, FullName fullName); Task CreateElementAsync(string contentProviderId, FullName fullName);
Task DeleteItemAsync(string contentProviderId, FullName fullName); Task DeleteItemAsync(string contentProviderId, FullName fullName);
Task MoveItemAsync(string contentProviderId, FullName fullName, FullName newPath); Task MoveItemAsync(string contentProviderId, FullName fullName, FullName newPath);
Task InitializeRemoteWriter(string contentProviderId, string transactionId, NativePath nativePath);
Task WriteBytesAsync(string transactionId, byte[] data, int? index);
Task FlushWriterAsync(string transactionId);
Task CloseWriterAsync(string transactionId);
} }

View File

@@ -7,4 +7,8 @@ public interface ISignalRHub
Task CreateElementAsync(string contentProviderId, string fullName); Task CreateElementAsync(string contentProviderId, string fullName);
Task DeleteItemAsync(string contentProviderId, string fullName); Task DeleteItemAsync(string contentProviderId, string fullName);
Task MoveItemAsync(string contentProviderId, string fullName, string newPath); Task MoveItemAsync(string contentProviderId, string fullName, string newPath);
Task FlushWriterAsync(string transactionId);
Task InitializeRemoteWriter(string contentProviderId, string transactionId, string nativePath);
Task WriteBytesAsync(string transactionId, string data, int index);
Task CloseWriterAsync(string transactionId);
} }

View File

@@ -1,6 +1,9 @@
using FileTime.Core.Models; using System.Text;
using FileTime.Core.Models;
using InitableService; using InitableService;
using Microsoft.AspNetCore.SignalR.Client; using Microsoft.AspNetCore.SignalR.Client;
using Microsoft.Extensions.Logging;
using Serilog;
using TypedSignalR.Client; using TypedSignalR.Client;
namespace FileTime.Server.Common.Connections.SignalR; namespace FileTime.Server.Common.Connections.SignalR;
@@ -8,8 +11,8 @@ namespace FileTime.Server.Common.Connections.SignalR;
public class SignalRConnection : IRemoteConnection, IAsyncInitable<string> public class SignalRConnection : IRemoteConnection, IAsyncInitable<string>
{ {
private static readonly Dictionary<string, SignalRConnection> Connections = new(); private static readonly Dictionary<string, SignalRConnection> Connections = new();
private static readonly object ConnectionsLock = new(); private static readonly object ConnectionsLock = new();
private string _baseUrl = null!; private string _baseUrl = null!;
private HubConnection _connection = null!; private HubConnection _connection = null!;
private ISignalRHub _client = null!; private ISignalRHub _client = null!;
@@ -18,13 +21,15 @@ public class SignalRConnection : IRemoteConnection, IAsyncInitable<string>
{ {
_baseUrl = baseUrl; _baseUrl = baseUrl;
_connection = new HubConnectionBuilder() var connectionBuilder = new HubConnectionBuilder()
.WithUrl(_baseUrl) .ConfigureLogging(logging => { logging.AddSerilog(); })
.Build(); .WithUrl(_baseUrl);
_connection = connectionBuilder.Build();
await _connection.StartAsync(); await _connection.StartAsync();
_client = _connection.CreateHubProxy<ISignalRHub>(); _client = _connection.CreateHubProxy<ISignalRHub>();
} }
public static async Task<SignalRConnection> GetOrCreateForAsync(string baseUrl) public static async Task<SignalRConnection> GetOrCreateForAsync(string baseUrl)
{ {
SignalRConnection? connection; SignalRConnection? connection;
@@ -62,4 +67,16 @@ public class SignalRConnection : IRemoteConnection, IAsyncInitable<string>
public async Task MoveItemAsync(string contentProviderId, FullName fullName, FullName newPath) public async Task MoveItemAsync(string contentProviderId, FullName fullName, FullName newPath)
=> await _client.MoveItemAsync(contentProviderId, fullName.Path, newPath.Path); => await _client.MoveItemAsync(contentProviderId, fullName.Path, newPath.Path);
public async Task WriteBytesAsync(string transactionId, byte[] data, int? index)
=> await _client.WriteBytesAsync(transactionId, Encoding.UTF8.GetString(data), index ?? -1);
public async Task FlushWriterAsync(string transactionId)
=> await _client.FlushWriterAsync(transactionId);
public async Task InitializeRemoteWriter(string contentProviderId, string transactionId, NativePath nativePath)
=> await _client.InitializeRemoteWriter(contentProviderId, transactionId, nativePath.Path);
public async Task CloseWriterAsync(string transactionId)
=> await _client.CloseWriterAsync(transactionId);
} }

View File

@@ -0,0 +1,13 @@
using FileTime.Core.ContentAccess;
namespace FileTime.Server.Common.ContentAccess;
public class ContentAccessManager : IContentAccessManager
{
private readonly Dictionary<string, IContentWriter> _contentWriters = new();
public void AddContentWriter(string transactionId, IContentWriter contentWriter)
=> _contentWriters.Add(transactionId, contentWriter);
public IContentWriter GetContentWriter(string transactionId) => _contentWriters[transactionId];
public void RemoveContentWriter(string transactionId) => _contentWriters.Remove(transactionId);
}

View File

@@ -12,6 +12,7 @@
<ItemGroup> <ItemGroup>
<PackageReference Include="Microsoft.AspNetCore.SignalR.Client" Version="7.0.9" /> <PackageReference Include="Microsoft.AspNetCore.SignalR.Client" Version="7.0.9" />
<PackageReference Include="Serilog.Extensions.Logging" Version="7.0.0" />
<PackageReference Include="TypedSignalR.Client" Version="3.4.0"> <PackageReference Include="TypedSignalR.Client" Version="3.4.0">
<PrivateAssets>all</PrivateAssets> <PrivateAssets>all</PrivateAssets>
<IncludeAssets>runtime; build; native; contentfiles; analyzers; buildtransitive</IncludeAssets> <IncludeAssets>runtime; build; native; contentfiles; analyzers; buildtransitive</IncludeAssets>

View File

@@ -1,14 +1,17 @@
using FileTime.Server.Common.Connections.SignalR; using FileTime.Server.Common.Connections.SignalR;
using FileTime.Server.Common.ContentAccess;
using Microsoft.Extensions.DependencyInjection; using Microsoft.Extensions.DependencyInjection;
using Microsoft.Extensions.DependencyInjection.Extensions;
namespace FileTime.Server.Common; namespace FileTime.Server.Common;
public static class Startup public static class Startup
{ {
public static IServiceCollection AddRemoteServices(this IServiceCollection services) public static IServiceCollection AddServerCoreServices(this IServiceCollection services)
{ {
services.AddTransient<SignalRConnection>(); services.AddTransient<SignalRConnection>();
services.AddSingleton<IApplicationStopper, ApplicationStopper>(); services.TryAddSingleton<IApplicationStopper, ApplicationStopper>();
services.AddSingleton<IContentAccessManager, ContentAccessManager>();
return services; return services;
} }
} }

View File

@@ -1,7 +1,10 @@
using FileTime.Core.ContentAccess; using System.Text;
using FileTime.Core.ContentAccess;
using FileTime.Core.Models; using FileTime.Core.Models;
using FileTime.Core.Timeline;
using FileTime.Server.Common; using FileTime.Server.Common;
using FileTime.Server.Common.Connections.SignalR; using FileTime.Server.Common.Connections.SignalR;
using FileTime.Server.Common.ContentAccess;
using Microsoft.AspNetCore.SignalR; using Microsoft.AspNetCore.SignalR;
namespace FileTime.Server.Web; namespace FileTime.Server.Web;
@@ -11,17 +14,20 @@ public class ConnectionHub : Hub<ISignalRClient>, ISignalRHub
private readonly IContentProviderRegistry _contentProviderRegistry; private readonly IContentProviderRegistry _contentProviderRegistry;
private readonly IContentAccessorFactory _contentAccessorFactory; private readonly IContentAccessorFactory _contentAccessorFactory;
private readonly IApplicationStopper _applicationStopper; private readonly IApplicationStopper _applicationStopper;
private readonly IContentAccessManager _contentAccessManager;
public ConnectionHub( public ConnectionHub(
IContentProviderRegistry contentProviderRegistry, IContentProviderRegistry contentProviderRegistry,
IContentAccessorFactory contentAccessorFactory, IContentAccessorFactory contentAccessorFactory,
IApplicationStopper applicationStopper) IApplicationStopper applicationStopper,
IContentAccessManager contentAccessManager)
{ {
_contentProviderRegistry = contentProviderRegistry; _contentProviderRegistry = contentProviderRegistry;
_contentAccessorFactory = contentAccessorFactory; _contentAccessorFactory = contentAccessorFactory;
_applicationStopper = applicationStopper; _applicationStopper = applicationStopper;
_contentAccessManager = contentAccessManager;
} }
public Task Exit() public Task Exit()
{ {
_applicationStopper.Stop(); _applicationStopper.Stop();
@@ -56,4 +62,29 @@ public class ConnectionHub : Hub<ISignalRClient>, ISignalRHub
var itemDeleter = _contentAccessorFactory.GetItemMover(contentProvider); var itemDeleter = _contentAccessorFactory.GetItemMover(contentProvider);
await itemDeleter.RenameAsync(contentProvider, new FullName(fullName), new FullName(newPath)); await itemDeleter.RenameAsync(contentProvider, new FullName(fullName), new FullName(newPath));
} }
public async Task InitializeRemoteWriter(string contentProviderId, string transactionId, string nativePath)
{
var contentProvider = _contentProviderRegistry.ContentProviders.First(p => p.Name == contentProviderId);
var item = await contentProvider.GetItemByNativePathAsync(new NativePath(nativePath), PointInTime.Present);
if (item is not IElement element)
throw new FileNotFoundException("Item is not an element", nativePath);
var contentWriter = await _contentAccessorFactory.GetContentWriterFactory(contentProvider).CreateContentWriterAsync(element);
_contentAccessManager.AddContentWriter(transactionId, contentWriter);
}
public async Task WriteBytesAsync(string transactionId, string data, int index)
=> await _contentAccessManager.GetContentWriter(transactionId).WriteBytesAsync(Encoding.UTF8.GetBytes(data), index == -1 ? null : index);
public async Task FlushWriterAsync(string transactionId)
=> await _contentAccessManager.GetContentWriter(transactionId).FlushAsync();
public Task CloseWriterAsync(string transactionId)
{
_contentAccessManager.GetContentWriter(transactionId).Dispose();
_contentAccessManager.RemoveContentWriter(transactionId);
return Task.CompletedTask;
}
} }

View File

@@ -1,11 +1,13 @@
using System.Net; using System.Net;
using Autofac; using Autofac;
using Autofac.Extensions.DependencyInjection; using Autofac.Extensions.DependencyInjection;
using FileTime.Server.Common;
using Microsoft.AspNetCore.Builder; using Microsoft.AspNetCore.Builder;
using Microsoft.AspNetCore.Hosting; using Microsoft.AspNetCore.Hosting;
using Microsoft.AspNetCore.Hosting.Server; using Microsoft.AspNetCore.Hosting.Server;
using Microsoft.AspNetCore.Hosting.Server.Features; using Microsoft.AspNetCore.Hosting.Server.Features;
using Microsoft.AspNetCore.Http; using Microsoft.AspNetCore.Http;
using Microsoft.AspNetCore.HttpLogging;
using Microsoft.Extensions.Configuration; using Microsoft.Extensions.Configuration;
using Microsoft.Extensions.DependencyInjection; using Microsoft.Extensions.DependencyInjection;
using Microsoft.Extensions.Hosting; using Microsoft.Extensions.Hosting;
@@ -15,14 +17,23 @@ namespace FileTime.Server.Web;
public class Program public class Program
{ {
public static async Task Start(string[] args, IContainer rootContainer, CancellationToken applicationExit) public static async Task Start(ConnectionHandlerParameters parameters)
{ {
var builder = WebApplication.CreateBuilder(args); var builder = WebApplication.CreateBuilder(parameters.Args);
var configuration = builder.Configuration; var configuration = builder.Configuration;
//Note: Use app wide configuration instead of the default ASP.NET Core configuration
#pragma warning disable ASP0013
builder.Host.ConfigureAppConfiguration((_, configurationBuilder) =>
{
configurationBuilder.Sources.Clear();
configurationBuilder.AddConfiguration(parameters.ConfigurationRoot);
});
#pragma warning restore ASP0013
builder.Host.UseServiceProviderFactory( builder.Host.UseServiceProviderFactory(
new AutofacChildLifetimeScopeServiceProviderFactory(rootContainer.BeginLifetimeScope("WebScope")) new AutofacChildLifetimeScopeServiceProviderFactory(parameters.RootContainer.BeginLifetimeScope("WebScope"))
); );
builder.Host.UseSerilog(); builder.Host.UseSerilog();
@@ -32,6 +43,7 @@ public class Program
serverOptions.Listen(new IPEndPoint(IPAddress.Loopback, port)); serverOptions.Listen(new IPEndPoint(IPAddress.Loopback, port));
}); });
builder.Services.AddHttpLogging(options => options.LoggingFields = HttpLoggingFields.All);
builder.Services.AddSignalR(); builder.Services.AddSignalR();
builder.Services.AddHealthChecks(); builder.Services.AddHealthChecks();
builder.Services.AddHostedService<PortWriterService>(); builder.Services.AddHostedService<PortWriterService>();
@@ -43,11 +55,12 @@ public class Program
if (!app.Environment.IsDevelopment()) if (!app.Environment.IsDevelopment())
{ {
app.UseHttpLogging();
} }
app.MapHub<ConnectionHub>("/RemoteHub"); app.MapHub<ConnectionHub>("/RemoteHub");
app.UseHealthChecks("/health"); app.UseHealthChecks("/health");
await app.RunAsync(applicationExit); await app.RunAsync(parameters.ApplicationExit);
} }
} }

View File

@@ -17,7 +17,15 @@
<ItemGroup> <ItemGroup>
<PackageReference Include="Autofac.Extensions.DependencyInjection" Version="8.0.0" /> <PackageReference Include="Autofac.Extensions.DependencyInjection" Version="8.0.0" />
<PackageReference Include="Serilog" Version="3.0.1" /> <PackageReference Include="Serilog" Version="3.0.1" />
<PackageReference Include="Serilog.Settings.Configuration" Version="7.0.0" />
<PackageReference Include="Serilog.Sinks.Console" Version="4.1.0" /> <PackageReference Include="Serilog.Sinks.Console" Version="4.1.0" />
</ItemGroup> </ItemGroup>
<ItemGroup Condition="'$(Configuration)' == 'Debug'">
<Content Include="appsettings.Development.json" CopyToOutputDirectory="PreserveNewest" />
</ItemGroup>
<ItemGroup Condition="'$(Configuration)' == 'Debug'">
<Content Include="appsettings.Local.json" CopyToOutputDirectory="PreserveNewest" />
</ItemGroup>
</Project> </Project>

View File

@@ -11,21 +11,33 @@ using Serilog;
var applicationCancellation = new CancellationTokenSource(); var applicationCancellation = new CancellationTokenSource();
var configurationRoot = CreateConfiguration();
Log.Logger = new LoggerConfiguration() Log.Logger = new LoggerConfiguration()
.MinimumLevel.Debug() #if DEBUG
.MinimumLevel.Verbose()
.ReadFrom.Configuration(configurationRoot)
#else
.MinimumLevel.Information()
#endif
.WriteTo.Console() .WriteTo.Console()
.CreateLogger(); .CreateLogger();
var bootstrapConfiguration = CreateConfiguration(); var rootContainer = CreateRootDiContainer(configurationRoot);
var rootContainer = CreateRootDiContainer(bootstrapConfiguration); var handlerParameters = new ConnectionHandlerParameters(
args,
rootContainer,
configurationRoot,
applicationCancellation.Token
);
var webThread = CreateStartup(FileTime.Server.Web.Program.Start); var webThread = CreateStartup(FileTime.Server.Web.Program.Start);
webThread.Start(); webThread.Start();
Thread CreateStartup(Func<string[], IContainer, CancellationToken, Task> startup) Thread CreateStartup(Func<ConnectionHandlerParameters, Task> startup)
{ {
var thread = new Thread(() => { HandleStartup(() => startup(args, rootContainer, applicationCancellation.Token).Wait()); }); var thread = new Thread(() => { HandleStartup(() => startup(handlerParameters).Wait()); });
return thread; return thread;
} }
@@ -45,6 +57,11 @@ void HandleStartup(Action action)
IConfigurationRoot CreateConfiguration() IConfigurationRoot CreateConfiguration()
{ {
var configurationBuilder = new ConfigurationBuilder(); var configurationBuilder = new ConfigurationBuilder();
configurationBuilder.AddCommandLine(args);
#if DEBUG
configurationBuilder.AddJsonFile("appsettings.Development.json", optional: true);
configurationBuilder.AddJsonFile("appsettings.Local.json", optional: true);
#endif
return configurationBuilder.Build(); return configurationBuilder.Build();
} }
@@ -54,9 +71,10 @@ IContainer CreateRootDiContainer(IConfigurationRoot configuration)
.RegisterDefaultServices(configuration) .RegisterDefaultServices(configuration)
.AddLocalProviderServices() .AddLocalProviderServices()
.AddServerServices() .AddServerServices()
.AddServerCoreServices()
.AddLogging(loggingBuilder => loggingBuilder.AddSerilog()); .AddLogging(loggingBuilder => loggingBuilder.AddSerilog());
serviceCollection.TryAddSingleton<IApplicationStopper>( serviceCollection.AddSingleton<IApplicationStopper>(
new ApplicationStopper(() => applicationCancellation.Cancel()) new ApplicationStopper(() => applicationCancellation.Cancel())
); );

View File

@@ -0,0 +1,13 @@
{
"Serilog": {
"MinimumLevel": {
"Default": "Verbose",
"Override": {
"System": "Information",
"Microsoft": "Information",
"Microsoft.AspNetCore.SignalR": "Debug",
"Microsoft.AspNetCore.Http.Connections": "Debug"
}
}
}
}