RemoteContentProvider get items WIP

This commit is contained in:
2023-08-28 09:29:52 +02:00
parent 79971fe0f4
commit bb44ca0308
49 changed files with 765 additions and 118 deletions

View File

@@ -9,6 +9,7 @@
<ItemGroup>
<ProjectReference Include="..\..\Core\FileTime.Core.Abstraction\FileTime.Core.Abstraction.csproj" />
<ProjectReference Include="..\..\Core\FileTime.Core.Serialization\FileTime.Core.Serialization.csproj" />
</ItemGroup>
<ItemGroup>

View File

@@ -1,4 +1,7 @@
using FileTime.Core.Models;
using FileTime.Core.Enums;
using FileTime.Core.Models;
using FileTime.Core.Serialization;
using FileTime.Core.Timeline;
namespace FileTime.Server.Common;
@@ -13,5 +16,17 @@ public interface IRemoteConnection
Task WriteBytesAsync(string transactionId, byte[] data, int? index, CancellationToken cancellationToken = default);
Task FlushWriterAsync(string transactionId, CancellationToken cancellationToken = default);
Task CloseWriterAsync(string transactionId);
Task<NativePath?> GetNativePathAsync(FullName fullName);
Task<NativePath> GetNativePathAsync(string contentProviderId, FullName fullName);
Task<ISerialized> GetItemByNativePathAsync(
string contentProviderId,
NativePath nativePath,
PointInTime pointInTime,
bool forceResolve,
AbsolutePathType forceResolvePathType,
ItemInitializationSettings itemInitializationSettings);
Task<SerializedAbsolutePath[]> GetChildren(
string contentProviderId,
string fullName);
}

View File

@@ -0,0 +1,9 @@
using FileTime.Core.Models;
namespace FileTime.Server.Common.ItemTracker;
public interface IItemTrackerRegistry
{
int Register(IItem item);
event Action<int>? ItemRemoved;
}

View File

@@ -2,5 +2,5 @@
public interface ISignalRClient
{
Task RemoveTrackedItem(int itemId);
}

View File

@@ -1,7 +1,13 @@
namespace FileTime.Server.Common.Connections.SignalR;
using FileTime.Core.Enums;
using FileTime.Core.Models;
using FileTime.Core.Serialization;
using FileTime.Core.Timeline;
namespace FileTime.Server.Common.Connections.SignalR;
public interface ISignalRHub
{
Task SetClientIdentifier(string providerName);
Task Exit();
Task CreateContainerAsync(string contentProviderId, string fullName);
Task CreateElementAsync(string contentProviderId, string fullName);
@@ -15,5 +21,16 @@ public interface ISignalRHub
//TODO: CancellationToken https://github.com/nenoNaninu/TypedSignalR.Client/issues/120
Task WriteBytesAsync(string transactionId, string data, int index);
Task CloseWriterAsync(string transactionId);
Task<string?> GetNativePathAsync(string fullNamePath);
Task<string> GetNativePathAsync(string contentProviderId, string fullNamePath);
Task<ISerialized> GetItemByNativePathAsync(
string contentProviderId,
NativePath nativePath,
PointInTime pointInTime,
bool forceResolve,
AbsolutePathType forceResolvePathType,
ItemInitializationSettings itemInitializationSettings);
Task<SerializedAbsolutePath[]> GetChildren(
string contentProviderId,
string fullName);
}

View File

