From d26401948a56d0df1666c4e04d74cc47c33857b2 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=C3=81d=C3=A1m=20Kov=C3=A1cs?= Date: Wed, 26 Jul 2023 21:21:55 +0200 Subject: [PATCH] Remote write --- .../FileTime.Core.Command.csproj | 1 + .../Avalonia/FileTime.GuiApp.App/App.axaml.cs | 2 +- .../FileTime.GuiApp.App.csproj | 1 + .../appsettings.Development.json | 4 +- .../LocalContentWriterFactory.cs | 26 +++++++++- .../IAdminContentAccessorFactory.cs | 4 +- .../AdminContentAccessorFactory.cs | 26 ++++++++-- .../AdminElevationManager.cs | 4 +- .../IRemoteContentWriter.cs | 12 +++++ .../RemoteContentWriter.cs | 51 +++++++++++++++++++ .../FileTime.Providers.Remote/Startup.cs | 1 + .../ConnectionHandlerParameters.cs | 11 ++++ .../ContentAccess/IContentAccessManager.cs | 10 ++++ ...FileTime.Server.Common.Abstractions.csproj | 5 ++ .../IRemoteConnection.cs | 4 ++ .../Connections/SignalR/ISignalRHub.cs | 4 ++ .../Connections/SignalR/SignalRConnection.cs | 31 ++++++++--- .../ContentAccess/ContentAccessManager.cs | 13 +++++ .../FileTime.Server.Common.csproj | 1 + src/Server/FileTime.Server.Common/Startup.cs | 7 ++- .../FileTime.Server.Web/ConnectionHub.cs | 37 ++++++++++++-- src/Server/FileTime.Server.Web/Main.cs | 21 ++++++-- .../FileTime.Server/FileTime.Server.csproj | 8 +++ src/Server/FileTime.Server/Program.cs | 30 ++++++++--- .../appsettings.Development.json | 13 +++++ 25 files changed, 293 insertions(+), 34 deletions(-) create mode 100644 src/Providers/FileTime.Providers.Remote.Abstractions/IRemoteContentWriter.cs create mode 100644 src/Providers/FileTime.Providers.Remote/RemoteContentWriter.cs create mode 100644 src/Server/FileTime.Server.Common.Abstractions/ConnectionHandlerParameters.cs create mode 100644 src/Server/FileTime.Server.Common.Abstractions/ContentAccess/IContentAccessManager.cs create mode 100644 src/Server/FileTime.Server.Common/ContentAccess/ContentAccessManager.cs create mode 100644 src/Server/FileTime.Server/appsettings.Development.json diff --git a/src/Core/FileTime.Core.Command/FileTime.Core.Command.csproj b/src/Core/FileTime.Core.Command/FileTime.Core.Command.csproj index 7b6d24c..50c2c46 100644 --- a/src/Core/FileTime.Core.Command/FileTime.Core.Command.csproj +++ b/src/Core/FileTime.Core.Command/FileTime.Core.Command.csproj @@ -5,6 +5,7 @@ + diff --git a/src/GuiApp/Avalonia/FileTime.GuiApp.App/App.axaml.cs b/src/GuiApp/Avalonia/FileTime.GuiApp.App/App.axaml.cs index 3cc81c6..5ce3207 100644 --- a/src/GuiApp/Avalonia/FileTime.GuiApp.App/App.axaml.cs +++ b/src/GuiApp/Avalonia/FileTime.GuiApp.App/App.axaml.cs @@ -21,7 +21,7 @@ public class App : Application var configuration = Startup.CreateConfiguration(); DI.ServiceProvider = DependencyInjection .RegisterDefaultServices(configuration: configuration) - .AddRemoteServices() + .AddServerCoreServices() .AddFrequencyNavigation() .AddCommandPalette() .AddSearch() diff --git a/src/GuiApp/Avalonia/FileTime.GuiApp.App/FileTime.GuiApp.App.csproj b/src/GuiApp/Avalonia/FileTime.GuiApp.App/FileTime.GuiApp.App.csproj index 8842942..ce029e4 100644 --- a/src/GuiApp/Avalonia/FileTime.GuiApp.App/FileTime.GuiApp.App.csproj +++ b/src/GuiApp/Avalonia/FileTime.GuiApp.App/FileTime.GuiApp.App.csproj @@ -36,6 +36,7 @@ + diff --git a/src/GuiApp/Avalonia/FileTime.GuiApp.App/appsettings.Development.json b/src/GuiApp/Avalonia/FileTime.GuiApp.App/appsettings.Development.json index cca6606..1a159eb 100644 --- a/src/GuiApp/Avalonia/FileTime.GuiApp.App/appsettings.Development.json +++ b/src/GuiApp/Avalonia/FileTime.GuiApp.App/appsettings.Development.json @@ -4,7 +4,9 @@ "Default": "Verbose", "Override": { "Microsoft": "Information", - "System": "Warning" + "System": "Warning", + "Microsoft.AspNetCore.SignalR": "Verbose", + "Microsoft.AspNetCore.Http.Connections": "Verbose" } } } diff --git a/src/Providers/FileTime.Providers.Local/LocalContentWriterFactory.cs b/src/Providers/FileTime.Providers.Local/LocalContentWriterFactory.cs index f2e0c4a..66696a0 100644 --- a/src/Providers/FileTime.Providers.Local/LocalContentWriterFactory.cs +++ b/src/Providers/FileTime.Providers.Local/LocalContentWriterFactory.cs @@ -1,12 +1,34 @@ using FileTime.Core.ContentAccess; using FileTime.Core.Models; +using FileTime.Providers.LocalAdmin; +using Microsoft.Extensions.Logging; namespace FileTime.Providers.Local; public class LocalContentWriterFactory : IContentWriterFactory { - public Task CreateContentWriterAsync(IElement element) + private readonly IAdminContentAccessorFactory _adminContentAccessorFactory; + private readonly ILogger _logger; + + public LocalContentWriterFactory( + IAdminContentAccessorFactory adminContentAccessorFactory, + ILogger logger) { - return Task.FromResult((IContentWriter) new LocalContentWriter(File.OpenWrite(element.NativePath!.Path))); + _adminContentAccessorFactory = adminContentAccessorFactory; + _logger = logger; + } + + public async Task 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; + } } } \ No newline at end of file diff --git a/src/Providers/FileTime.Providers.LocalAdmin.Abstractions/IAdminContentAccessorFactory.cs b/src/Providers/FileTime.Providers.LocalAdmin.Abstractions/IAdminContentAccessorFactory.cs index 8e6e70c..664c5a6 100644 --- a/src/Providers/FileTime.Providers.LocalAdmin.Abstractions/IAdminContentAccessorFactory.cs +++ b/src/Providers/FileTime.Providers.LocalAdmin.Abstractions/IAdminContentAccessorFactory.cs @@ -1,4 +1,5 @@ -using FileTime.Providers.Remote; +using FileTime.Core.Models; +using FileTime.Providers.Remote; namespace FileTime.Providers.LocalAdmin; @@ -8,4 +9,5 @@ public interface IAdminContentAccessorFactory Task CreateAdminItemCreatorAsync(); Task CreateAdminItemDeleterAsync(); Task CreateAdminItemMoverAsync(); + Task CreateContentWriterAsync(NativePath nativePath); } \ No newline at end of file diff --git a/src/Providers/FileTime.Providers.LocalAdmin/AdminContentAccessorFactory.cs b/src/Providers/FileTime.Providers.LocalAdmin/AdminContentAccessorFactory.cs index e3e2bc3..e277870 100644 --- a/src/Providers/FileTime.Providers.LocalAdmin/AdminContentAccessorFactory.cs +++ b/src/Providers/FileTime.Providers.LocalAdmin/AdminContentAccessorFactory.cs @@ -1,7 +1,9 @@ using System.Diagnostics; +using FileTime.Core.Models; using FileTime.Providers.Remote; using FileTime.Server.Common; using InitableService; +using Microsoft.Extensions.DependencyInjection; namespace FileTime.Providers.LocalAdmin; @@ -13,7 +15,7 @@ public class AdminContentAccessorFactory : IAdminContentAccessorFactory public AdminContentAccessorFactory( IAdminElevationManager adminElevationManager, IServiceProvider serviceProvider - ) + ) { _adminElevationManager = adminElevationManager; _serviceProvider = serviceProvider; @@ -27,19 +29,33 @@ public class AdminContentAccessorFactory : IAdminContentAccessorFactory public async Task CreateAdminItemDeleterAsync() => await CreateHelperAsync(); - public async Task CreateAdminItemMoverAsync() + public async Task CreateAdminItemMoverAsync() => await CreateHelperAsync(); + public async Task CreateContentWriterAsync(NativePath nativePath) + { + await _adminElevationManager.CreateAdminInstanceIfNecessaryAsync(); + var connection = await _adminElevationManager.CreateConnectionAsync(); + var contentWriter = _serviceProvider.GetInitableResolver( + connection, + _adminElevationManager.ProviderName, + nativePath, + Guid.NewGuid() + ).GetRequiredService(); + + return contentWriter; + } + private async Task CreateHelperAsync() where T : class, IInitable { await _adminElevationManager.CreateAdminInstanceIfNecessaryAsync(); var connection = await _adminElevationManager.CreateConnectionAsync(); - + Debug.Assert(connection != null); - + var helper = _serviceProvider.GetInitableResolver( - connection, + connection, _adminElevationManager.ProviderName) .GetRequiredService(); return helper; diff --git a/src/Providers/FileTime.Providers.LocalAdmin/AdminElevationManager.cs b/src/Providers/FileTime.Providers.LocalAdmin/AdminElevationManager.cs index 98beae6..7a558f7 100644 --- a/src/Providers/FileTime.Providers.LocalAdmin/AdminElevationManager.cs +++ b/src/Providers/FileTime.Providers.LocalAdmin/AdminElevationManager.cs @@ -89,8 +89,6 @@ public class AdminElevationManager : IAdminElevationManager, INotifyPropertyChan process.Start(); _adminProcess = process; - IsAdminInstanceRunning = true; - //TODO: timeout while (!File.Exists(portFileName) || new FileInfo(portFileName).Length == 0) await Task.Delay(10); @@ -109,6 +107,8 @@ public class AdminElevationManager : IAdminElevationManager, INotifyPropertyChan } } + IsAdminInstanceRunning = true; + var connectionInfo = new ConnectionInfo { SignalRBaseUrl = $"http://localhost:{port}/RemoteHub" diff --git a/src/Providers/FileTime.Providers.Remote.Abstractions/IRemoteContentWriter.cs b/src/Providers/FileTime.Providers.Remote.Abstractions/IRemoteContentWriter.cs new file mode 100644 index 0000000..3df1127 --- /dev/null +++ b/src/Providers/FileTime.Providers.Remote.Abstractions/IRemoteContentWriter.cs @@ -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 +{ +} \ No newline at end of file diff --git a/src/Providers/FileTime.Providers.Remote/RemoteContentWriter.cs b/src/Providers/FileTime.Providers.Remote/RemoteContentWriter.cs new file mode 100644 index 0000000..eb3a90a --- /dev/null +++ b/src/Providers/FileTime.Providers.Remote/RemoteContentWriter.cs @@ -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); + } +} \ No newline at end of file diff --git a/src/Providers/FileTime.Providers.Remote/Startup.cs b/src/Providers/FileTime.Providers.Remote/Startup.cs index 6be7b19..9e77315 100644 --- a/src/Providers/FileTime.Providers.Remote/Startup.cs +++ b/src/Providers/FileTime.Providers.Remote/Startup.cs @@ -11,6 +11,7 @@ public static class Startup serviceCollection.TryAddTransient(); serviceCollection.TryAddTransient(); serviceCollection.TryAddTransient(); + serviceCollection.TryAddTransient(); return serviceCollection; } } \ No newline at end of file diff --git a/src/Server/FileTime.Server.Common.Abstractions/ConnectionHandlerParameters.cs b/src/Server/FileTime.Server.Common.Abstractions/ConnectionHandlerParameters.cs new file mode 100644 index 0000000..22f1d5e --- /dev/null +++ b/src/Server/FileTime.Server.Common.Abstractions/ConnectionHandlerParameters.cs @@ -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 +); \ No newline at end of file diff --git a/src/Server/FileTime.Server.Common.Abstractions/ContentAccess/IContentAccessManager.cs b/src/Server/FileTime.Server.Common.Abstractions/ContentAccess/IContentAccessManager.cs new file mode 100644 index 0000000..6d8d93e --- /dev/null +++ b/src/Server/FileTime.Server.Common.Abstractions/ContentAccess/IContentAccessManager.cs @@ -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); +} \ No newline at end of file diff --git a/src/Server/FileTime.Server.Common.Abstractions/FileTime.Server.Common.Abstractions.csproj b/src/Server/FileTime.Server.Common.Abstractions/FileTime.Server.Common.Abstractions.csproj index aa2bde0..9f55574 100644 --- a/src/Server/FileTime.Server.Common.Abstractions/FileTime.Server.Common.Abstractions.csproj +++ b/src/Server/FileTime.Server.Common.Abstractions/FileTime.Server.Common.Abstractions.csproj @@ -11,4 +11,9 @@ + + + + + diff --git a/src/Server/FileTime.Server.Common.Abstractions/IRemoteConnection.cs b/src/Server/FileTime.Server.Common.Abstractions/IRemoteConnection.cs index c99c791..01c9419 100644 --- a/src/Server/FileTime.Server.Common.Abstractions/IRemoteConnection.cs +++ b/src/Server/FileTime.Server.Common.Abstractions/IRemoteConnection.cs @@ -9,4 +9,8 @@ public interface IRemoteConnection Task CreateElementAsync(string contentProviderId, FullName fullName); Task DeleteItemAsync(string contentProviderId, FullName fullName); 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); } \ No newline at end of file diff --git a/src/Server/FileTime.Server.Common/Connections/SignalR/ISignalRHub.cs b/src/Server/FileTime.Server.Common/Connections/SignalR/ISignalRHub.cs index 91d1fdd..a2d5283 100644 --- a/src/Server/FileTime.Server.Common/Connections/SignalR/ISignalRHub.cs +++ b/src/Server/FileTime.Server.Common/Connections/SignalR/ISignalRHub.cs @@ -7,4 +7,8 @@ public interface ISignalRHub Task CreateElementAsync(string contentProviderId, string fullName); Task DeleteItemAsync(string contentProviderId, string fullName); 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); } \ No newline at end of file diff --git a/src/Server/FileTime.Server.Common/Connections/SignalR/SignalRConnection.cs b/src/Server/FileTime.Server.Common/Connections/SignalR/SignalRConnection.cs index 1a4047c..9659a4a 100644 --- a/src/Server/FileTime.Server.Common/Connections/SignalR/SignalRConnection.cs +++ b/src/Server/FileTime.Server.Common/Connections/SignalR/SignalRConnection.cs @@ -1,6 +1,9 @@ -using FileTime.Core.Models; +using System.Text; +using FileTime.Core.Models; using InitableService; using Microsoft.AspNetCore.SignalR.Client; +using Microsoft.Extensions.Logging; +using Serilog; using TypedSignalR.Client; namespace FileTime.Server.Common.Connections.SignalR; @@ -8,8 +11,8 @@ namespace FileTime.Server.Common.Connections.SignalR; public class SignalRConnection : IRemoteConnection, IAsyncInitable { private static readonly Dictionary Connections = new(); - private static readonly object ConnectionsLock = new(); - + private static readonly object ConnectionsLock = new(); + private string _baseUrl = null!; private HubConnection _connection = null!; private ISignalRHub _client = null!; @@ -18,13 +21,15 @@ public class SignalRConnection : IRemoteConnection, IAsyncInitable { _baseUrl = baseUrl; - _connection = new HubConnectionBuilder() - .WithUrl(_baseUrl) - .Build(); + var connectionBuilder = new HubConnectionBuilder() + .ConfigureLogging(logging => { logging.AddSerilog(); }) + .WithUrl(_baseUrl); + + _connection = connectionBuilder.Build(); await _connection.StartAsync(); _client = _connection.CreateHubProxy(); } - + public static async Task GetOrCreateForAsync(string baseUrl) { SignalRConnection? connection; @@ -62,4 +67,16 @@ public class SignalRConnection : IRemoteConnection, IAsyncInitable public async Task MoveItemAsync(string contentProviderId, FullName fullName, FullName newPath) => 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); } \ No newline at end of file diff --git a/src/Server/FileTime.Server.Common/ContentAccess/ContentAccessManager.cs b/src/Server/FileTime.Server.Common/ContentAccess/ContentAccessManager.cs new file mode 100644 index 0000000..d3a6f6d --- /dev/null +++ b/src/Server/FileTime.Server.Common/ContentAccess/ContentAccessManager.cs @@ -0,0 +1,13 @@ +using FileTime.Core.ContentAccess; + +namespace FileTime.Server.Common.ContentAccess; + +public class ContentAccessManager : IContentAccessManager +{ + private readonly Dictionary _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); +} \ No newline at end of file diff --git a/src/Server/FileTime.Server.Common/FileTime.Server.Common.csproj b/src/Server/FileTime.Server.Common/FileTime.Server.Common.csproj index 5908eec..cdd9ad8 100644 --- a/src/Server/FileTime.Server.Common/FileTime.Server.Common.csproj +++ b/src/Server/FileTime.Server.Common/FileTime.Server.Common.csproj @@ -12,6 +12,7 @@ + all runtime; build; native; contentfiles; analyzers; buildtransitive diff --git a/src/Server/FileTime.Server.Common/Startup.cs b/src/Server/FileTime.Server.Common/Startup.cs index 4c34e58..c419b2f 100644 --- a/src/Server/FileTime.Server.Common/Startup.cs +++ b/src/Server/FileTime.Server.Common/Startup.cs @@ -1,14 +1,17 @@ using FileTime.Server.Common.Connections.SignalR; +using FileTime.Server.Common.ContentAccess; using Microsoft.Extensions.DependencyInjection; +using Microsoft.Extensions.DependencyInjection.Extensions; namespace FileTime.Server.Common; public static class Startup { - public static IServiceCollection AddRemoteServices(this IServiceCollection services) + public static IServiceCollection AddServerCoreServices(this IServiceCollection services) { services.AddTransient(); - services.AddSingleton(); + services.TryAddSingleton(); + services.AddSingleton(); return services; } } \ No newline at end of file diff --git a/src/Server/FileTime.Server.Web/ConnectionHub.cs b/src/Server/FileTime.Server.Web/ConnectionHub.cs index 16cf313..a07cf31 100644 --- a/src/Server/FileTime.Server.Web/ConnectionHub.cs +++ b/src/Server/FileTime.Server.Web/ConnectionHub.cs @@ -1,7 +1,10 @@ -using FileTime.Core.ContentAccess; +using System.Text; +using FileTime.Core.ContentAccess; using FileTime.Core.Models; +using FileTime.Core.Timeline; using FileTime.Server.Common; using FileTime.Server.Common.Connections.SignalR; +using FileTime.Server.Common.ContentAccess; using Microsoft.AspNetCore.SignalR; namespace FileTime.Server.Web; @@ -11,17 +14,20 @@ public class ConnectionHub : Hub, ISignalRHub private readonly IContentProviderRegistry _contentProviderRegistry; private readonly IContentAccessorFactory _contentAccessorFactory; private readonly IApplicationStopper _applicationStopper; + private readonly IContentAccessManager _contentAccessManager; public ConnectionHub( IContentProviderRegistry contentProviderRegistry, IContentAccessorFactory contentAccessorFactory, - IApplicationStopper applicationStopper) + IApplicationStopper applicationStopper, + IContentAccessManager contentAccessManager) { _contentProviderRegistry = contentProviderRegistry; _contentAccessorFactory = contentAccessorFactory; _applicationStopper = applicationStopper; + _contentAccessManager = contentAccessManager; } - + public Task Exit() { _applicationStopper.Stop(); @@ -56,4 +62,29 @@ public class ConnectionHub : Hub, ISignalRHub var itemDeleter = _contentAccessorFactory.GetItemMover(contentProvider); 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; + } } \ No newline at end of file diff --git a/src/Server/FileTime.Server.Web/Main.cs b/src/Server/FileTime.Server.Web/Main.cs index 3c62f9c..4767fd1 100644 --- a/src/Server/FileTime.Server.Web/Main.cs +++ b/src/Server/FileTime.Server.Web/Main.cs @@ -1,11 +1,13 @@ using System.Net; using Autofac; using Autofac.Extensions.DependencyInjection; +using FileTime.Server.Common; using Microsoft.AspNetCore.Builder; using Microsoft.AspNetCore.Hosting; using Microsoft.AspNetCore.Hosting.Server; using Microsoft.AspNetCore.Hosting.Server.Features; using Microsoft.AspNetCore.Http; +using Microsoft.AspNetCore.HttpLogging; using Microsoft.Extensions.Configuration; using Microsoft.Extensions.DependencyInjection; using Microsoft.Extensions.Hosting; @@ -15,14 +17,23 @@ namespace FileTime.Server.Web; 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; + //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( - new AutofacChildLifetimeScopeServiceProviderFactory(rootContainer.BeginLifetimeScope("WebScope")) + new AutofacChildLifetimeScopeServiceProviderFactory(parameters.RootContainer.BeginLifetimeScope("WebScope")) ); builder.Host.UseSerilog(); @@ -32,6 +43,7 @@ public class Program serverOptions.Listen(new IPEndPoint(IPAddress.Loopback, port)); }); + builder.Services.AddHttpLogging(options => options.LoggingFields = HttpLoggingFields.All); builder.Services.AddSignalR(); builder.Services.AddHealthChecks(); builder.Services.AddHostedService(); @@ -43,11 +55,12 @@ public class Program if (!app.Environment.IsDevelopment()) { + app.UseHttpLogging(); } app.MapHub("/RemoteHub"); app.UseHealthChecks("/health"); - await app.RunAsync(applicationExit); + await app.RunAsync(parameters.ApplicationExit); } } \ No newline at end of file diff --git a/src/Server/FileTime.Server/FileTime.Server.csproj b/src/Server/FileTime.Server/FileTime.Server.csproj index d87b452..fa9c6fe 100644 --- a/src/Server/FileTime.Server/FileTime.Server.csproj +++ b/src/Server/FileTime.Server/FileTime.Server.csproj @@ -17,7 +17,15 @@ + + + + + + + + diff --git a/src/Server/FileTime.Server/Program.cs b/src/Server/FileTime.Server/Program.cs index 07c4fee..f7cfba8 100644 --- a/src/Server/FileTime.Server/Program.cs +++ b/src/Server/FileTime.Server/Program.cs @@ -11,21 +11,33 @@ using Serilog; var applicationCancellation = new CancellationTokenSource(); +var configurationRoot = CreateConfiguration(); + Log.Logger = new LoggerConfiguration() - .MinimumLevel.Debug() +#if DEBUG + .MinimumLevel.Verbose() + .ReadFrom.Configuration(configurationRoot) +#else + .MinimumLevel.Information() +#endif .WriteTo.Console() .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); webThread.Start(); -Thread CreateStartup(Func startup) +Thread CreateStartup(Func startup) { - var thread = new Thread(() => { HandleStartup(() => startup(args, rootContainer, applicationCancellation.Token).Wait()); }); + var thread = new Thread(() => { HandleStartup(() => startup(handlerParameters).Wait()); }); return thread; } @@ -45,6 +57,11 @@ void HandleStartup(Action action) IConfigurationRoot CreateConfiguration() { 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(); } @@ -54,9 +71,10 @@ IContainer CreateRootDiContainer(IConfigurationRoot configuration) .RegisterDefaultServices(configuration) .AddLocalProviderServices() .AddServerServices() + .AddServerCoreServices() .AddLogging(loggingBuilder => loggingBuilder.AddSerilog()); - serviceCollection.TryAddSingleton( + serviceCollection.AddSingleton( new ApplicationStopper(() => applicationCancellation.Cancel()) ); diff --git a/src/Server/FileTime.Server/appsettings.Development.json b/src/Server/FileTime.Server/appsettings.Development.json new file mode 100644 index 0000000..b13aca4 --- /dev/null +++ b/src/Server/FileTime.Server/appsettings.Development.json @@ -0,0 +1,13 @@ +{ + "Serilog": { + "MinimumLevel": { + "Default": "Verbose", + "Override": { + "System": "Information", + "Microsoft": "Information", + "Microsoft.AspNetCore.SignalR": "Debug", + "Microsoft.AspNetCore.Http.Connections": "Debug" + } + } + } +} \ No newline at end of file