diff --git a/src/AppCommon/FileTime.App.Core/Services/UserCommandHandler/ToolUserCommandHandlerService.cs b/src/AppCommon/FileTime.App.Core/Services/UserCommandHandler/ToolUserCommandHandlerService.cs index 64ae4ea..bdabdd9 100644 --- a/src/AppCommon/FileTime.App.Core/Services/UserCommandHandler/ToolUserCommandHandlerService.cs +++ b/src/AppCommon/FileTime.App.Core/Services/UserCommandHandler/ToolUserCommandHandlerService.cs @@ -28,6 +28,7 @@ public class ToolUserCommandHandlerService : UserCommandHandlerServiceBase private readonly IContainerSizeScanProvider _containerSizeScanProvider; private readonly IProgramsService _programsService; private readonly IContentProviderRegistry _contentProviderRegistry; + private readonly IServiceProvider _serviceProvider; private readonly ILogger _logger; private IDeclarativeProperty? _currentLocation; private IDeclarativeProperty? _currentSelectedItem; @@ -45,6 +46,7 @@ public class ToolUserCommandHandlerService : UserCommandHandlerServiceBase IContainerSizeScanProvider containerSizeScanProvider, IProgramsService programsService, IContentProviderRegistry contentProviderRegistry, + IServiceProvider serviceProvider, ILogger logger) : base(appState) { _systemClipboardService = systemClipboardService; @@ -57,6 +59,7 @@ public class ToolUserCommandHandlerService : UserCommandHandlerServiceBase _containerSizeScanProvider = containerSizeScanProvider; _programsService = programsService; _contentProviderRegistry = contentProviderRegistry; + _serviceProvider = serviceProvider; _logger = logger; SaveCurrentLocation(l => _currentLocation = l); SaveCurrentSelectedItem(i => _currentSelectedItem = i); @@ -92,7 +95,7 @@ public class ToolUserCommandHandlerService : UserCommandHandlerServiceBase Func>? connection = null; if (path.StartsWith("http")) { - connection = async () => await SignalRConnection.GetOrCreateForAsync(path); + connection = async () => await SignalRConnection.GetOrCreateForAsync(path, providerName.Value); } if (connection is null) @@ -109,15 +112,17 @@ public class ToolUserCommandHandlerService : UserCommandHandlerServiceBase var remoteContentProvider = new RemoteContentProvider( _timelessContentProvider, + _serviceProvider, connection, "local", providerName.Value); - + _contentProviderRegistry.AddContentProvider(remoteContentProvider); - - + await _userCommandHandlerService.HandleCommandAsync( new OpenContainerCommand(new AbsolutePath(_timelessContentProvider, remoteContentProvider))); + + await remoteContentProvider.InitializeChildren(); } private Task Edit() diff --git a/src/AppCommon/FileTime.App.Core/Startup.cs b/src/AppCommon/FileTime.App.Core/Startup.cs index de284d8..5cf3382 100644 --- a/src/AppCommon/FileTime.App.Core/Startup.cs +++ b/src/AppCommon/FileTime.App.Core/Startup.cs @@ -13,7 +13,7 @@ namespace FileTime.App.Core; public static class Startup { - public static IServiceCollection AddCoreAppServices(this IServiceCollection serviceCollection, IConfigurationRoot configuration) + public static IServiceCollection AddAppCoreDependencies(this IServiceCollection serviceCollection, IConfigurationRoot configuration) { serviceCollection.TryAddTransient(); serviceCollection.TryAddTransient(); diff --git a/src/AppCommon/FileTime.App.DependencyInjection/DependencyInjection.cs b/src/AppCommon/FileTime.App.DependencyInjection/DependencyInjection.cs index e7dc504..86d5107 100644 --- a/src/AppCommon/FileTime.App.DependencyInjection/DependencyInjection.cs +++ b/src/AppCommon/FileTime.App.DependencyInjection/DependencyInjection.cs @@ -2,14 +2,7 @@ using FileTime.App.Core; using FileTime.App.Core.Models; using FileTime.App.Core.Services; using FileTime.App.Core.Services.Persistence; -using FileTime.Core.Command; -using FileTime.Core.Command.CreateContainer; -using FileTime.Core.Command.CreateElement; -using FileTime.Core.Command.Delete; -using FileTime.Core.CommandHandlers; -using FileTime.Core.ContentAccess; -using FileTime.Core.Services; -using FileTime.Core.Timeline; +using FileTime.Core; using FileTime.Providers.Local; using FileTime.Providers.LocalAdmin; using FileTime.Providers.Remote; @@ -25,36 +18,16 @@ public static class DependencyInjection { serviceCollection ??= new ServiceCollection(); - serviceCollection.TryAddSingleton(); - serviceCollection.TryAddSingleton(); - serviceCollection.TryAddSingleton(); - serviceCollection.TryAddSingleton(); - serviceCollection.TryAddSingleton(); - serviceCollection.TryAddSingleton(); - //TODO: check local/remote context - serviceCollection.TryAddSingleton(); - serviceCollection.TryAddSingleton(); - serviceCollection.TryAddSingleton(); serviceCollection.TryAddSingleton(); - serviceCollection.TryAddTransient(); - serviceCollection.TryAddSingleton(); serviceCollection.AddSingleton(sp => sp.GetRequiredService()); serviceCollection.AddSingleton(sp => sp.GetRequiredService()); return serviceCollection - .AddCoreAppServices(configuration) + .AddCoreDependencies() + .AddAppCoreDependencies(configuration) .AddLocalProviderServices() .AddLocalAdminProviderServices(configuration) - .AddRemoteProviderServices() - .RegisterCommands() - .AddDefaultCommandHandlers(); + .AddRemoteProviderServices(); } - - private static IServiceCollection RegisterCommands(this IServiceCollection serviceCollection) - => serviceCollection - .AddCommands() - .AddTransient() - .AddTransient() - .AddTransient(); } \ No newline at end of file diff --git a/src/AppCommon/FileTime.App.DependencyInjection/FileTime.App.DependencyInjection.csproj b/src/AppCommon/FileTime.App.DependencyInjection/FileTime.App.DependencyInjection.csproj index 1596156..960bc6f 100644 --- a/src/AppCommon/FileTime.App.DependencyInjection/FileTime.App.DependencyInjection.csproj +++ b/src/AppCommon/FileTime.App.DependencyInjection/FileTime.App.DependencyInjection.csproj @@ -15,6 +15,8 @@ + + diff --git a/src/ConsoleApp/FileTime.ConsoleUI/DI.cs b/src/ConsoleApp/FileTime.ConsoleUI/DI.cs index 5904a0f..bb5ba21 100644 --- a/src/ConsoleApp/FileTime.ConsoleUI/DI.cs +++ b/src/ConsoleApp/FileTime.ConsoleUI/DI.cs @@ -6,7 +6,7 @@ using FileTime.App.Search; using FileTime.ConsoleUI.App; using FileTime.ConsoleUI.App.Services; using FileTime.Providers.Local; -using FileTime.Server.Common; +using FileTime.Server; using FileTime.Tools.Compression; using Microsoft.Extensions.Configuration; using Microsoft.Extensions.DependencyInjection; diff --git a/src/ConsoleApp/FileTime.ConsoleUI/FileTime.ConsoleUI.csproj b/src/ConsoleApp/FileTime.ConsoleUI/FileTime.ConsoleUI.csproj index be200b8..b8613f0 100644 --- a/src/ConsoleApp/FileTime.ConsoleUI/FileTime.ConsoleUI.csproj +++ b/src/ConsoleApp/FileTime.ConsoleUI/FileTime.ConsoleUI.csproj @@ -17,6 +17,7 @@ + diff --git a/src/Core/FileTime.Core.Abstraction/FileTime.Core.Abstraction.csproj b/src/Core/FileTime.Core.Abstraction/FileTime.Core.Abstraction.csproj index c81ed0c..3a280f0 100644 --- a/src/Core/FileTime.Core.Abstraction/FileTime.Core.Abstraction.csproj +++ b/src/Core/FileTime.Core.Abstraction/FileTime.Core.Abstraction.csproj @@ -25,4 +25,5 @@ + diff --git a/src/Core/FileTime.Core.Abstraction/Timeline/PointInTime.cs b/src/Core/FileTime.Core.Abstraction/Timeline/PointInTime.cs index 42a46ce..f301ae5 100644 --- a/src/Core/FileTime.Core.Abstraction/Timeline/PointInTime.cs +++ b/src/Core/FileTime.Core.Abstraction/Timeline/PointInTime.cs @@ -2,8 +2,8 @@ namespace FileTime.Core.Timeline; public class PointInTime { - public static readonly PointInTime Eternal = new PointInTime(); - public static readonly PointInTime Present = new PointInTime(); + public static readonly PointInTime Eternal = new(); + public static readonly PointInTime Present = new(); private readonly List _differences; @@ -46,5 +46,5 @@ public class PointInTime return merged; } - public static PointInTime CreateEmpty() => new PointInTime(); + public static PointInTime CreateEmpty() => new(); } \ No newline at end of file diff --git a/src/Core/FileTime.Core.Command/Startup.cs b/src/Core/FileTime.Core.Command/Startup.cs deleted file mode 100644 index 591d0e0..0000000 --- a/src/Core/FileTime.Core.Command/Startup.cs +++ /dev/null @@ -1,13 +0,0 @@ -using FileTime.Core.Command.Copy; -using FileTime.Core.Command.Move; -using Microsoft.Extensions.DependencyInjection; - -namespace FileTime.Core.Command; - -public static class Startup -{ - public static IServiceCollection AddCommands(this IServiceCollection serviceCollection) => - serviceCollection - .AddSingleton() - .AddSingleton(); -} \ No newline at end of file diff --git a/src/Core/FileTime.Core.CommandHandlers/Startup.cs b/src/Core/FileTime.Core.CommandHandlers/Startup.cs deleted file mode 100644 index a73e9a5..0000000 --- a/src/Core/FileTime.Core.CommandHandlers/Startup.cs +++ /dev/null @@ -1,13 +0,0 @@ -using FileTime.Core.Command; -using Microsoft.Extensions.DependencyInjection; - -namespace FileTime.Core.CommandHandlers; - -public static class Startup -{ - public static IServiceCollection AddDefaultCommandHandlers(this IServiceCollection serviceCollection) - { - return serviceCollection - .AddSingleton(); - } -} \ No newline at end of file diff --git a/src/Core/FileTime.Core.ContentAccess/ContentProviderRegistry.cs b/src/Core/FileTime.Core.ContentAccess/ContentProviderRegistry.cs index 6d02442..bf25557 100644 --- a/src/Core/FileTime.Core.ContentAccess/ContentProviderRegistry.cs +++ b/src/Core/FileTime.Core.ContentAccess/ContentProviderRegistry.cs @@ -28,6 +28,7 @@ public class ContentProviderRegistry : IContentProviderRegistry private void InitializeContentProviderListIfNeeded() { + if (_initialized) return; lock (_lock) { if (!_initialized) diff --git a/src/Core/FileTime.Core.Extensions.DependencyInjection/FileTime.Core.Extensions.DependencyInjection.csproj b/src/Core/FileTime.Core.Extensions.DependencyInjection/FileTime.Core.Extensions.DependencyInjection.csproj new file mode 100644 index 0000000..0473d75 --- /dev/null +++ b/src/Core/FileTime.Core.Extensions.DependencyInjection/FileTime.Core.Extensions.DependencyInjection.csproj @@ -0,0 +1,24 @@ + + + + net7.0 + enable + enable + FileTime.Core + + + + + + + + + + + + + + + + + diff --git a/src/Core/FileTime.Core.Extensions.DependencyInjection/Startup.cs b/src/Core/FileTime.Core.Extensions.DependencyInjection/Startup.cs new file mode 100644 index 0000000..fbd1002 --- /dev/null +++ b/src/Core/FileTime.Core.Extensions.DependencyInjection/Startup.cs @@ -0,0 +1,88 @@ +using FileTime.Core.Command; +using FileTime.Core.Command.Copy; +using FileTime.Core.Command.CreateContainer; +using FileTime.Core.Command.CreateElement; +using FileTime.Core.Command.Delete; +using FileTime.Core.Command.Move; +using FileTime.Core.CommandHandlers; +using FileTime.Core.ContentAccess; +using FileTime.Core.Models; +using FileTime.Core.Serialization; +using FileTime.Core.Serialization.Container; +using FileTime.Core.Services; +using FileTime.Core.Timeline; +using Microsoft.Extensions.DependencyInjection; +using Microsoft.Extensions.DependencyInjection.Extensions; + +namespace FileTime.Core; + +public static class Startup +{ + public static IServiceCollection AddCoreDependencies(this IServiceCollection serviceCollection) + => serviceCollection + .AddCoreServices() + .AddTimelineServices() + .AddDefaultCommandHandlers() + .AddCommands() + .AddCommandFactories() + .AddCommandServices() + .AddContentAccessServices() + .AddSerialization(); + + private static IServiceCollection AddContentAccessServices(this IServiceCollection serviceCollection) + { + serviceCollection.TryAddSingleton(); + serviceCollection.TryAddSingleton(); + serviceCollection.TryAddSingleton(); + + return serviceCollection; + } + + private static IServiceCollection AddCommandServices(this IServiceCollection serviceCollection) + { + serviceCollection.TryAddSingleton(); + + return serviceCollection; + } + + private static IServiceCollection AddCoreServices(this IServiceCollection serviceCollection) + { + serviceCollection.TryAddTransient(); + serviceCollection.TryAddSingleton(); + + return serviceCollection; + } + + private static IServiceCollection AddTimelineServices(this IServiceCollection serviceCollection) + { + serviceCollection.TryAddSingleton(); + serviceCollection.TryAddSingleton(); + //TODO: check local/remote context + serviceCollection.TryAddSingleton(); + serviceCollection.TryAddSingleton(); + + return serviceCollection; + } + + private static IServiceCollection AddCommands(this IServiceCollection serviceCollection) + => serviceCollection + .AddTransient() + .AddTransient() + .AddTransient(); + + private static IServiceCollection AddCommandFactories(this IServiceCollection serviceCollection) => + serviceCollection + .AddSingleton() + .AddSingleton(); + + + private static IServiceCollection AddSerialization(this IServiceCollection serviceCollection) + { + serviceCollection.TryAddSingleton, ContainerSerializer>(); + return serviceCollection; + } + + private static IServiceCollection AddDefaultCommandHandlers(this IServiceCollection serviceCollection) + => serviceCollection + .AddSingleton(); +} \ No newline at end of file diff --git a/src/Core/FileTime.Core.Serialization/AbsolutePathSerializer.cs b/src/Core/FileTime.Core.Serialization/AbsolutePathSerializer.cs new file mode 100644 index 0000000..7294746 --- /dev/null +++ b/src/Core/FileTime.Core.Serialization/AbsolutePathSerializer.cs @@ -0,0 +1,14 @@ +using FileTime.Core.Models; + +namespace FileTime.Core.Serialization; + +public class AbsolutePathSerializer +{ + public static SerializedAbsolutePath Serialize(AbsolutePath absolutePath) + => new() + { + PointInTime = absolutePath.PointInTime, + Path = absolutePath.Path.Path, + Type = absolutePath.Type + }; +} \ No newline at end of file diff --git a/src/Core/FileTime.Core.Serialization/Container/ContainerDeserializationContext.cs b/src/Core/FileTime.Core.Serialization/Container/ContainerDeserializationContext.cs new file mode 100644 index 0000000..6c13f0a --- /dev/null +++ b/src/Core/FileTime.Core.Serialization/Container/ContainerDeserializationContext.cs @@ -0,0 +1,5 @@ +using FileTime.Core.ContentAccess; + +namespace FileTime.Core.Serialization.Container; + +public record ContainerDeserializationContext(IContentProvider ContentProvider); \ No newline at end of file diff --git a/src/Core/FileTime.Core.Serialization/Container/ContainerDeserializationResult.cs b/src/Core/FileTime.Core.Serialization/Container/ContainerDeserializationResult.cs new file mode 100644 index 0000000..6f6d61d --- /dev/null +++ b/src/Core/FileTime.Core.Serialization/Container/ContainerDeserializationResult.cs @@ -0,0 +1,10 @@ +using System.Collections.ObjectModel; +using FileTime.Core.Models; + +namespace FileTime.Core.Serialization.Container; + +public record ContainerDeserializationResult( + Models.Container Container, + ObservableCollection Exceptions, + ExtensionCollection Extensions, + ObservableCollection Items); \ No newline at end of file diff --git a/src/Core/FileTime.Core.Serialization/Container/ContainerDeserializer.cs b/src/Core/FileTime.Core.Serialization/Container/ContainerDeserializer.cs new file mode 100644 index 0000000..6af35c4 --- /dev/null +++ b/src/Core/FileTime.Core.Serialization/Container/ContainerDeserializer.cs @@ -0,0 +1,67 @@ +using System.Collections.ObjectModel; +using FileTime.Core.Enums; +using FileTime.Core.Models; +using FileTime.Core.Timeline; + +namespace FileTime.Core.Serialization.Container; + +public class ContainerDeserializer +{ + private readonly ITimelessContentProvider _timelessContentProvider; + + public ContainerDeserializer(ITimelessContentProvider timelessContentProvider) + { + _timelessContentProvider = timelessContentProvider; + } + + public ContainerDeserializationResult Deserialize( + SerializedContainer source, + ContainerDeserializationContext context) + { + ObservableCollection exceptions = new(); + ExtensionCollection extensions = new(); + ObservableCollection items = new(); + + var mappedItems = source.Items + .Select(x => new AbsolutePath( + _timelessContentProvider, + x.PointInTime, + new FullName(x.Path), + x.Type + ) + ); + + foreach (var item in + mappedItems) + { + items.Add(item); + } + + var container = new Models.Container( + source.Name, + source.DisplayName, + new FullName(source.FullName), + new NativePath(source.NativePath), + new AbsolutePath(_timelessContentProvider, PointInTime.Present, new FullName(source.Parent), AbsolutePathType.Container), + source.IsHidden, + source.IsExists, + source.CreatedAt, + source.ModifiedAt, + source.CanDelete, + source.CanRename, + source.Attributes, + context.ContentProvider, + source.AllowRecursiveDeletion, + PointInTime.Present, + exceptions, + new ReadOnlyExtensionCollection(extensions), + items + ); + + return new ContainerDeserializationResult( + container, + exceptions, + extensions, + items); + } +} \ No newline at end of file diff --git a/src/Core/FileTime.Core.Serialization/Container/ContainerSerializer.cs b/src/Core/FileTime.Core.Serialization/Container/ContainerSerializer.cs new file mode 100644 index 0000000..29e4f53 --- /dev/null +++ b/src/Core/FileTime.Core.Serialization/Container/ContainerSerializer.cs @@ -0,0 +1,32 @@ +using FileTime.Core.Models; + +namespace FileTime.Core.Serialization.Container; + +public class ContainerSerializer : ISerializer +{ + Task ISerializer.SerializeAsync(int id, IContainer item) => Task.FromResult(Serialize(id, item)); + + private ISerialized Serialize(int id, IContainer container) + { + var items = container.Items.Select(AbsolutePathSerializer.Serialize).ToArray(); + var serialized = new SerializedContainer + { + Id = id, + Name = container.Name, + DisplayName = container.DisplayName, + FullName = container.FullName!.Path, + NativePath = container.NativePath!.Path, + Parent = container.Parent!.Path.Path, + IsHidden = container.IsHidden, + IsExists = container.IsExists, + CreatedAt = container.CreatedAt, + ModifiedAt = container.ModifiedAt, + CanDelete = container.CanDelete, + CanRename = container.CanRename, + Attributes = container.Attributes, + AllowRecursiveDeletion = container.AllowRecursiveDeletion, + Items = items + }; + return serialized; + } +} \ No newline at end of file diff --git a/src/Core/FileTime.Core.Serialization/Container/SerializedContainer.cs b/src/Core/FileTime.Core.Serialization/Container/SerializedContainer.cs new file mode 100644 index 0000000..09342ec --- /dev/null +++ b/src/Core/FileTime.Core.Serialization/Container/SerializedContainer.cs @@ -0,0 +1,29 @@ +using System.Runtime.Serialization; +using FileTime.Core.Enums; +using MessagePack; + +namespace FileTime.Core.Serialization.Container; + +[DataContract] +[MessagePackObject] +public class SerializedContainer : ISerialized +{ + [Key(0)] [DataMember(Order = 0)] public required int Id { get; set; } + [Key(1)] [DataMember(Order = 1)] public required string Name { get; set; } + + [Key(2)] [DataMember(Order = 2)] public required string DisplayName { get; set; } + + [Key(3)] [DataMember(Order = 3)] public required string FullName { get; set; } + + [Key(4)] [DataMember(Order = 4)] public required string NativePath { get; set; } + [Key(5)] [DataMember(Order = 5)] public required string Parent { get; set; } + [Key(6)] [DataMember(Order = 6)] public required bool IsHidden { get; set; } + [Key(7)] [DataMember(Order = 7)] public required bool IsExists { get; set; } + [Key(8)] [DataMember(Order = 8)] public required DateTime? CreatedAt { get; set; } + [Key(9)] [DataMember(Order = 9)] public required DateTime? ModifiedAt { get; set; } + [Key(10)] [DataMember(Order = 10)] public required SupportsDelete CanDelete { get; set; } + [Key(11)] [DataMember(Order = 11)] public required bool CanRename { get; set; } + [Key(12)] [DataMember(Order = 12)] public required string? Attributes { get; set; } + [Key(13)] [DataMember(Order = 13)] public required bool AllowRecursiveDeletion { get; set; } + [Key(14)] [DataMember(Order = 14)] public required SerializedAbsolutePath[] Items { get; set; } +} \ No newline at end of file diff --git a/src/Core/FileTime.Core.Serialization/FileTime.Core.Serialization.csproj b/src/Core/FileTime.Core.Serialization/FileTime.Core.Serialization.csproj new file mode 100644 index 0000000..4ec8fc5 --- /dev/null +++ b/src/Core/FileTime.Core.Serialization/FileTime.Core.Serialization.csproj @@ -0,0 +1,24 @@ + + + + net7.0 + enable + enable + + + + + + + + + + + all + runtime; build; native; contentfiles; analyzers; buildtransitive + + + + + + diff --git a/src/Core/FileTime.Core.Serialization/ISerialized.cs b/src/Core/FileTime.Core.Serialization/ISerialized.cs new file mode 100644 index 0000000..aee530f --- /dev/null +++ b/src/Core/FileTime.Core.Serialization/ISerialized.cs @@ -0,0 +1,9 @@ +using FileTime.Core.Serialization.Container; + +namespace FileTime.Core.Serialization; + +[MessagePack.Union(0, typeof(SerializedContainer))] +public interface ISerialized +{ + int Id { get; } +} \ No newline at end of file diff --git a/src/Core/FileTime.Core.Serialization/ISerializer.cs b/src/Core/FileTime.Core.Serialization/ISerializer.cs new file mode 100644 index 0000000..47e96c0 --- /dev/null +++ b/src/Core/FileTime.Core.Serialization/ISerializer.cs @@ -0,0 +1,14 @@ +using FileTime.Core.Models; + +namespace FileTime.Core.Serialization; + +public interface ISerializer +{ + Task SerializeAsync(int id, object item); +} + +public interface ISerializer where T : IItem +{ + async Task SerializeAsync(int id, object item) => await SerializeAsync(id, (T) item); + Task SerializeAsync(int id, T item); +} \ No newline at end of file diff --git a/src/Core/FileTime.Core.Serialization/SerializedAbsolutePath.cs b/src/Core/FileTime.Core.Serialization/SerializedAbsolutePath.cs new file mode 100644 index 0000000..f1f6ea0 --- /dev/null +++ b/src/Core/FileTime.Core.Serialization/SerializedAbsolutePath.cs @@ -0,0 +1,15 @@ +using System.Runtime.Serialization; +using FileTime.Core.Enums; +using FileTime.Core.Timeline; +using MessagePack; + +namespace FileTime.Core.Serialization; + +[MessagePackObject] +[DataContract] +public class SerializedAbsolutePath +{ + [Key(0)] [DataMember(Order = 0)] public required PointInTime PointInTime { get; set; } + [Key(1)] [DataMember(Order = 1)] public required string Path { get; set; } + [Key(2)] [DataMember(Order = 2)] public required AbsolutePathType Type { get; set; } +} \ No newline at end of file diff --git a/src/Core/FileTime.Core.Serialization/Timeline/PointInTimeSerializer.cs b/src/Core/FileTime.Core.Serialization/Timeline/PointInTimeSerializer.cs new file mode 100644 index 0000000..be73e2c --- /dev/null +++ b/src/Core/FileTime.Core.Serialization/Timeline/PointInTimeSerializer.cs @@ -0,0 +1,11 @@ +using FileTime.Core.Timeline; + +namespace FileTime.Core.Serialization.Timeline; + +public class PointInTimeSerializer +{ + public static SerializedPointInTime Serialize(PointInTime pointInTime) + { + return new(); + } +} \ No newline at end of file diff --git a/src/Core/FileTime.Core.Serialization/Timeline/SerializedDifference.cs b/src/Core/FileTime.Core.Serialization/Timeline/SerializedDifference.cs new file mode 100644 index 0000000..80088e8 --- /dev/null +++ b/src/Core/FileTime.Core.Serialization/Timeline/SerializedDifference.cs @@ -0,0 +1,9 @@ +using FileTime.Core.Timeline; + +namespace FileTime.Core.Serialization.Timeline; + +public class SerializedDifference +{ + public required SerializedAbsolutePath AbsolutePath { get; set; } + public required DifferenceActionType Action { get; set; } +} \ No newline at end of file diff --git a/src/Core/FileTime.Core.Serialization/Timeline/SerializedPointInTime.cs b/src/Core/FileTime.Core.Serialization/Timeline/SerializedPointInTime.cs new file mode 100644 index 0000000..71c7a7e --- /dev/null +++ b/src/Core/FileTime.Core.Serialization/Timeline/SerializedPointInTime.cs @@ -0,0 +1,6 @@ +namespace FileTime.Core.Serialization.Timeline; + +public class SerializedPointInTime +{ + +} \ No newline at end of file diff --git a/src/Core/FileTime.Core.Services/Tab.cs b/src/Core/FileTime.Core.Services/Tab.cs index 5728d1c..7e9b5b4 100644 --- a/src/Core/FileTime.Core.Services/Tab.cs +++ b/src/Core/FileTime.Core.Services/Tab.cs @@ -188,7 +188,17 @@ public class Tab : ITab private static IItem MapItem(AbsolutePath item) { - var t = Task.Run(async () => await item.ResolveAsync(true)); + var t = Task.Run(async () => + { + try + { + return await item.ResolveAsync(true); + } + catch + { + return null; + } + }); t.Wait(); return t.Result; } diff --git a/src/FileTime.sln b/src/FileTime.sln index f400b77..fc83526 100644 --- a/src/FileTime.sln +++ b/src/FileTime.sln @@ -139,6 +139,14 @@ Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "FileTime.Providers.Smb", "P EndProject Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "FileTime.Providers.Smb.Abstractions", "Providers\FileTime.Providers.Smb.Abstractions\FileTime.Providers.Smb.Abstractions.csproj", "{595F736D-4711-4AD3-8D4F-13DD85FB35E7}" EndProject +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "FileTime.Core.Serialization", "Core\FileTime.Core.Serialization\FileTime.Core.Serialization.csproj", "{0AAC0BDF-35F2-434C-882E-047415D55682}" +EndProject +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "FileTime.Core.Extensions.DependencyInjection", "Core\FileTime.Core.Extensions.DependencyInjection\FileTime.Core.Extensions.DependencyInjection.csproj", "{0A29616B-5413-4DA9-96EC-B45D6AF632C9}" +EndProject +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "FileTime.Server.Tracker", "Server\FileTime.Server.Tracker\FileTime.Server.Tracker.csproj", "{BD382403-86D8-4E99-B279-370FC22F059C}" +EndProject +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "FileTime.Server.Extensions.DependencyInjection", "Server\FileTime.Server.Extensions.DependencyInjection\FileTime.Server.Extensions.DependencyInjection.csproj", "{25AA9F04-EEEE-49C4-870B-CDFF71717687}" +EndProject Global GlobalSection(SolutionConfigurationPlatforms) = preSolution Debug|Any CPU = Debug|Any CPU @@ -381,6 +389,22 @@ Global {595F736D-4711-4AD3-8D4F-13DD85FB35E7}.Debug|Any CPU.Build.0 = Debug|Any CPU {595F736D-4711-4AD3-8D4F-13DD85FB35E7}.Release|Any CPU.ActiveCfg = Release|Any CPU {595F736D-4711-4AD3-8D4F-13DD85FB35E7}.Release|Any CPU.Build.0 = Release|Any CPU + {0AAC0BDF-35F2-434C-882E-047415D55682}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {0AAC0BDF-35F2-434C-882E-047415D55682}.Debug|Any CPU.Build.0 = Debug|Any CPU + {0AAC0BDF-35F2-434C-882E-047415D55682}.Release|Any CPU.ActiveCfg = Release|Any CPU + {0AAC0BDF-35F2-434C-882E-047415D55682}.Release|Any CPU.Build.0 = Release|Any CPU + {0A29616B-5413-4DA9-96EC-B45D6AF632C9}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {0A29616B-5413-4DA9-96EC-B45D6AF632C9}.Debug|Any CPU.Build.0 = Debug|Any CPU + {0A29616B-5413-4DA9-96EC-B45D6AF632C9}.Release|Any CPU.ActiveCfg = Release|Any CPU + {0A29616B-5413-4DA9-96EC-B45D6AF632C9}.Release|Any CPU.Build.0 = Release|Any CPU + {BD382403-86D8-4E99-B279-370FC22F059C}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {BD382403-86D8-4E99-B279-370FC22F059C}.Debug|Any CPU.Build.0 = Debug|Any CPU + {BD382403-86D8-4E99-B279-370FC22F059C}.Release|Any CPU.ActiveCfg = Release|Any CPU + {BD382403-86D8-4E99-B279-370FC22F059C}.Release|Any CPU.Build.0 = Release|Any CPU + {25AA9F04-EEEE-49C4-870B-CDFF71717687}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {25AA9F04-EEEE-49C4-870B-CDFF71717687}.Debug|Any CPU.Build.0 = Debug|Any CPU + {25AA9F04-EEEE-49C4-870B-CDFF71717687}.Release|Any CPU.ActiveCfg = Release|Any CPU + {25AA9F04-EEEE-49C4-870B-CDFF71717687}.Release|Any CPU.Build.0 = Release|Any CPU EndGlobalSection GlobalSection(SolutionProperties) = preSolution HideSolutionNode = FALSE @@ -446,6 +470,10 @@ Global {CA48A181-256E-4546-B164-6768B240BFFC} = {2FC40FE1-4446-44AB-BF77-00F94D995FA3} {CC8AA3FC-7A5B-4558-AB35-DFF59F2AEF47} = {2FC40FE1-4446-44AB-BF77-00F94D995FA3} {595F736D-4711-4AD3-8D4F-13DD85FB35E7} = {2FC40FE1-4446-44AB-BF77-00F94D995FA3} + {0AAC0BDF-35F2-434C-882E-047415D55682} = {3324D046-1E05-46B5-B1BA-82910D56B332} + {0A29616B-5413-4DA9-96EC-B45D6AF632C9} = {3324D046-1E05-46B5-B1BA-82910D56B332} + {BD382403-86D8-4E99-B279-370FC22F059C} = {778AAF38-20FF-438C-A9C3-60850C8B5A27} + {25AA9F04-EEEE-49C4-870B-CDFF71717687} = {778AAF38-20FF-438C-A9C3-60850C8B5A27} EndGlobalSection GlobalSection(ExtensibilityGlobals) = postSolution SolutionGuid = {859FB3DF-C60A-46B1-82E5-90274905D1EF} diff --git a/src/GuiApp/Avalonia/FileTime.GuiApp/App.axaml.cs b/src/GuiApp/Avalonia/FileTime.GuiApp/App.axaml.cs index 7d6fb00..ba4f6d1 100644 --- a/src/GuiApp/Avalonia/FileTime.GuiApp/App.axaml.cs +++ b/src/GuiApp/Avalonia/FileTime.GuiApp/App.axaml.cs @@ -9,6 +9,7 @@ using FileTime.GuiApp.App; using FileTime.GuiApp.App.Font; using FileTime.GuiApp.App.ViewModels; using FileTime.GuiApp.App.Views; +using FileTime.Server; using FileTime.Server.Common; using FileTime.Tools.Compression; using Microsoft.Extensions.DependencyInjection; @@ -31,7 +32,7 @@ public class Application : Avalonia.Application .AddCompression() .ConfigureFont(configuration) .RegisterLogging() - .RegisterServices() + .RegisterGuiServices() .AddSettings() .AddViewModels() .BuildServiceProvider(); diff --git a/src/GuiApp/Avalonia/FileTime.GuiApp/FileTime.GuiApp.csproj b/src/GuiApp/Avalonia/FileTime.GuiApp/FileTime.GuiApp.csproj index d30517d..28df0d2 100644 --- a/src/GuiApp/Avalonia/FileTime.GuiApp/FileTime.GuiApp.csproj +++ b/src/GuiApp/Avalonia/FileTime.GuiApp/FileTime.GuiApp.csproj @@ -46,6 +46,7 @@ + diff --git a/src/GuiApp/Avalonia/FileTime.GuiApp/Startup.cs b/src/GuiApp/Avalonia/FileTime.GuiApp/Startup.cs index 81853c5..10393f7 100644 --- a/src/GuiApp/Avalonia/FileTime.GuiApp/Startup.cs +++ b/src/GuiApp/Avalonia/FileTime.GuiApp/Startup.cs @@ -55,7 +55,7 @@ public static class Startup return serviceCollection; } - internal static IServiceCollection RegisterServices(this IServiceCollection serviceCollection) + internal static IServiceCollection RegisterGuiServices(this IServiceCollection serviceCollection) { serviceCollection.TryAddSingleton(); serviceCollection.TryAddSingleton(); diff --git a/src/Providers/FileTime.Providers.LocalAdmin/AdminElevationManager.cs b/src/Providers/FileTime.Providers.LocalAdmin/AdminElevationManager.cs index a3a4e3e..a3f83d0 100644 --- a/src/Providers/FileTime.Providers.LocalAdmin/AdminElevationManager.cs +++ b/src/Providers/FileTime.Providers.LocalAdmin/AdminElevationManager.cs @@ -14,6 +14,7 @@ namespace FileTime.Providers.LocalAdmin; public class AdminElevationManager : IAdminElevationManager, INotifyPropertyChanged, IExitHandler { + private const string AdminContentProviderName = "localAdminRemote"; private class ConnectionInfo { public string? SignalRBaseUrl { get; init; } @@ -144,9 +145,10 @@ public class AdminElevationManager : IAdminElevationManager, INotifyPropertyChan _remoteContentProvider = new RemoteContentProvider( _timelessContentProvider, - async () => await SignalRConnection.GetOrCreateForAsync(_connectionInfo.SignalRBaseUrl), + _serviceProvider, + async () => await SignalRConnection.GetOrCreateForAsync(_connectionInfo.SignalRBaseUrl, AdminContentProviderName), "local", - "localAdminRemote" + AdminContentProviderName ); return Task.FromResult((IRemoteContentProvider)_remoteContentProvider); diff --git a/src/Providers/FileTime.Providers.Remote.Abstractions/IRemoteContentProvider.cs b/src/Providers/FileTime.Providers.Remote.Abstractions/IRemoteContentProvider.cs index 82321b5..2f3b9dc 100644 --- a/src/Providers/FileTime.Providers.Remote.Abstractions/IRemoteContentProvider.cs +++ b/src/Providers/FileTime.Providers.Remote.Abstractions/IRemoteContentProvider.cs @@ -7,4 +7,5 @@ public interface IRemoteContentProvider : IContentProvider { Task GetRemoteConnectionAsync(); string RemoteProviderName { get; } + Task InitializeChildren(); } \ No newline at end of file diff --git a/src/Providers/FileTime.Providers.Remote/RemoteContentProvider.cs b/src/Providers/FileTime.Providers.Remote/RemoteContentProvider.cs index 83fd69f..9e70510 100644 --- a/src/Providers/FileTime.Providers.Remote/RemoteContentProvider.cs +++ b/src/Providers/FileTime.Providers.Remote/RemoteContentProvider.cs @@ -1,47 +1,101 @@ using FileTime.Core.ContentAccess; using FileTime.Core.Enums; using FileTime.Core.Models; +using FileTime.Core.Serialization.Container; using FileTime.Core.Timeline; using FileTime.Server.Common; +using Microsoft.Extensions.DependencyInjection; namespace FileTime.Providers.Remote; public sealed class RemoteContentProvider : ContentProviderBase, IRemoteContentProvider { - public string RemoteProviderName { get; } + private readonly IServiceProvider _serviceProvider; private readonly Func> _remoteConnectionProvider; + private readonly SemaphoreSlim _initializeSemaphore = new(1, 1); + private bool _initialized; + + public string RemoteProviderName { get; } public RemoteContentProvider( ITimelessContentProvider timelessContentProvider, + IServiceProvider serviceProvider, Func> remoteConnectionProvider, string remoteName, string name) : base(name, timelessContentProvider) { RemoteProviderName = remoteName; + _serviceProvider = serviceProvider; _remoteConnectionProvider = remoteConnectionProvider; } public async Task GetRemoteConnectionAsync() => await _remoteConnectionProvider(); + public async Task InitializeChildren() + { + await _initializeSemaphore.WaitAsync(); + try + { + if (_initialized) return; + + //TODO: loading indicator + + var connection = await GetRemoteConnectionAsync(); + var children = await connection.GetChildren(RemoteProviderName, RemoteProviderName); + _initialized = true; + } + finally + { + _initializeSemaphore.Release(); + } + } + //TODO implement - public override Task GetItemByNativePathAsync( + public override async Task GetItemByNativePathAsync( NativePath nativePath, PointInTime pointInTime, bool forceResolve = false, AbsolutePathType forceResolvePathType = AbsolutePathType.Unknown, - ItemInitializationSettings itemInitializationSettings = default) => - throw new NotImplementedException(); + ItemInitializationSettings itemInitializationSettings = default) + { + if (nativePath.Path == string.Empty) + { + return this; + } + + var connection = await GetRemoteConnectionAsync(); + var serialized = await connection.GetItemByNativePathAsync( + RemoteProviderName, + nativePath, + pointInTime, + forceResolve, + forceResolvePathType, + itemInitializationSettings + ); + + if (serialized is SerializedContainer serializedContainer) + { + var containerDeserializer = _serviceProvider.GetRequiredService(); + var container = containerDeserializer.Deserialize( + serializedContainer, + new ContainerDeserializationContext(this) + ); + + return container.Container; + } + + throw new NotSupportedException(); + } - //TODO: make it async public override async ValueTask GetNativePathAsync(FullName fullName) { var remoteFullname = new FullName(ConvertLocalFullNameToRemote(fullName)); var connection = await GetRemoteConnectionAsync(); - var remoteNativePath = await connection.GetNativePathAsync(remoteFullname); - return new NativePath(remoteNativePath!.Path); + var remoteNativePath = await connection.GetNativePathAsync(RemoteProviderName, remoteFullname); + return new NativePath(remoteNativePath.Path); } public override FullName GetFullName(NativePath nativePath) => throw new NotImplementedException(); 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 31696f6..d7a5c9f 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 @@ -9,6 +9,7 @@ + diff --git a/src/Server/FileTime.Server.Common.Abstractions/IRemoteConnection.cs b/src/Server/FileTime.Server.Common.Abstractions/IRemoteConnection.cs index 19a6dd5..d73b154 100644 --- a/src/Server/FileTime.Server.Common.Abstractions/IRemoteConnection.cs +++ b/src/Server/FileTime.Server.Common.Abstractions/IRemoteConnection.cs @@ -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 GetNativePathAsync(FullName fullName); + Task GetNativePathAsync(string contentProviderId, FullName fullName); + + Task GetItemByNativePathAsync( + string contentProviderId, + NativePath nativePath, + PointInTime pointInTime, + bool forceResolve, + AbsolutePathType forceResolvePathType, + ItemInitializationSettings itemInitializationSettings); + + Task GetChildren( + string contentProviderId, + string fullName); } \ No newline at end of file diff --git a/src/Server/FileTime.Server.Common.Abstractions/ItemTracker/IItemTrackerRegistry.cs b/src/Server/FileTime.Server.Common.Abstractions/ItemTracker/IItemTrackerRegistry.cs new file mode 100644 index 0000000..3e59a1b --- /dev/null +++ b/src/Server/FileTime.Server.Common.Abstractions/ItemTracker/IItemTrackerRegistry.cs @@ -0,0 +1,9 @@ +using FileTime.Core.Models; + +namespace FileTime.Server.Common.ItemTracker; + +public interface IItemTrackerRegistry +{ + int Register(IItem item); + event Action? ItemRemoved; +} \ No newline at end of file diff --git a/src/Server/FileTime.Server.Common/Connections/SignalR/ISignalRClient.cs b/src/Server/FileTime.Server.Common/Connections/SignalR/ISignalRClient.cs index bd55598..4541bec 100644 --- a/src/Server/FileTime.Server.Common/Connections/SignalR/ISignalRClient.cs +++ b/src/Server/FileTime.Server.Common/Connections/SignalR/ISignalRClient.cs @@ -2,5 +2,5 @@ public interface ISignalRClient { - + Task RemoveTrackedItem(int itemId); } \ 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 ac89f85..4e8fb0e 100644 --- a/src/Server/FileTime.Server.Common/Connections/SignalR/ISignalRHub.cs +++ b/src/Server/FileTime.Server.Common/Connections/SignalR/ISignalRHub.cs @@ -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 GetNativePathAsync(string fullNamePath); + Task GetNativePathAsync(string contentProviderId, string fullNamePath); + Task GetItemByNativePathAsync( + string contentProviderId, + NativePath nativePath, + PointInTime pointInTime, + bool forceResolve, + AbsolutePathType forceResolvePathType, + ItemInitializationSettings itemInitializationSettings); + + Task GetChildren( + string contentProviderId, + string fullName); } \ 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 57f0a04..a0ba9a2 100644 --- a/src/Server/FileTime.Server.Common/Connections/SignalR/SignalRConnection.cs +++ b/src/Server/FileTime.Server.Common/Connections/SignalR/SignalRConnection.cs @@ -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 +public class SignalRConnection : IRemoteConnection, IAsyncInitable { private static readonly Dictionary Connections = new(); private static readonly object ConnectionsLock = new(); @@ -15,7 +18,7 @@ public class SignalRConnection : IRemoteConnection, IAsyncInitable 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 _connection = connectionBuilder.Build(); await _connection.StartAsync(); _client = _connection.CreateHubProxy(); + await _client.SetClientIdentifier(providerName); } - public static async Task GetOrCreateForAsync(string baseUrl) + public static async Task GetOrCreateForAsync(string baseUrl, string providerName) { SignalRConnection? connection; lock (ConnectionsLock) @@ -47,7 +51,7 @@ public class SignalRConnection : IRemoteConnection, IAsyncInitable Connections.Add(baseUrl, connection); } - await connection.InitAsync(baseUrl); + await connection.InitAsync(baseUrl, providerName); return connection; } @@ -78,9 +82,32 @@ public class SignalRConnection : IRemoteConnection, IAsyncInitable public async Task CloseWriterAsync(string transactionId) => await _client.CloseWriterAsync(transactionId); - public async Task GetNativePathAsync(FullName fullName) + public async Task 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 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 GetChildren(string contentProviderId, string fullName) + => await _client.GetChildren(contentProviderId, fullName); } \ 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 16f5fe5..2e942dc 100644 --- a/src/Server/FileTime.Server.Common/FileTime.Server.Common.csproj +++ b/src/Server/FileTime.Server.Common/FileTime.Server.Common.csproj @@ -7,6 +7,7 @@ + diff --git a/src/Server/FileTime.Server.Common/Startup.cs b/src/Server/FileTime.Server.Common/Startup.cs deleted file mode 100644 index c419b2f..0000000 --- a/src/Server/FileTime.Server.Common/Startup.cs +++ /dev/null @@ -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(); - services.TryAddSingleton(); - services.AddSingleton(); - return services; - } -} \ No newline at end of file diff --git a/src/Server/FileTime.Server.Extensions.DependencyInjection/FileTime.Server.Extensions.DependencyInjection.csproj b/src/Server/FileTime.Server.Extensions.DependencyInjection/FileTime.Server.Extensions.DependencyInjection.csproj new file mode 100644 index 0000000..fc2a746 --- /dev/null +++ b/src/Server/FileTime.Server.Extensions.DependencyInjection/FileTime.Server.Extensions.DependencyInjection.csproj @@ -0,0 +1,19 @@ + + + + net7.0 + enable + enable + FileTime.Server + + + + + + + + + + + + diff --git a/src/Server/FileTime.Server.Extensions.DependencyInjection/Startup.cs b/src/Server/FileTime.Server.Extensions.DependencyInjection/Startup.cs new file mode 100644 index 0000000..5cedb81 --- /dev/null +++ b/src/Server/FileTime.Server.Extensions.DependencyInjection/Startup.cs @@ -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(); + serviceCollection.TryAddSingleton(); + serviceCollection.TryAddSingleton(); + return serviceCollection.AddRemoteTrackerServices(); + } + private static IServiceCollection AddRemoteTrackerServices(this IServiceCollection serviceCollection) + { + serviceCollection.TryAddSingleton(); + return serviceCollection; + } +} \ No newline at end of file diff --git a/src/Server/FileTime.Server.Tracker/FileTime.Server.Tracker.csproj b/src/Server/FileTime.Server.Tracker/FileTime.Server.Tracker.csproj new file mode 100644 index 0000000..0a5251f --- /dev/null +++ b/src/Server/FileTime.Server.Tracker/FileTime.Server.Tracker.csproj @@ -0,0 +1,13 @@ + + + + net7.0 + enable + enable + + + + + + + diff --git a/src/Server/FileTime.Server.Tracker/ItemTracker/ItemTrackerRegistry.cs b/src/Server/FileTime.Server.Tracker/ItemTracker/ItemTrackerRegistry.cs new file mode 100644 index 0000000..3b9663f --- /dev/null +++ b/src/Server/FileTime.Server.Tracker/ItemTracker/ItemTrackerRegistry.cs @@ -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> _items = new(); + + private int _globalId = 1; + public event Action? ItemRemoved; + + public int Register(IItem item) + { + lock (_lock) + { + while (_items.ContainsKey(_globalId)) _globalId++; + _items[_globalId] = new WeakReference(item); + return _globalId; + } + } + + private void Clean() + { + lock (_lock) + { + var keys = _items.Keys.ToArray(); + var keysToRemove = new List(); + foreach (var key in keys) + { + if (!_items[key].TryGetTarget(out _)) + { + keysToRemove.Add(key); + } + } + + foreach (var key in keysToRemove) + { + _items.Remove(key); + ItemRemoved?.Invoke(key); + } + } + } +} \ 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 8b34583..252a14f 100644 --- a/src/Server/FileTime.Server.Web/ConnectionHub.cs +++ b/src/Server/FileTime.Server.Web/ConnectionHub.cs @@ -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, 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 _trackedItemIds = new(); + + //TODO clean this sometimes + private readonly Dictionary _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, 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, ISignalRHub return Task.CompletedTask; } - public async Task GetNativePathAsync(string fullNamePath) - => (await _timelessContentProvider.GetNativePathByFullNameAsync(new FullName(fullNamePath)))?.Path; + public async Task GetNativePathAsync(string contentProviderId, string fullNamePath) + { + var contentProvider = GetContentProvider(contentProviderId); + return (await contentProvider.GetNativePathAsync(new FullName(fullNamePath))).Path; + } + + public async Task 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 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); } \ 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 34fb25f..b22e381 100644 --- a/src/Server/FileTime.Server/FileTime.Server.csproj +++ b/src/Server/FileTime.Server/FileTime.Server.csproj @@ -11,6 +11,7 @@ + diff --git a/src/Server/FileTime.Server/Program.cs b/src/Server/FileTime.Server/Program.cs index 22e8dd8..c72c2f4 100644 --- a/src/Server/FileTime.Server/Program.cs +++ b/src/Server/FileTime.Server/Program.cs @@ -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;