@@ -1,4 +1,7 @@
using FileTime.Core.Models;
using FileTime.Core.Enums;
using FileTime.Core.Models;
using FileTime.Core.Serialization;
using FileTime.Core.Timeline;
using InitableService;
using Microsoft.AspNetCore.SignalR.Client;
using Serilog;
@@ -6,7 +9,7 @@ using TypedSignalR.Client;
namespace FileTime.Server.Common.Connections.SignalR;
public class SignalRConnection : IRemoteConnection, IAsyncInitable<string>
public class SignalRConnection : IRemoteConnection, IAsyncInitable<string, string>
{
private static readonly Dictionary<string, SignalRConnection> Connections = new();
private static readonly object ConnectionsLock = new();
@@ -15,7 +18,7 @@ public class SignalRConnection : IRemoteConnection, IAsyncInitable<string>
private HubConnection _connection = null!;
private ISignalRHub _client = null!;
public async Task InitAsync(string baseUrl)
public async Task InitAsync(string baseUrl, string providerName)
{
_baseUrl = baseUrl;
@@ -26,9 +29,10 @@ public class SignalRConnection : IRemoteConnection, IAsyncInitable<string>
_connection = connectionBuilder.Build();
await _connection.StartAsync();
_client = _connection.CreateHubProxy<ISignalRHub>();
await _client.SetClientIdentifier(providerName);
}
public static async Task<SignalRConnection> GetOrCreateForAsync(string baseUrl)
public static async Task<SignalRConnection> GetOrCreateForAsync(string baseUrl, string providerName)
{
SignalRConnection? connection;
lock (ConnectionsLock)
@@ -47,7 +51,7 @@ public class SignalRConnection : IRemoteConnection, IAsyncInitable<string>
Connections.Add(baseUrl, connection);
}
await connection.InitAsync(baseUrl);
await connection.InitAsync(baseUrl, providerName);
return connection;
}
@@ -78,9 +82,32 @@ public class SignalRConnection : IRemoteConnection, IAsyncInitable<string>
public async Task CloseWriterAsync(string transactionId)
=> await _client.CloseWriterAsync(transactionId);
public async Task<NativePath?> GetNativePathAsync(FullName fullName)
public async Task<NativePath> GetNativePathAsync(string contentProviderId, FullName fullName)
{
var path = await _client.GetNativePathAsync(fullName.Path);
return path is null ? null : new NativePath(path);
var path = await _client.GetNativePathAsync(contentProviderId, fullName.Path);
return new NativePath(path);
}
public async Task<ISerialized> GetItemByNativePathAsync(
string contentProviderId,
NativePath nativePath,
PointInTime pointInTime,
bool forceResolve,
AbsolutePathType forceResolvePathType,
ItemInitializationSettings itemInitializationSettings)
{
var item = await _client.GetItemByNativePathAsync(
contentProviderId,
nativePath,
pointInTime,
forceResolve,
forceResolvePathType,
itemInitializationSettings
);
return item;
}
public async Task<SerializedAbsolutePath[]> GetChildren(string contentProviderId, string fullName)
=> await _client.GetChildren(contentProviderId, fullName);
}

View File

@@ -7,6 +7,7 @@
</PropertyGroup>
<ItemGroup>
<ProjectReference Include="..\..\Core\FileTime.Core.Serialization\FileTime.Core.Serialization.csproj" />
<ProjectReference Include="..\FileTime.Server.Common.Abstractions\FileTime.Server.Common.Abstractions.csproj" />
</ItemGroup>

View File

@@ -1,17 +0,0 @@
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 AddServerCoreServices(this IServiceCollection services)
{
services.AddTransient<SignalRConnection>();
services.TryAddSingleton<IApplicationStopper, ApplicationStopper>();
services.AddSingleton<IContentAccessManager, ContentAccessManager>();
return services;
}
}

View File

@@ -0,0 +1,19 @@
<Project Sdk="Microsoft.NET.Sdk">
<PropertyGroup>
<TargetFramework>net7.0</TargetFramework>
<ImplicitUsings>enable</ImplicitUsings>
<Nullable>enable</Nullable>
<RootNamespace>FileTime.Server</RootNamespace>
</PropertyGroup>
<ItemGroup>
<PackageReference Include="Microsoft.Extensions.DependencyInjection.Abstractions" Version="7.0.0" />
</ItemGroup>
<ItemGroup>
<ProjectReference Include="..\FileTime.Server.Common\FileTime.Server.Common.csproj" />
<ProjectReference Include="..\FileTime.Server.Tracker\FileTime.Server.Tracker.csproj" />
</ItemGroup>
</Project>

View File

@@ -0,0 +1,25 @@
using FileTime.Server.Common;
using FileTime.Server.Common.Connections.SignalR;
using FileTime.Server.Common.ContentAccess;
using FileTime.Server.Common.ItemTracker;
using FileTime.Server.Tracker.ItemTracker;
using Microsoft.Extensions.DependencyInjection;
using Microsoft.Extensions.DependencyInjection.Extensions;
namespace FileTime.Server;
public static class Startup
{
public static IServiceCollection AddServerCoreServices(this IServiceCollection serviceCollection)
{
serviceCollection.AddTransient<SignalRConnection>();
serviceCollection.TryAddSingleton<IApplicationStopper, ApplicationStopper>();
serviceCollection.TryAddSingleton<IContentAccessManager, ContentAccessManager>();
return serviceCollection.AddRemoteTrackerServices();
}
private static IServiceCollection AddRemoteTrackerServices(this IServiceCollection serviceCollection)
{
serviceCollection.TryAddSingleton<IItemTrackerRegistry, ItemTrackerRegistry>();
return serviceCollection;
}
}

