Remote write
This commit is contained in:
@@ -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>
|
||||||
|
|
||||||
|
|||||||
@@ -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()
|
||||||
|
|||||||
@@ -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>
|
||||||
|
|||||||
@@ -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"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -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;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -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);
|
||||||
}
|
}
|
||||||
@@ -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;
|
||||||
@@ -30,6 +32,20 @@ public class AdminContentAccessorFactory : IAdminContentAccessorFactory
|
|||||||
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>
|
||||||
{
|
{
|
||||||
|
|||||||
@@ -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"
|
||||||
|
|||||||
@@ -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>
|
||||||
|
{
|
||||||
|
}
|
||||||
@@ -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);
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -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;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -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
|
||||||
|
);
|
||||||
@@ -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);
|
||||||
|
}
|
||||||
@@ -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>
|
||||||
|
|||||||
@@ -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);
|
||||||
}
|
}
|
||||||
@@ -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);
|
||||||
}
|
}
|
||||||
@@ -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;
|
||||||
@@ -18,9 +21,11 @@ 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>();
|
||||||
}
|
}
|
||||||
@@ -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);
|
||||||
}
|
}
|
||||||
@@ -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);
|
||||||
|
}
|
||||||
@@ -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>
|
||||||
|
|||||||
@@ -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;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -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,15 +14,18 @@ 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()
|
||||||
@@ -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;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
@@ -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);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -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>
|
||||||
|
|||||||
@@ -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())
|
||||||
);
|
);
|
||||||
|
|
||||||
|
|||||||
13
src/Server/FileTime.Server/appsettings.Development.json
Normal file
13
src/Server/FileTime.Server/appsettings.Development.json
Normal file
@@ -0,0 +1,13 @@
|
|||||||
|
{
|
||||||
|
"Serilog": {
|
||||||
|
"MinimumLevel": {
|
||||||
|
"Default": "Verbose",
|
||||||
|
"Override": {
|
||||||
|
"System": "Information",
|
||||||
|
"Microsoft": "Information",
|
||||||
|
"Microsoft.AspNetCore.SignalR": "Debug",
|
||||||
|
"Microsoft.AspNetCore.Http.Connections": "Debug"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
Reference in New Issue
Block a user