RemoteContentProvider get items WIP
This commit is contained in:
@@ -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>
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
@@ -0,0 +1,9 @@
|
||||
using FileTime.Core.Models;
|
||||
|
||||
namespace FileTime.Server.Common.ItemTracker;
|
||||
|
||||
public interface IItemTrackerRegistry
|
||||
{
|
||||
int Register(IItem item);
|
||||
event Action<int>? ItemRemoved;
|
||||
}
|
||||
@@ -2,5 +2,5 @@
|
||||
|
||||
public interface ISignalRClient
|
||||
{
|
||||
|
||||
Task RemoveTrackedItem(int itemId);
|
||||
}
|
||||
@@ -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);
|
||||
}
|
||||
@@ -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);
|
||||
}
|
||||
@@ -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>
|
||||
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
}
|
||||
@@ -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>
|
||||
@@ -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;
|
||||
}
|
||||
}
|
||||
@@ -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>
|
||||
@@ -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);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -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);
|
||||
}
|
||||
@@ -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>
|
||||
|
||||
|
||||
@@ -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;
|
||||
|
||||
Reference in New Issue
Block a user