View File

@@ -0,0 +1,13 @@
<Project Sdk="Microsoft.NET.Sdk">
<PropertyGroup>
<TargetFramework>net7.0</TargetFramework>
<ImplicitUsings>enable</ImplicitUsings>
<Nullable>enable</Nullable>
</PropertyGroup>
<ItemGroup>
<ProjectReference Include="..\FileTime.Server.Common.Abstractions\FileTime.Server.Common.Abstractions.csproj" />
</ItemGroup>
</Project>

View File

@@ -0,0 +1,45 @@
using FileTime.Core.Models;
using FileTime.Server.Common.ItemTracker;
namespace FileTime.Server.Tracker.ItemTracker;
public class ItemTrackerRegistry : IItemTrackerRegistry
{
private readonly object _lock = new();
private readonly Dictionary<int, WeakReference<IItem>> _items = new();
private int _globalId = 1;
public event Action<int>? ItemRemoved;
public int Register(IItem item)
{
lock (_lock)
{
while (_items.ContainsKey(_globalId)) _globalId++;
_items[_globalId] = new WeakReference<IItem>(item);
return _globalId;
}
}
private void Clean()
{
lock (_lock)
{
var keys = _items.Keys.ToArray();
var keysToRemove = new List<int>();
foreach (var key in keys)
{
if (!_items[key].TryGetTarget(out _))
{
keysToRemove.Add(key);
}
}
foreach (var key in keysToRemove)
{
_items.Remove(key);
ItemRemoved?.Invoke(key);
}
}
}
}

View File

@@ -1,10 +1,14 @@
using FileTime.Core.ContentAccess;
using FileTime.Core.Enums;
using FileTime.Core.Models;
using FileTime.Core.Serialization;
using FileTime.Core.Timeline;
using FileTime.Server.Common;
using FileTime.Server.Common.Connections.SignalR;
using FileTime.Server.Common.ContentAccess;
using FileTime.Server.Common.ItemTracker;
using Microsoft.AspNetCore.SignalR;
using Microsoft.Extensions.DependencyInjection;
namespace FileTime.Server.Web;
@@ -14,20 +18,46 @@ public class ConnectionHub : Hub<ISignalRClient>, ISignalRHub
private readonly IContentAccessorFactory _contentAccessorFactory;
private readonly IApplicationStopper _applicationStopper;
private readonly IContentAccessManager _contentAccessManager;
private readonly ITimelessContentProvider _timelessContentProvider;
private readonly IItemTrackerRegistry _itemTrackerRegistry;
private readonly IServiceProvider _serviceProvider;
private readonly Dictionary<int, string> _trackedItemIds = new();
//TODO clean this sometimes
private readonly Dictionary<string, string> _clientIdToConnectionId = new();
public ConnectionHub(
IContentProviderRegistry contentProviderRegistry,
IContentAccessorFactory contentAccessorFactory,
IApplicationStopper applicationStopper,
IContentAccessManager contentAccessManager,
ITimelessContentProvider timelessContentProvider)
IItemTrackerRegistry itemTrackerRegistry,
IServiceProvider serviceProvider)
{
_contentProviderRegistry = contentProviderRegistry;
_contentAccessorFactory = contentAccessorFactory;
_applicationStopper = applicationStopper;
_contentAccessManager = contentAccessManager;
_timelessContentProvider = timelessContentProvider;
_itemTrackerRegistry = itemTrackerRegistry;
_serviceProvider = serviceProvider;
_itemTrackerRegistry.ItemRemoved += ItemTrackerRegistryOnItemRemoved;
}
private void ItemTrackerRegistryOnItemRemoved(int id)
{
if (_trackedItemIds.ContainsKey(id)) return;
var clientId = _trackedItemIds[id];
var connectionId = _clientIdToConnectionId[clientId];
Clients.Client(connectionId).RemoveTrackedItem(id);
}
public Task SetClientIdentifier(string providerName)
{
_clientIdToConnectionId[providerName] = Context.ConnectionId;
return Task.CompletedTask;
}
public Task Exit()
@@ -39,35 +69,35 @@ public class ConnectionHub : Hub<ISignalRClient>, ISignalRHub
public async Task CreateContainerAsync(string contentProviderId, string fullName)
{
//TODO handle no content provider with id
var contentProvider = _contentProviderRegistry.ContentProviders.First(p => p.Name == contentProviderId);
var contentProvider = GetContentProvider(contentProviderId);
var itemCreator = _contentAccessorFactory.GetItemCreator(contentProvider);
await itemCreator.CreateContainerAsync(contentProvider, new FullName(fullName));
}
public async Task CreateElementAsync(string contentProviderId, string fullName)
{
var contentProvider = _contentProviderRegistry.ContentProviders.First(p => p.Name == contentProviderId);
var contentProvider = GetContentProvider(contentProviderId);
var itemCreator = _contentAccessorFactory.GetItemCreator(contentProvider);
await itemCreator.CreateElementAsync(contentProvider, new FullName(fullName));
}
public async Task DeleteItemAsync(string contentProviderId, string fullName)
{
var contentProvider = _contentProviderRegistry.ContentProviders.First(p => p.Name == contentProviderId);
var contentProvider = GetContentProvider(contentProviderId);
var itemDeleter = _contentAccessorFactory.GetItemDeleter(contentProvider);
await itemDeleter.DeleteAsync(contentProvider, new FullName(fullName));
}
public async Task MoveItemAsync(string contentProviderId, string fullName, string newPath)
{
var contentProvider = _contentProviderRegistry.ContentProviders.First(p => p.Name == contentProviderId);
var contentProvider = GetContentProvider(contentProviderId);
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 contentProvider = GetContentProvider(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);
@@ -90,6 +120,55 @@ public class ConnectionHub : Hub<ISignalRClient>, ISignalRHub
return Task.CompletedTask;
}
public async Task<string?> GetNativePathAsync(string fullNamePath)
=> (await _timelessContentProvider.GetNativePathByFullNameAsync(new FullName(fullNamePath)))?.Path;
public async Task<string> GetNativePathAsync(string contentProviderId, string fullNamePath)
{
var contentProvider = GetContentProvider(contentProviderId);
return (await contentProvider.GetNativePathAsync(new FullName(fullNamePath))).Path;
}
public async Task<ISerialized> GetItemByNativePathAsync(
string contentProviderId,
NativePath nativePath,
PointInTime pointInTime,
bool forceResolve,
AbsolutePathType forceResolvePathType,
ItemInitializationSettings itemInitializationSettings)
{
var contentProvider = GetContentProvider(contentProviderId);
var item = await contentProvider.GetItemByNativePathAsync(
nativePath,
pointInTime,
forceResolve,
forceResolvePathType,
itemInitializationSettings
);
var id = _itemTrackerRegistry.Register(item);
var serializerType = typeof(ISerializer<>).MakeGenericType(item.GetType());
var serializer = (ISerializer) _serviceProvider.GetRequiredService(serializerType);
var serializedObject = await serializer.SerializeAsync(id, item);
return serializedObject;
}
public async Task<SerializedAbsolutePath[]> GetChildren(
string contentProviderId,
string fullName)
{
var contentProvider = GetContentProvider(contentProviderId);
var item = await contentProvider.GetItemByFullNameAsync(
new FullName(fullName),
PointInTime.Present);
if (item is IContainer container)
return container.Items.Select(AbsolutePathSerializer.Serialize).ToArray();
throw new NotSupportedException();
}
private IContentProvider GetContentProvider(string contentProviderId)
=> _contentProviderRegistry
.ContentProviders
.First(p => p.Name == contentProviderId);
}

View File

@@ -11,6 +11,7 @@
<ProjectReference Include="..\..\AppCommon\FileTime.App.DependencyInjection\FileTime.App.DependencyInjection.csproj" />
<ProjectReference Include="..\..\Providers\FileTime.Providers.Local\FileTime.Providers.Local.csproj" />
<ProjectReference Include="..\FileTime.Server.App\FileTime.Server.App.csproj" />
<ProjectReference Include="..\FileTime.Server.Extensions.DependencyInjection\FileTime.Server.Extensions.DependencyInjection.csproj" />
<ProjectReference Include="..\FileTime.Server.Web\FileTime.Server.Web.csproj" />
</ItemGroup>

View File

@@ -2,6 +2,7 @@
using Autofac.Extensions.DependencyInjection;
using FileTime.App.DependencyInjection;
using FileTime.Providers.Local;
using FileTime.Server;
using FileTime.Server.App;
using FileTime.Server.Common;
using Microsoft.Extensions.Configuration;