From 6ee5afa632f355b12dafb74883ca1177d2306eb2 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=C3=81d=C3=A1m=20Kov=C3=A1cs?= Date: Sat, 21 May 2022 14:30:24 +0200 Subject: [PATCH] Timeless refactor --- .../Services/IClipboardService.cs | 6 +- .../UserCommand/OpenContainerCommand.cs | 4 +- .../ViewModels/IAppState.cs | 2 + .../ViewModels/ITabViewModel.cs | 8 +- .../Extensions/ViewModelExtensions.cs | 5 +- .../Services/ClipboardService.cs | 12 +-- ...emManipulationUserCommandHandlerService.cs | 23 +++-- .../NavigationUserCommandHandlerService.cs | 10 ++- .../UserCommandHandlerServiceBase.cs | 23 ++++- .../ViewModels/AppStateBase.cs | 1 + .../ViewModels/ItemViewModel.cs | 2 +- .../ViewModels/TabViewModel.cs | 20 ++--- .../DependencyInjection.cs | 3 + .../FileTime.App.DependencyInjection.csproj | 1 + .../Command/CanCommandRun.cs | 8 ++ .../Command/ICommand.cs | 5 +- .../Command/IExecutableCommand.cs | 8 ++ .../Models/AbsolutePath.cs | 53 +++++++++++ .../Models/IAbsolutePath.cs | 15 ---- .../Models/IContainer.cs | 2 +- .../FileTime.Core.Abstraction/Models/IItem.cs | 4 +- .../Services/IContentProvider.cs | 8 +- .../Services/ITab.cs | 4 +- .../Timeline/CommandTimeState.cs | 21 +++++ .../Timeline/Difference.cs | 16 ++++ .../Timeline/DifferenceActionType.cs | 7 ++ .../Timeline/ICommandScheduler.cs | 6 ++ .../Timeline/ITimelessContentProvider.cs | 16 ++++ .../Timeline/ParallelCommands.cs | 90 +++++++++++++++++++ .../Timeline/PointInTime.cs | 41 +++++++++ .../FileTime.Core.Command/Copy/CopyCommand.cs | 10 +++ .../CreateContainer/CreateContainerCommand.cs | 21 +++++ src/Core/FileTime.Core.Models/AbsolutePath.cs | 44 --------- src/Core/FileTime.Core.Models/Container.cs | 6 +- src/Core/FileTime.Core.Models/Element.cs | 4 +- .../ContentProviderBase.cs | 20 +++-- src/Core/FileTime.Core.Services/Tab.cs | 31 ++++--- .../CommandScheduler.cs | 6 ++ .../FileTime.Core.Timeline.csproj | 13 +++ .../TimelessContentProvider.cs | 38 ++++++++ src/FileTime.sln | 7 ++ ...{IHaveAbsolutePath.cs => IHaveFullPath.cs} | 4 +- .../ViewModels/RootDriveInfo.cs | 6 +- .../Services/RootDriveInfoService.cs | 11 +-- .../ViewModels/MainWindowViewModel.cs | 15 +++- .../FileTime.GuiApp/Views/MainWindow.axaml.cs | 13 ++- .../LocalContentProvider.cs | 79 +++++++++------- 47 files changed, 571 insertions(+), 181 deletions(-) create mode 100644 src/Core/FileTime.Core.Abstraction/Command/CanCommandRun.cs create mode 100644 src/Core/FileTime.Core.Abstraction/Command/IExecutableCommand.cs create mode 100644 src/Core/FileTime.Core.Abstraction/Models/AbsolutePath.cs delete mode 100644 src/Core/FileTime.Core.Abstraction/Models/IAbsolutePath.cs create mode 100644 src/Core/FileTime.Core.Abstraction/Timeline/CommandTimeState.cs create mode 100644 src/Core/FileTime.Core.Abstraction/Timeline/Difference.cs create mode 100644 src/Core/FileTime.Core.Abstraction/Timeline/DifferenceActionType.cs create mode 100644 src/Core/FileTime.Core.Abstraction/Timeline/ICommandScheduler.cs create mode 100644 src/Core/FileTime.Core.Abstraction/Timeline/ITimelessContentProvider.cs create mode 100644 src/Core/FileTime.Core.Abstraction/Timeline/ParallelCommands.cs create mode 100644 src/Core/FileTime.Core.Abstraction/Timeline/PointInTime.cs create mode 100644 src/Core/FileTime.Core.Command/CreateContainer/CreateContainerCommand.cs delete mode 100644 src/Core/FileTime.Core.Models/AbsolutePath.cs create mode 100644 src/Core/FileTime.Core.Timeline/CommandScheduler.cs create mode 100644 src/Core/FileTime.Core.Timeline/FileTime.Core.Timeline.csproj create mode 100644 src/Core/FileTime.Core.Timeline/TimelessContentProvider.cs rename src/GuiApp/Avalonia/FileTime.GuiApp.Abstractions/Models/{IHaveAbsolutePath.cs => IHaveFullPath.cs} (50%) diff --git a/src/AppCommon/FileTime.App.Core.Abstraction/Services/IClipboardService.cs b/src/AppCommon/FileTime.App.Core.Abstraction/Services/IClipboardService.cs index 16f4a40..6430b05 100644 --- a/src/AppCommon/FileTime.App.Core.Abstraction/Services/IClipboardService.cs +++ b/src/AppCommon/FileTime.App.Core.Abstraction/Services/IClipboardService.cs @@ -6,10 +6,10 @@ namespace FileTime.App.Core.Services; public interface IClipboardService { Type? CommandType { get; } - IReadOnlyList Content { get; } + IReadOnlyList Content { get; } - void AddContent(IAbsolutePath absolutePath); - void RemoveContent(IAbsolutePath absolutePath); + void AddContent(FullName absolutePath); + void RemoveContent(FullName absolutePath); void Clear(); void SetCommand() where T : ITransportationCommand; } \ No newline at end of file diff --git a/src/AppCommon/FileTime.App.Core.Abstraction/UserCommand/OpenContainerCommand.cs b/src/AppCommon/FileTime.App.Core.Abstraction/UserCommand/OpenContainerCommand.cs index b368dac..9e32d7b 100644 --- a/src/AppCommon/FileTime.App.Core.Abstraction/UserCommand/OpenContainerCommand.cs +++ b/src/AppCommon/FileTime.App.Core.Abstraction/UserCommand/OpenContainerCommand.cs @@ -4,9 +4,9 @@ namespace FileTime.App.Core.UserCommand; public class OpenContainerCommand : IUserCommand { - public IAbsolutePath Path { get; } + public AbsolutePath Path { get; } - public OpenContainerCommand(IAbsolutePath path) + public OpenContainerCommand(AbsolutePath path) { Path = path; } diff --git a/src/AppCommon/FileTime.App.Core.Abstraction/ViewModels/IAppState.cs b/src/AppCommon/FileTime.App.Core.Abstraction/ViewModels/IAppState.cs index a284a32..5e0725f 100644 --- a/src/AppCommon/FileTime.App.Core.Abstraction/ViewModels/IAppState.cs +++ b/src/AppCommon/FileTime.App.Core.Abstraction/ViewModels/IAppState.cs @@ -1,5 +1,7 @@ using System.Collections.ObjectModel; +using System.Reactive.Subjects; using FileTime.App.Core.Models.Enums; +using FileTime.Core.Timeline; namespace FileTime.App.Core.ViewModels; diff --git a/src/AppCommon/FileTime.App.Core.Abstraction/ViewModels/ITabViewModel.cs b/src/AppCommon/FileTime.App.Core.Abstraction/ViewModels/ITabViewModel.cs index 65ae0b1..3e0166c 100644 --- a/src/AppCommon/FileTime.App.Core.Abstraction/ViewModels/ITabViewModel.cs +++ b/src/AppCommon/FileTime.App.Core.Abstraction/ViewModels/ITabViewModel.cs @@ -15,7 +15,7 @@ public interface ITabViewModel : IInitable IObservable CurrentLocation { get; } IObservable CurrentSelectedItem { get; } IObservable>?> CurrentItems { get; } - IObservable> MarkedItems { get; } + IObservable> MarkedItems { get; } IObservable>?> SelectedsChildren { get; } IObservable>?> ParentsChildren { get; } BindedCollection? CurrentItemsCollection { get; } @@ -25,7 +25,7 @@ public interface ITabViewModel : IInitable IObservable?> ParentsChildrenCollectionObservable { get; } IObservable?> SelectedsChildrenCollectionObservable { get; } void ClearMarkedItems(); - void RemoveMarkedItem(IAbsolutePath item); - void AddMarkedItem(IAbsolutePath item); - void ToggleMarkedItem(IAbsolutePath item); + void RemoveMarkedItem(FullName fullName); + void AddMarkedItem(FullName fullName); + void ToggleMarkedItem(FullName fullName); } \ No newline at end of file diff --git a/src/AppCommon/FileTime.App.Core/Extensions/ViewModelExtensions.cs b/src/AppCommon/FileTime.App.Core/Extensions/ViewModelExtensions.cs index 5e91b89..2589e65 100644 --- a/src/AppCommon/FileTime.App.Core/Extensions/ViewModelExtensions.cs +++ b/src/AppCommon/FileTime.App.Core/Extensions/ViewModelExtensions.cs @@ -1,13 +1,14 @@ using FileTime.App.Core.ViewModels; using FileTime.Core.Models; +using FileTime.Core.Timeline; namespace FileTime.App.Core.Extensions; public static class ViewModelExtensions { - public static IAbsolutePath ToAbsolutePath(this IItemViewModel itemViewModel) + public static AbsolutePath ToAbsolutePath(this IItemViewModel itemViewModel, ITimelessContentProvider timelessContentProvider) { var item = itemViewModel.BaseItem ?? throw new ArgumentException($"{nameof(itemViewModel)} does not have {nameof(IItemViewModel.BaseItem)}"); - return new AbsolutePath(item); + return new AbsolutePath(timelessContentProvider, item); } } \ No newline at end of file diff --git a/src/AppCommon/FileTime.App.Core/Services/ClipboardService.cs b/src/AppCommon/FileTime.App.Core/Services/ClipboardService.cs index beeed61..fa1d861 100644 --- a/src/AppCommon/FileTime.App.Core/Services/ClipboardService.cs +++ b/src/AppCommon/FileTime.App.Core/Services/ClipboardService.cs @@ -5,17 +5,17 @@ namespace FileTime.App.Core.Services; public class ClipboardService : IClipboardService { - private List _content; - public IReadOnlyList Content { get; private set; } + private List _content; + public IReadOnlyList Content { get; private set; } public Type? CommandType { get; private set; } public ClipboardService() { - _content = new List(); + _content = new List(); Content = _content.AsReadOnly(); } - public void AddContent(IAbsolutePath absolutePath) + public void AddContent(FullName absolutePath) { foreach (var content in _content) { @@ -25,7 +25,7 @@ public class ClipboardService : IClipboardService _content.Add(absolutePath); } - public void RemoveContent(IAbsolutePath absolutePath) + public void RemoveContent(FullName absolutePath) { for (var i = 0; i < _content.Count; i++) { @@ -38,7 +38,7 @@ public class ClipboardService : IClipboardService public void Clear() { - _content = new List(); + _content = new List(); Content = _content.AsReadOnly(); CommandType = null; } diff --git a/src/AppCommon/FileTime.App.Core/Services/UserCommandHandler/ItemManipulationUserCommandHandlerService.cs b/src/AppCommon/FileTime.App.Core/Services/UserCommandHandler/ItemManipulationUserCommandHandlerService.cs index a16ed1d..4acbdd5 100644 --- a/src/AppCommon/FileTime.App.Core/Services/UserCommandHandler/ItemManipulationUserCommandHandlerService.cs +++ b/src/AppCommon/FileTime.App.Core/Services/UserCommandHandler/ItemManipulationUserCommandHandlerService.cs @@ -4,8 +4,10 @@ using FileTime.App.Core.Models.Enums; using FileTime.App.Core.UserCommand; using FileTime.App.Core.ViewModels; using FileTime.Core.Command; +using FileTime.Core.Command.CreateContainer; using FileTime.Core.Interactions; using FileTime.Core.Models; +using FileTime.Core.Timeline; using Microsoft.Extensions.Logging; using CopyCommand = FileTime.Core.Command.Copy.CopyCommand; @@ -19,24 +21,30 @@ public class ItemManipulationUserCommandHandlerService : UserCommandHandlerServi private readonly IClipboardService _clipboardService; private readonly IInputInterface _inputInterface; private readonly ILogger _logger; - private readonly BindedCollection? _markedItems; + private readonly ITimelessContentProvider _timelessContentProvider; + private readonly BindedCollection? _markedItems; + private PointInTime _currentPointInTime; public ItemManipulationUserCommandHandlerService( IAppState appState, IUserCommandHandlerService userCommandHandlerService, IClipboardService clipboardService, IInputInterface inputInterface, - ILogger logger) : base(appState) + ILogger logger, + ITimelessContentProvider timelessContentProvider) : base(appState, timelessContentProvider) { _userCommandHandlerService = userCommandHandlerService; _clipboardService = clipboardService; _inputInterface = inputInterface; _logger = logger; + _timelessContentProvider = timelessContentProvider; + _currentPointInTime = null!; SaveSelectedTab(t => _selectedTab = t); SaveCurrentSelectedItem(i => _currentSelectedItem = i); + SaveCurrentPointInTime(t => _currentPointInTime = t); - _markedItems = new BindedCollection(appState.SelectedTab.Select(t => t?.MarkedItems)); + _markedItems = new BindedCollection(appState.SelectedTab.Select(t => t?.MarkedItems)); AddCommandHandlers(new IUserCommandHandler[] { @@ -51,7 +59,7 @@ public class ItemManipulationUserCommandHandlerService : UserCommandHandlerServi { if (_selectedTab == null || _currentSelectedItem?.BaseItem?.FullName == null) return; - _selectedTab.ToggleMarkedItem(new AbsolutePath(_currentSelectedItem.BaseItem)); + _selectedTab.ToggleMarkedItem(_currentSelectedItem.BaseItem.FullName); await _userCommandHandlerService.HandleCommandAsync(MoveCursorDownCommand.Instance); } @@ -71,7 +79,8 @@ public class ItemManipulationUserCommandHandlerService : UserCommandHandlerServi } else if (_currentSelectedItem?.BaseItem != null) { - _clipboardService.AddContent(new AbsolutePath(_currentSelectedItem.BaseItem)); + var item = _currentSelectedItem.BaseItem; + _clipboardService.AddContent(item.FullName ?? throw new ArgumentException($"{nameof(item.FullName)} can not be null.", nameof(item))); } return Task.CompletedTask; @@ -114,8 +123,10 @@ public class ItemManipulationUserCommandHandlerService : UserCommandHandlerServi var containerNameInput = new TextInputElement("Container name"); await _inputInterface.ReadInputs(new List() { containerNameInput }); - + //TODO: message on empty result var newContainerName = containerNameInput.Value; + + var command = new CreateContainerCommand(); } } \ No newline at end of file diff --git a/src/AppCommon/FileTime.App.Core/Services/UserCommandHandler/NavigationUserCommandHandlerService.cs b/src/AppCommon/FileTime.App.Core/Services/UserCommandHandler/NavigationUserCommandHandlerService.cs index 7516d55..bb7a7f2 100644 --- a/src/AppCommon/FileTime.App.Core/Services/UserCommandHandler/NavigationUserCommandHandlerService.cs +++ b/src/AppCommon/FileTime.App.Core/Services/UserCommandHandler/NavigationUserCommandHandlerService.cs @@ -4,6 +4,7 @@ using FileTime.App.Core.UserCommand; using FileTime.App.Core.ViewModels; using FileTime.Core.Models; using FileTime.Core.Services; +using FileTime.Core.Timeline; using FileTime.Providers.Local; using Microsoft.Extensions.DependencyInjection; @@ -15,6 +16,7 @@ public class NavigationUserCommandHandlerService : UserCommandHandlerServiceBase private readonly IServiceProvider _serviceProvider; private readonly ILocalContentProvider _localContentProvider; private readonly IUserCommandHandlerService _userCommandHandlerService; + private readonly ITimelessContentProvider _timelessContentProvider; private ITabViewModel? _selectedTab; private IContainer? _currentLocation; private IItemViewModel? _currentSelectedItem; @@ -25,12 +27,14 @@ public class NavigationUserCommandHandlerService : UserCommandHandlerServiceBase IAppState appState, IServiceProvider serviceProvider, ILocalContentProvider localContentProvider, - IUserCommandHandlerService userCommandHandlerService) : base(appState) + IUserCommandHandlerService userCommandHandlerService, + ITimelessContentProvider timelessContentProvider) : base(appState) { _appState = appState; _serviceProvider = serviceProvider; _localContentProvider = localContentProvider; _userCommandHandlerService = userCommandHandlerService; + _timelessContentProvider = timelessContentProvider; SaveSelectedTab(t => _selectedTab = t); SaveCurrentSelectedItem(i => _currentSelectedItem = i); @@ -71,7 +75,7 @@ public class NavigationUserCommandHandlerService : UserCommandHandlerServiceBase private async Task GoUp() { - if (_currentLocation?.Parent is not IAbsolutePath parentPath || await parentPath.ResolveAsyncSafe() is not IContainer newContainer) return; + if (_currentLocation?.Parent is not AbsolutePath parentPath || await parentPath.ResolveAsyncSafe() is not IContainer newContainer) return; _selectedTab?.Tab?.SetCurrentLocation(newContainer); } @@ -94,7 +98,7 @@ public class NavigationUserCommandHandlerService : UserCommandHandlerServiceBase var newSelectedItem = getNewSelected(_currentItems); if (newSelectedItem == null) return; - _selectedTab.Tab?.SetSelectedItem(newSelectedItem.ToAbsolutePath()); + _selectedTab.Tab?.SetSelectedItem(newSelectedItem.ToAbsolutePath(_timelessContentProvider)); } private Task EnterRapidTravel() diff --git a/src/AppCommon/FileTime.App.Core/Services/UserCommandHandler/UserCommandHandlerServiceBase.cs b/src/AppCommon/FileTime.App.Core/Services/UserCommandHandler/UserCommandHandlerServiceBase.cs index e4b5705..13d36bc 100644 --- a/src/AppCommon/FileTime.App.Core/Services/UserCommandHandler/UserCommandHandlerServiceBase.cs +++ b/src/AppCommon/FileTime.App.Core/Services/UserCommandHandler/UserCommandHandlerServiceBase.cs @@ -2,6 +2,7 @@ using System.Reactive.Linq; using DynamicData; using FileTime.App.Core.ViewModels; using FileTime.Core.Models; +using FileTime.Core.Timeline; namespace FileTime.App.Core.Services.UserCommandHandler; @@ -9,10 +10,14 @@ public abstract class UserCommandHandlerServiceBase : IUserCommandHandler { private readonly List _userCommandHandlers = new(); private readonly IAppState? _appState; + private readonly ITimelessContentProvider? _timelessContentProvider; - protected UserCommandHandlerServiceBase(IAppState? appState = null) + protected UserCommandHandlerServiceBase( + IAppState? appState = null, + ITimelessContentProvider? timelessContentProvider = null) { _appState = appState; + _timelessContentProvider = timelessContentProvider; } public bool CanHandleCommand(UserCommand.IUserCommand command) => _userCommandHandlers.Any(h => h.CanHandleCommand(command)); @@ -48,13 +53,23 @@ public abstract class UserCommandHandlerServiceBase : IUserCommandHandler protected IDisposable SaveCurrentItems(Action> handler) => RunWithAppState(appState => appState.SelectedTab.Select(t => t?.CurrentItemsCollectionObservable ?? Observable.Return((IEnumerable?) Enumerable.Empty())).Switch().Subscribe(i => handler(i ?? Enumerable.Empty()))); - protected IDisposable SaveMarkedItems(Action> handler) - => RunWithAppState(appState => appState.SelectedTab.Select(t => t == null ? Observable.Empty>() : t.MarkedItems).Switch().Subscribe(handler)); + protected IDisposable SaveMarkedItems(Action> handler) + => RunWithAppState(appState => appState.SelectedTab.Select(t => t == null ? Observable.Empty>() : t.MarkedItems).Switch().Subscribe(handler)); + + protected IDisposable SaveCurrentPointInTime(Action handler) + => RunWithTimelessContentProvider(timelessContentProvider => timelessContentProvider.CurrentPointInTime.Subscribe(handler)); private IDisposable RunWithAppState(Func act) { - if (_appState == null) throw new NullReferenceException($"AppState is nit initialized in {nameof(UserCommandHandlerServiceBase)}."); + if (_appState == null) throw new NullReferenceException($"AppState is not initialized in {nameof(UserCommandHandlerServiceBase)}."); return act(_appState); } + + private IDisposable RunWithTimelessContentProvider(Func act) + { + if (_timelessContentProvider == null) throw new NullReferenceException($"TimelessContainer is not initialized in {nameof(UserCommandHandlerServiceBase)}."); + + return act(_timelessContentProvider); + } } \ No newline at end of file diff --git a/src/AppCommon/FileTime.App.Core/ViewModels/AppStateBase.cs b/src/AppCommon/FileTime.App.Core/ViewModels/AppStateBase.cs index 9e815a6..4873eb9 100644 --- a/src/AppCommon/FileTime.App.Core/ViewModels/AppStateBase.cs +++ b/src/AppCommon/FileTime.App.Core/ViewModels/AppStateBase.cs @@ -3,6 +3,7 @@ using System.Reactive.Linq; using System.Reactive.Subjects; using DynamicData; using FileTime.App.Core.Models.Enums; +using FileTime.Core.Timeline; using MvvmGen; using MoreLinq; diff --git a/src/AppCommon/FileTime.App.Core/ViewModels/ItemViewModel.cs b/src/AppCommon/FileTime.App.Core/ViewModels/ItemViewModel.cs index 6671b95..c21b8af 100644 --- a/src/AppCommon/FileTime.App.Core/ViewModels/ItemViewModel.cs +++ b/src/AppCommon/FileTime.App.Core/ViewModels/ItemViewModel.cs @@ -57,7 +57,7 @@ public abstract partial class ItemViewModel : IItemViewModel DisplayNameText = item.DisplayName; IsMarked = itemViewModelType is ItemViewModelType.Main - ? parentTab.MarkedItems.ToCollection().Select(m => m.Any(i => i.Path.Path == item.FullName?.Path)) + ? parentTab.MarkedItems.ToCollection().Select(m => m.Any(i => i.Path == item.FullName?.Path)) : Observable.Return(false); IsSelected = itemViewModelType is ItemViewModelType.Main diff --git a/src/AppCommon/FileTime.App.Core/ViewModels/TabViewModel.cs b/src/AppCommon/FileTime.App.Core/ViewModels/TabViewModel.cs index e59f8d2..fd5c1d1 100644 --- a/src/AppCommon/FileTime.App.Core/ViewModels/TabViewModel.cs +++ b/src/AppCommon/FileTime.App.Core/ViewModels/TabViewModel.cs @@ -19,7 +19,7 @@ public partial class TabViewModel : ITabViewModel, IDisposable private readonly IItemNameConverterService _itemNameConverterService; private readonly IAppState _appState; private readonly IRxSchedulerService _rxSchedulerService; - private readonly SourceList _markedItems = new(); + private readonly SourceList _markedItems = new(); private readonly List _disposables = new(); private bool _disposed; @@ -31,7 +31,7 @@ public partial class TabViewModel : ITabViewModel, IDisposable public IObservable CurrentLocation { get; private set; } = null!; public IObservable CurrentSelectedItem { get; private set; } = null!; public IObservable>?> CurrentItems { get; private set; } = null!; - public IObservable> MarkedItems { get; } + public IObservable> MarkedItems { get; } public IObservable>?> SelectedsChildren { get; private set; } = null!; public IObservable>?> ParentsChildren { get; private set; } = null!; @@ -171,7 +171,7 @@ public partial class TabViewModel : ITabViewModel, IDisposable } } - private static async Task MapItem(IAbsolutePath item) + private static async Task MapItem(AbsolutePath item) => await item.ResolveAsync(forceResolve: true, itemInitializationSettings: new ItemInitializationSettings(true)); @@ -211,24 +211,24 @@ public partial class TabViewModel : ITabViewModel, IDisposable throw new ArgumentException($"{nameof(item)} is not {nameof(IContainer)} neither {nameof(IElement)}"); } - public void AddMarkedItem(IAbsolutePath item) => _markedItems.Add(item); + public void AddMarkedItem(FullName fullName) => _markedItems.Add(fullName); - public void RemoveMarkedItem(IAbsolutePath item) + public void RemoveMarkedItem(FullName fullName) { - var itemsToRemove = _markedItems.Items.Where(i => i.Path.Path == item.Path.Path).ToList(); + var itemsToRemove = _markedItems.Items.Where(i => i.Path == fullName.Path).ToList(); _markedItems.RemoveMany(itemsToRemove); } - public void ToggleMarkedItem(IAbsolutePath item) + public void ToggleMarkedItem(FullName fullName) { - if (_markedItems.Items.Any(i => i.Path.Path == item.Path.Path)) + if (_markedItems.Items.Any(i => i.Path == fullName.Path)) { - RemoveMarkedItem(item); + RemoveMarkedItem(fullName); } else { - AddMarkedItem(item); + AddMarkedItem(fullName); } } diff --git a/src/AppCommon/FileTime.App.DependencyInjection/DependencyInjection.cs b/src/AppCommon/FileTime.App.DependencyInjection/DependencyInjection.cs index a3a38c1..3caac05 100644 --- a/src/AppCommon/FileTime.App.DependencyInjection/DependencyInjection.cs +++ b/src/AppCommon/FileTime.App.DependencyInjection/DependencyInjection.cs @@ -1,5 +1,6 @@ using FileTime.App.Core; using FileTime.Core.Services; +using FileTime.Core.Timeline; using FileTime.Providers.Local; using Microsoft.Extensions.DependencyInjection; @@ -13,6 +14,8 @@ public static class DependencyInjection return serviceCollection .AddTransient() + .AddSingleton() + .AddSingleton() .AddCoreAppServices() .AddLocalServices(); } diff --git a/src/AppCommon/FileTime.App.DependencyInjection/FileTime.App.DependencyInjection.csproj b/src/AppCommon/FileTime.App.DependencyInjection/FileTime.App.DependencyInjection.csproj index 440ce50..ce4dcb5 100644 --- a/src/AppCommon/FileTime.App.DependencyInjection/FileTime.App.DependencyInjection.csproj +++ b/src/AppCommon/FileTime.App.DependencyInjection/FileTime.App.DependencyInjection.csproj @@ -14,6 +14,7 @@ + diff --git a/src/Core/FileTime.Core.Abstraction/Command/CanCommandRun.cs b/src/Core/FileTime.Core.Abstraction/Command/CanCommandRun.cs new file mode 100644 index 0000000..b123027 --- /dev/null +++ b/src/Core/FileTime.Core.Abstraction/Command/CanCommandRun.cs @@ -0,0 +1,8 @@ +namespace FileTime.Core.Command; + +public enum CanCommandRun +{ + True, + False, + Forcable +} \ No newline at end of file diff --git a/src/Core/FileTime.Core.Abstraction/Command/ICommand.cs b/src/Core/FileTime.Core.Abstraction/Command/ICommand.cs index 8508a67..9e41fa5 100644 --- a/src/Core/FileTime.Core.Abstraction/Command/ICommand.cs +++ b/src/Core/FileTime.Core.Abstraction/Command/ICommand.cs @@ -1,6 +1,9 @@ +using FileTime.Core.Timeline; + namespace FileTime.Core.Command; public interface ICommand { - + Task CanRun(PointInTime currentTime); + Task SimulateCommand(PointInTime? currentTime); } \ No newline at end of file diff --git a/src/Core/FileTime.Core.Abstraction/Command/IExecutableCommand.cs b/src/Core/FileTime.Core.Abstraction/Command/IExecutableCommand.cs new file mode 100644 index 0000000..786bfad --- /dev/null +++ b/src/Core/FileTime.Core.Abstraction/Command/IExecutableCommand.cs @@ -0,0 +1,8 @@ +using FileTime.Core.Timeline; + +namespace FileTime.Core.Command; + +public interface IExecutableCommand : ICommand +{ + Task Execute(ICommandScheduler commandScheduler); +} \ No newline at end of file diff --git a/src/Core/FileTime.Core.Abstraction/Models/AbsolutePath.cs b/src/Core/FileTime.Core.Abstraction/Models/AbsolutePath.cs new file mode 100644 index 0000000..262d473 --- /dev/null +++ b/src/Core/FileTime.Core.Abstraction/Models/AbsolutePath.cs @@ -0,0 +1,53 @@ +using FileTime.Core.Enums; +using FileTime.Core.Timeline; + +namespace FileTime.Core.Models; + +public class AbsolutePath +{ + public ITimelessContentProvider TimelessProvider { get; } + public PointInTime PointInTime { get; } + + public FullName Path { get; } + public AbsolutePathType Type { get; } + + public AbsolutePath( + ITimelessContentProvider timelessProvider, + PointInTime pointInTime, + FullName path, + AbsolutePathType type) + { + TimelessProvider = timelessProvider; + Path = path; + Type = type; + PointInTime = pointInTime; + } + + public AbsolutePath(ITimelessContentProvider timelessProvider, IItem item) + { + TimelessProvider = timelessProvider; + PointInTime = item.PointInTime; + Path = item.FullName ?? throw new ArgumentException($"{nameof(item.FullName)} can not be null.", nameof(item)); + Type = item.Type; + } + + public async Task ResolveAsync(bool forceResolve = false, + ItemInitializationSettings itemInitializationSettings = default) + { + return await TimelessProvider.GetItemByFullNameAsync(Path, PointInTime, forceResolve, Type, + itemInitializationSettings); + } + + public async Task ResolveAsyncSafe(bool forceResolve = false, + ItemInitializationSettings itemInitializationSettings = default) + { + try + { + return await ResolveAsync(forceResolve, itemInitializationSettings); + } + catch + { + return null; + } + } +} \ No newline at end of file diff --git a/src/Core/FileTime.Core.Abstraction/Models/IAbsolutePath.cs b/src/Core/FileTime.Core.Abstraction/Models/IAbsolutePath.cs deleted file mode 100644 index f958f00..0000000 --- a/src/Core/FileTime.Core.Abstraction/Models/IAbsolutePath.cs +++ /dev/null @@ -1,15 +0,0 @@ -using FileTime.Core.Enums; -using FileTime.Core.Services; - -namespace FileTime.Core.Models; - -public interface IAbsolutePath -{ - IContentProvider ContentProvider { get; } - IContentProvider? VirtualContentProvider { get; } - FullName Path { get; } - AbsolutePathType Type { get; } - - Task ResolveAsync(bool forceResolve = false, ItemInitializationSettings itemInitializationSettings = default); - Task ResolveAsyncSafe(bool forceResolve = false, ItemInitializationSettings itemInitializationSettings = default); -} \ No newline at end of file diff --git a/src/Core/FileTime.Core.Abstraction/Models/IContainer.cs b/src/Core/FileTime.Core.Abstraction/Models/IContainer.cs index e1c6b29..6a6b905 100644 --- a/src/Core/FileTime.Core.Abstraction/Models/IContainer.cs +++ b/src/Core/FileTime.Core.Abstraction/Models/IContainer.cs @@ -4,6 +4,6 @@ namespace FileTime.Core.Models; public interface IContainer : IItem { - IObservable>?> Items { get; } + IObservable>?> Items { get; } IObservable IsLoading { get; } } \ No newline at end of file diff --git a/src/Core/FileTime.Core.Abstraction/Models/IItem.cs b/src/Core/FileTime.Core.Abstraction/Models/IItem.cs index 8ccd165..6bc78bc 100644 --- a/src/Core/FileTime.Core.Abstraction/Models/IItem.cs +++ b/src/Core/FileTime.Core.Abstraction/Models/IItem.cs @@ -1,6 +1,7 @@ using System.Reactive.Linq; using FileTime.Core.Enums; using FileTime.Core.Services; +using FileTime.Core.Timeline; namespace FileTime.Core.Models; @@ -10,7 +11,7 @@ public interface IItem string DisplayName { get; } FullName? FullName { get; } NativePath? NativePath { get; } - IAbsolutePath? Parent { get; } + AbsolutePath? Parent { get; } bool IsHidden { get; } bool IsExists { get; } DateTime? CreatedAt { get; } @@ -19,6 +20,7 @@ public interface IItem IContentProvider Provider { get; } string? Attributes { get; } AbsolutePathType Type { get; } + PointInTime PointInTime { get; } IObservable> Exceptions { get; } ReadOnlyExtensionCollection Extensions { get; } diff --git a/src/Core/FileTime.Core.Abstraction/Services/IContentProvider.cs b/src/Core/FileTime.Core.Abstraction/Services/IContentProvider.cs index 91022e8..26169e3 100644 --- a/src/Core/FileTime.Core.Abstraction/Services/IContentProvider.cs +++ b/src/Core/FileTime.Core.Abstraction/Services/IContentProvider.cs @@ -1,6 +1,7 @@ using FileTime.Core.Behaviors; using FileTime.Core.Enums; using FileTime.Core.Models; +using FileTime.Core.Timeline; namespace FileTime.Core.Services; @@ -8,17 +9,18 @@ public interface IContentProvider : IContainer, IOnContainerEnter { Task GetItemByFullNameAsync( FullName fullName, + PointInTime pointInTime, bool forceResolve = false, AbsolutePathType forceResolvePathType = AbsolutePathType.Unknown, ItemInitializationSettings itemInitializationSettings = default); - Task GetItemByNativePathAsync( - NativePath nativePath, + Task GetItemByNativePathAsync(NativePath nativePath, + PointInTime pointInTime, bool forceResolve = false, AbsolutePathType forceResolvePathType = AbsolutePathType.Unknown, ItemInitializationSettings itemInitializationSettings = default); - Task> GetItemsByContainerAsync(FullName fullName); + Task> GetItemsByContainerAsync(FullName fullName, PointInTime pointInTime); NativePath GetNativePath(FullName fullName); Task GetContentAsync(IElement element, int? maxLength = null, CancellationToken cancellationToken = default); diff --git a/src/Core/FileTime.Core.Abstraction/Services/ITab.cs b/src/Core/FileTime.Core.Abstraction/Services/ITab.cs index 9d6d6d3..ded0da7 100644 --- a/src/Core/FileTime.Core.Abstraction/Services/ITab.cs +++ b/src/Core/FileTime.Core.Abstraction/Services/ITab.cs @@ -7,12 +7,12 @@ namespace FileTime.Core.Services; public interface ITab : IInitable { IObservable CurrentLocation { get; } - IObservable CurrentSelectedItem { get; } + IObservable CurrentSelectedItem { get; } IObservable>?> CurrentItems { get; } void SetCurrentLocation(IContainer newLocation); void AddItemFilter(ItemFilter filter); void RemoveItemFilter(ItemFilter filter); void RemoveItemFilter(string name); - void SetSelectedItem(IAbsolutePath newSelectedItem); + void SetSelectedItem(AbsolutePath newSelectedItem); } \ No newline at end of file diff --git a/src/Core/FileTime.Core.Abstraction/Timeline/CommandTimeState.cs b/src/Core/FileTime.Core.Abstraction/Timeline/CommandTimeState.cs new file mode 100644 index 0000000..8b0b2e7 --- /dev/null +++ b/src/Core/FileTime.Core.Abstraction/Timeline/CommandTimeState.cs @@ -0,0 +1,21 @@ +using FileTime.Core.Command; + +namespace FileTime.Core.Timeline; + +public class CommandTimeState +{ + public ICommand Command { get; } + public CanCommandRun CanRun { get; private set; } = CanCommandRun.False; + public bool ForceRun { get; set; } + + public CommandTimeState(ICommand command, PointInTime? startTime) + { + Command = command; + Task.Run(async () => await UpdateState(startTime)).Wait(); + } + + public async Task UpdateState(PointInTime? startPoint) + { + CanRun = startPoint == null ? CanCommandRun.False : await Command.CanRun(startPoint); + } +} \ No newline at end of file diff --git a/src/Core/FileTime.Core.Abstraction/Timeline/Difference.cs b/src/Core/FileTime.Core.Abstraction/Timeline/Difference.cs new file mode 100644 index 0000000..c74d915 --- /dev/null +++ b/src/Core/FileTime.Core.Abstraction/Timeline/Difference.cs @@ -0,0 +1,16 @@ +using FileTime.Core.Models; +using FileTime.Core.Services; + +namespace FileTime.Core.Timeline; + +public class Difference +{ + public AbsolutePath AbsolutePath { get; } + public DifferenceActionType Action { get; } + + public Difference(DifferenceActionType action, AbsolutePath absolutePath) + { + AbsolutePath = absolutePath; + Action = action; + } +} \ No newline at end of file diff --git a/src/Core/FileTime.Core.Abstraction/Timeline/DifferenceActionType.cs b/src/Core/FileTime.Core.Abstraction/Timeline/DifferenceActionType.cs new file mode 100644 index 0000000..2e5956a --- /dev/null +++ b/src/Core/FileTime.Core.Abstraction/Timeline/DifferenceActionType.cs @@ -0,0 +1,7 @@ +namespace FileTime.Core.Timeline; + +public enum DifferenceActionType +{ + Create, + Delete +} \ No newline at end of file diff --git a/src/Core/FileTime.Core.Abstraction/Timeline/ICommandScheduler.cs b/src/Core/FileTime.Core.Abstraction/Timeline/ICommandScheduler.cs new file mode 100644 index 0000000..0046851 --- /dev/null +++ b/src/Core/FileTime.Core.Abstraction/Timeline/ICommandScheduler.cs @@ -0,0 +1,6 @@ +namespace FileTime.Core.Timeline; + +public interface ICommandScheduler +{ + +} \ No newline at end of file diff --git a/src/Core/FileTime.Core.Abstraction/Timeline/ITimelessContentProvider.cs b/src/Core/FileTime.Core.Abstraction/Timeline/ITimelessContentProvider.cs new file mode 100644 index 0000000..356d56d --- /dev/null +++ b/src/Core/FileTime.Core.Abstraction/Timeline/ITimelessContentProvider.cs @@ -0,0 +1,16 @@ +using System.Reactive.Subjects; +using FileTime.Core.Enums; +using FileTime.Core.Models; + +namespace FileTime.Core.Timeline; + +public interface ITimelessContentProvider +{ + BehaviorSubject CurrentPointInTime { get; } + + Task GetItemByFullNameAsync(FullName fullName, + PointInTime? pointInTime, + bool forceResolve = false, + AbsolutePathType forceResolvePathType = AbsolutePathType.Unknown, + ItemInitializationSettings itemInitializationSettings = default); +} \ No newline at end of file diff --git a/src/Core/FileTime.Core.Abstraction/Timeline/ParallelCommands.cs b/src/Core/FileTime.Core.Abstraction/Timeline/ParallelCommands.cs new file mode 100644 index 0000000..0b84227 --- /dev/null +++ b/src/Core/FileTime.Core.Abstraction/Timeline/ParallelCommands.cs @@ -0,0 +1,90 @@ +using FileTime.Core.Command; + +namespace FileTime.Core.Timeline; + +public class ParallelCommands +{ + private static ushort _idCounter; + public List _commands; + public ushort Id { get; } + public IReadOnlyList Commands { get; } + public PointInTime? Result { get; private set; } + + public ParallelCommands(PointInTime? result) + : this(new List(), result) + { + } + + private ParallelCommands(List commands, PointInTime? result) + { + Id = _idCounter++; + + _commands = commands; + Commands = _commands.AsReadOnly(); + + Result = result; + } + + public static async Task Create(PointInTime? startTime, IEnumerable commands) + { + var commandStates = new List(); + var currentTime = startTime; + foreach (var command in commands) + { + CommandTimeState commandTimeState = new(command, currentTime); + if (currentTime != null) + { + var canRun = await command.CanRun(currentTime); + if (canRun == CanCommandRun.True) + { + currentTime = await command.SimulateCommand(currentTime); + } + else + { + currentTime = null; + } + } + + commandStates.Add(commandTimeState); + } + + return new ParallelCommands(commandStates, currentTime); + } + + public async Task AddCommand(ICommand command) + { + _commands.Add(new CommandTimeState(command, Result)); + if (Result != null) + { + Result = await command.SimulateCommand(Result); + } + } + + public async Task RefreshResult(PointInTime? startPoint) + { + var result = startPoint; + foreach (var command in _commands) + { + await command.UpdateState(result); + if (result != null) + { + var canRun = await command.Command.CanRun(result); + if (canRun == CanCommandRun.True || (canRun == CanCommandRun.Forcable && command.ForceRun)) + { + result = await command.Command.SimulateCommand(result); + } + else + { + result = null; + } + } + } + + Result = result; + return Result; + } + + public void RemoveAt(int number) => _commands.RemoveAt(number); + + internal void Remove(CommandTimeState command) => _commands.Remove(command); +} \ No newline at end of file diff --git a/src/Core/FileTime.Core.Abstraction/Timeline/PointInTime.cs b/src/Core/FileTime.Core.Abstraction/Timeline/PointInTime.cs new file mode 100644 index 0000000..7b58d77 --- /dev/null +++ b/src/Core/FileTime.Core.Abstraction/Timeline/PointInTime.cs @@ -0,0 +1,41 @@ +namespace FileTime.Core.Timeline; + +public class PointInTime +{ + private readonly List _differences; + public static readonly PointInTime Eternal = new PointInTime(); + public static readonly PointInTime Present = new PointInTime(); + + public IReadOnlyList Differences { get; } + + private PointInTime() : this(new List()) + { + } + + private PointInTime(IEnumerable differences) + { + _differences = new List(differences); + Differences = _differences.AsReadOnly(); + } + + private PointInTime(PointInTime previous, IEnumerable differences) + : this(MergeDifferences(previous.Differences, differences)) + { + } + + public PointInTime WithDifferences(IEnumerable differences) => + new(this, differences); + + private static List MergeDifferences(IEnumerable previouses, + IEnumerable differences) + { + var merged = new List(); + + merged.AddRange(previouses); + merged.AddRange(differences); + + return merged; + } + + public static PointInTime CreateEmpty() => new PointInTime(); +} \ No newline at end of file diff --git a/src/Core/FileTime.Core.Command/Copy/CopyCommand.cs b/src/Core/FileTime.Core.Command/Copy/CopyCommand.cs index 385a55b..e7d44fc 100644 --- a/src/Core/FileTime.Core.Command/Copy/CopyCommand.cs +++ b/src/Core/FileTime.Core.Command/Copy/CopyCommand.cs @@ -1,6 +1,16 @@ +using FileTime.Core.Timeline; + namespace FileTime.Core.Command.Copy; public class CopyCommand : ITransportationCommand { + public Task CanRun(PointInTime currentTime) + { + throw new NotImplementedException(); + } + public Task SimulateCommand(PointInTime? currentTime) + { + throw new NotImplementedException(); + } } \ No newline at end of file diff --git a/src/Core/FileTime.Core.Command/CreateContainer/CreateContainerCommand.cs b/src/Core/FileTime.Core.Command/CreateContainer/CreateContainerCommand.cs new file mode 100644 index 0000000..e5286ef --- /dev/null +++ b/src/Core/FileTime.Core.Command/CreateContainer/CreateContainerCommand.cs @@ -0,0 +1,21 @@ +using FileTime.Core.Timeline; + +namespace FileTime.Core.Command.CreateContainer; + +public class CreateContainerCommand : IExecutableCommand +{ + public Task CanRun(PointInTime currentTime) + { + throw new NotImplementedException(); + } + + public Task SimulateCommand(PointInTime? currentTime) + { + throw new NotImplementedException(); + } + + public Task Execute(ICommandScheduler commandScheduler) + { + throw new NotImplementedException(); + } +} \ No newline at end of file diff --git a/src/Core/FileTime.Core.Models/AbsolutePath.cs b/src/Core/FileTime.Core.Models/AbsolutePath.cs deleted file mode 100644 index 38d9b9f..0000000 --- a/src/Core/FileTime.Core.Models/AbsolutePath.cs +++ /dev/null @@ -1,44 +0,0 @@ -using FileTime.Core.Enums; -using FileTime.Core.Services; - -namespace FileTime.Core.Models; - -public class AbsolutePath : IAbsolutePath -{ - public IContentProvider ContentProvider { get; } - public IContentProvider? VirtualContentProvider { get; } - - public FullName Path { get; } - public AbsolutePathType Type { get; } - - public AbsolutePath(IContentProvider contentProvider, FullName path, AbsolutePathType type, IContentProvider? virtualContentProvider = null) - { - ContentProvider = contentProvider; - Path = path; - VirtualContentProvider = virtualContentProvider; - Type = type; - } - - public AbsolutePath(IItem item, IContentProvider? virtualContentProvider = null) - { - ContentProvider = item.Provider; - Path = item.FullName ?? throw new ArgumentException($"{nameof(item.FullName)} can not be null.", nameof(item)); - VirtualContentProvider = virtualContentProvider; - Type = item.Type; - } - - public async Task ResolveAsync(bool forceResolve = false, ItemInitializationSettings itemInitializationSettings = default) - { - var provider = VirtualContentProvider ?? ContentProvider; - return await provider.GetItemByFullNameAsync(Path, forceResolve, Type, itemInitializationSettings); - } - - public async Task ResolveAsyncSafe(bool forceResolve = false, ItemInitializationSettings itemInitializationSettings = default) - { - try - { - return await ResolveAsync(forceResolve, itemInitializationSettings); - } - catch { return null; } - } -} \ No newline at end of file diff --git a/src/Core/FileTime.Core.Models/Container.cs b/src/Core/FileTime.Core.Models/Container.cs index 7e33425..0871c79 100644 --- a/src/Core/FileTime.Core.Models/Container.cs +++ b/src/Core/FileTime.Core.Models/Container.cs @@ -4,6 +4,7 @@ using System.Reactive.Subjects; using DynamicData; using FileTime.Core.Enums; using FileTime.Core.Services; +using FileTime.Core.Timeline; namespace FileTime.Core.Models; @@ -12,7 +13,7 @@ public record Container( string DisplayName, FullName FullName, NativePath NativePath, - IAbsolutePath? Parent, + AbsolutePath? Parent, bool IsHidden, bool IsExists, DateTime? CreatedAt, @@ -20,9 +21,10 @@ public record Container( bool CanRename, string? Attributes, IContentProvider Provider, + PointInTime PointInTime, IObservable> Exceptions, ReadOnlyExtensionCollection Extensions, - IObservable>?> Items) : IContainer + IObservable>?> Items) : IContainer { BehaviorSubject IsLoading { get; } = new BehaviorSubject(false); IObservable IContainer.IsLoading => IsLoading.AsObservable(); diff --git a/src/Core/FileTime.Core.Models/Element.cs b/src/Core/FileTime.Core.Models/Element.cs index ff46f46..dfd6895 100644 --- a/src/Core/FileTime.Core.Models/Element.cs +++ b/src/Core/FileTime.Core.Models/Element.cs @@ -1,6 +1,7 @@ using System.Collections.ObjectModel; using FileTime.Core.Enums; using FileTime.Core.Services; +using FileTime.Core.Timeline; namespace FileTime.Core.Models; @@ -9,7 +10,7 @@ public record Element( string DisplayName, FullName FullName, NativePath NativePath, - IAbsolutePath? Parent, + AbsolutePath? Parent, bool IsHidden, bool IsExists, DateTime? CreatedAt, @@ -17,6 +18,7 @@ public record Element( bool CanRename, string? Attributes, IContentProvider Provider, + PointInTime PointInTime, IObservable> Exceptions, ReadOnlyExtensionCollection Extensions) : IElement { diff --git a/src/Core/FileTime.Core.Services/ContentProviderBase.cs b/src/Core/FileTime.Core.Services/ContentProviderBase.cs index ce8ea30..d88ba75 100644 --- a/src/Core/FileTime.Core.Services/ContentProviderBase.cs +++ b/src/Core/FileTime.Core.Services/ContentProviderBase.cs @@ -4,6 +4,7 @@ using System.Reactive.Subjects; using DynamicData; using FileTime.Core.Enums; using FileTime.Core.Models; +using FileTime.Core.Timeline; namespace FileTime.Core.Services; @@ -11,10 +12,10 @@ public abstract class ContentProviderBase : IContentProvider { private readonly ReadOnlyExtensionCollection _extensions; - protected BehaviorSubject>?> Items { get; } = new(null); + protected BehaviorSubject>?> Items { get; } = new(null); protected ExtensionCollection Extensions { get; } - IObservable>?> IContainer.Items => Items; + IObservable>?> IContainer.Items => Items; public string Name { get; } @@ -34,7 +35,7 @@ public abstract class ContentProviderBase : IContentProvider public IContentProvider Provider => this; - public IAbsolutePath? Parent => null; + public AbsolutePath? Parent => null; public DateTime? CreatedAt => null; @@ -45,6 +46,7 @@ public abstract class ContentProviderBase : IContentProvider IObservable IContainer.IsLoading => IsLoading.AsObservable(); public AbsolutePathType Type => AbsolutePathType.Container; + public PointInTime PointInTime { get; } = PointInTime.Eternal; public IObservable> Exceptions => Observable.Return(Enumerable.Empty()); @@ -60,21 +62,21 @@ public abstract class ContentProviderBase : IContentProvider public virtual Task OnEnter() => Task.CompletedTask; - public virtual async Task GetItemByFullNameAsync( - FullName fullName, + public virtual async Task GetItemByFullNameAsync(FullName fullName, + PointInTime pointInTime, bool forceResolve = false, AbsolutePathType forceResolvePathType = AbsolutePathType.Unknown, ItemInitializationSettings itemInitializationSettings = default) - => await GetItemByNativePathAsync(GetNativePath(fullName), forceResolve, forceResolvePathType, + => await GetItemByNativePathAsync(GetNativePath(fullName), pointInTime, forceResolve, forceResolvePathType, itemInitializationSettings); - public abstract Task GetItemByNativePathAsync( - NativePath nativePath, + public abstract Task GetItemByNativePathAsync(NativePath nativePath, + PointInTime pointInTime, bool forceResolve = false, AbsolutePathType forceResolvePathType = AbsolutePathType.Unknown, ItemInitializationSettings itemInitializationSettings = default); - public abstract Task> GetItemsByContainerAsync(FullName fullName); + public abstract Task> GetItemsByContainerAsync(FullName fullName, PointInTime pointInTime); public abstract NativePath GetNativePath(FullName fullName); public abstract Task GetContentAsync(IElement element, diff --git a/src/Core/FileTime.Core.Services/Tab.cs b/src/Core/FileTime.Core.Services/Tab.cs index 612eca1..865ac71 100644 --- a/src/Core/FileTime.Core.Services/Tab.cs +++ b/src/Core/FileTime.Core.Services/Tab.cs @@ -3,22 +3,30 @@ using System.Reactive.Subjects; using DynamicData; using DynamicData.Alias; using FileTime.Core.Models; +using FileTime.Core.Timeline; namespace FileTime.Core.Services; public class Tab : ITab { + private readonly ITimelessContentProvider _timelessContentProvider; private readonly BehaviorSubject _currentLocation = new(null); - private readonly BehaviorSubject _currentSelectedItem = new(null); + private readonly BehaviorSubject _currentSelectedItem = new(null); private readonly SourceList _itemFilters = new(); - private IAbsolutePath? _currentSelectedItemCached; + private AbsolutePath? _currentSelectedItemCached; + private PointInTime _currentPointInTime; public IObservable CurrentLocation { get; } public IObservable>?> CurrentItems { get; } - public IObservable CurrentSelectedItem { get; } + public IObservable CurrentSelectedItem { get; } - public Tab() + public Tab(ITimelessContentProvider timelessContentProvider) { + _timelessContentProvider = timelessContentProvider; + _currentPointInTime = null!; + + _timelessContentProvider.CurrentPointInTime.Subscribe(p => _currentPointInTime = p); + CurrentLocation = _currentLocation.DistinctUntilChanged().Publish(null).RefCount(); CurrentItems = Observable.Merge( @@ -32,9 +40,9 @@ public class Tab : ITab (items, filters) => items?.Where(i => filters.All(f => f.Filter(i)))), CurrentLocation .Where(c => c is null) - .Select(_ => (IObservable>?) null) + .Select(_ => (IObservable>?)null) ) - .Publish((IObservable>?) null) + .Publish((IObservable>?)null) .RefCount(); CurrentSelectedItem = @@ -66,22 +74,22 @@ public class Tab : ITab }); } - private async Task MapItem(IAbsolutePath item) => await item.ResolveAsync(true); + private async Task MapItem(AbsolutePath item) => await item.ResolveAsync(true); public void Init(IContainer currentLocation) { _currentLocation.OnNext(currentLocation); } - private static IAbsolutePath? GetSelectedItemByItems(IEnumerable items) + private AbsolutePath? GetSelectedItemByItems(IEnumerable items) { //TODO: - return new AbsolutePath(items.First()); + return new AbsolutePath(_timelessContentProvider, items.First()); } public void SetCurrentLocation(IContainer newLocation) => _currentLocation.OnNext(newLocation); - public void SetSelectedItem(IAbsolutePath newSelectedItem) => _currentSelectedItem.OnNext(newSelectedItem); + public void SetSelectedItem(AbsolutePath newSelectedItem) => _currentSelectedItem.OnNext(newSelectedItem); public void AddItemFilter(ItemFilter filter) => _itemFilters.Add(filter); public void RemoveItemFilter(ItemFilter filter) => _itemFilters.Remove(filter); @@ -95,7 +103,8 @@ public class Tab : ITab public async Task OpenSelected() { if (_currentSelectedItemCached == null) return; - var resolvedSelectedItem = await _currentSelectedItemCached.ContentProvider.GetItemByFullNameAsync(_currentSelectedItemCached.Path); + var resolvedSelectedItem = + await _currentSelectedItemCached.TimelessProvider.GetItemByFullNameAsync(_currentSelectedItemCached.Path, _currentPointInTime); if (resolvedSelectedItem is not IContainer resolvedContainer) return; SetCurrentLocation(resolvedContainer); diff --git a/src/Core/FileTime.Core.Timeline/CommandScheduler.cs b/src/Core/FileTime.Core.Timeline/CommandScheduler.cs new file mode 100644 index 0000000..98d58bf --- /dev/null +++ b/src/Core/FileTime.Core.Timeline/CommandScheduler.cs @@ -0,0 +1,6 @@ +namespace FileTime.Core.Timeline; + +public class CommandScheduler : ICommandScheduler +{ + +} \ No newline at end of file diff --git a/src/Core/FileTime.Core.Timeline/FileTime.Core.Timeline.csproj b/src/Core/FileTime.Core.Timeline/FileTime.Core.Timeline.csproj new file mode 100644 index 0000000..af99e5a --- /dev/null +++ b/src/Core/FileTime.Core.Timeline/FileTime.Core.Timeline.csproj @@ -0,0 +1,13 @@ + + + + net6.0 + enable + enable + + + + + + + diff --git a/src/Core/FileTime.Core.Timeline/TimelessContentProvider.cs b/src/Core/FileTime.Core.Timeline/TimelessContentProvider.cs new file mode 100644 index 0000000..b22c908 --- /dev/null +++ b/src/Core/FileTime.Core.Timeline/TimelessContentProvider.cs @@ -0,0 +1,38 @@ +using System.Reactive.Subjects; +using FileTime.Core.Enums; +using FileTime.Core.Models; +using FileTime.Core.Services; +using Microsoft.Extensions.DependencyInjection; + +namespace FileTime.Core.Timeline; + +public class TimelessContentProvider : ITimelessContentProvider +{ + private readonly Lazy> _contentProviders; + + public BehaviorSubject CurrentPointInTime { get; } = + new BehaviorSubject(PointInTime.Present); + + public TimelessContentProvider(IServiceProvider serviceProvider) + { + _contentProviders = + new Lazy>(() => serviceProvider.GetServices().ToList()); + } + + public async Task GetItemByFullNameAsync(FullName fullName, PointInTime? pointInTime, + bool forceResolve = false, + AbsolutePathType forceResolvePathType = AbsolutePathType.Unknown, + ItemInitializationSettings itemInitializationSettings = default) + { + //TODO time modifications + var contentProviderName = fullName.Path.Split(Constants.SeparatorChar).FirstOrDefault(); + var contentProvider = _contentProviders.Value.FirstOrDefault(p => p.Name == contentProviderName); + + if (contentProvider is null) + throw new Exception($"No content provider is found for name '{contentProviderName}'"); + + return await contentProvider.GetItemByFullNameAsync(fullName, pointInTime ?? PointInTime.Present, + forceResolve, forceResolvePathType, + itemInitializationSettings); + } +} \ No newline at end of file diff --git a/src/FileTime.sln b/src/FileTime.sln index a264f79..1f96016 100644 --- a/src/FileTime.sln +++ b/src/FileTime.sln @@ -53,6 +53,8 @@ Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "FileTime.Tools", "Tools\Fil EndProject Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "FileTime.Core.Command", "Core\FileTime.Core.Command\FileTime.Core.Command.csproj", "{1846BE76-8F68-4FC3-8954-871F14743F0B}" EndProject +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "FileTime.Core.Timeline", "Core\FileTime.Core.Timeline\FileTime.Core.Timeline.csproj", "{2AC5CAFF-EBDA-4C6E-BCFF-304B651F2906}" +EndProject Global GlobalSection(SolutionConfigurationPlatforms) = preSolution Debug|Any CPU = Debug|Any CPU @@ -127,6 +129,10 @@ Global {1846BE76-8F68-4FC3-8954-871F14743F0B}.Debug|Any CPU.Build.0 = Debug|Any CPU {1846BE76-8F68-4FC3-8954-871F14743F0B}.Release|Any CPU.ActiveCfg = Release|Any CPU {1846BE76-8F68-4FC3-8954-871F14743F0B}.Release|Any CPU.Build.0 = Release|Any CPU + {2AC5CAFF-EBDA-4C6E-BCFF-304B651F2906}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {2AC5CAFF-EBDA-4C6E-BCFF-304B651F2906}.Debug|Any CPU.Build.0 = Debug|Any CPU + {2AC5CAFF-EBDA-4C6E-BCFF-304B651F2906}.Release|Any CPU.ActiveCfg = Release|Any CPU + {2AC5CAFF-EBDA-4C6E-BCFF-304B651F2906}.Release|Any CPU.Build.0 = Release|Any CPU EndGlobalSection GlobalSection(SolutionProperties) = preSolution HideSolutionNode = FALSE @@ -150,6 +156,7 @@ Global {D7D1C76A-05B0-49BC-BCFF-06340E264EC1} = {01F231DE-4A65-435F-B4BB-77EE5221890C} {B7A45654-E56C-43C8-998E-0F4661395540} = {8C3CFEFE-78A5-4940-B388-D15FCE02ECE9} {1846BE76-8F68-4FC3-8954-871F14743F0B} = {3324D046-1E05-46B5-B1BA-82910D56B332} + {2AC5CAFF-EBDA-4C6E-BCFF-304B651F2906} = {3324D046-1E05-46B5-B1BA-82910D56B332} EndGlobalSection GlobalSection(ExtensibilityGlobals) = postSolution SolutionGuid = {859FB3DF-C60A-46B1-82E5-90274905D1EF} diff --git a/src/GuiApp/Avalonia/FileTime.GuiApp.Abstractions/Models/IHaveAbsolutePath.cs b/src/GuiApp/Avalonia/FileTime.GuiApp.Abstractions/Models/IHaveFullPath.cs similarity index 50% rename from src/GuiApp/Avalonia/FileTime.GuiApp.Abstractions/Models/IHaveAbsolutePath.cs rename to src/GuiApp/Avalonia/FileTime.GuiApp.Abstractions/Models/IHaveFullPath.cs index 6e3217d..e423d0a 100644 --- a/src/GuiApp/Avalonia/FileTime.GuiApp.Abstractions/Models/IHaveAbsolutePath.cs +++ b/src/GuiApp/Avalonia/FileTime.GuiApp.Abstractions/Models/IHaveFullPath.cs @@ -2,7 +2,7 @@ using FileTime.Core.Models; namespace FileTime.GuiApp.Models; -public interface IHaveAbsolutePath +public interface IHaveFullPath { - IAbsolutePath Path { get; } + FullName Path { get; } } \ No newline at end of file diff --git a/src/GuiApp/Avalonia/FileTime.GuiApp.Abstractions/ViewModels/RootDriveInfo.cs b/src/GuiApp/Avalonia/FileTime.GuiApp.Abstractions/ViewModels/RootDriveInfo.cs index e52f4c3..d96c834 100644 --- a/src/GuiApp/Avalonia/FileTime.GuiApp.Abstractions/ViewModels/RootDriveInfo.cs +++ b/src/GuiApp/Avalonia/FileTime.GuiApp.Abstractions/ViewModels/RootDriveInfo.cs @@ -7,7 +7,7 @@ using IContainer = FileTime.Core.Models.IContainer; namespace FileTime.GuiApp.ViewModels; -public partial class RootDriveInfo : IHaveAbsolutePath, INotifyPropertyChanged +public partial class RootDriveInfo : IHaveFullPath, INotifyPropertyChanged { private readonly DriveInfo _driveInfo; @@ -25,7 +25,7 @@ public partial class RootDriveInfo : IHaveAbsolutePath, INotifyPropertyChanged [Notify] public long UsedPercentage => Size == 0 ? 0 : Used * 100 / Size; - public IAbsolutePath Path { get; } + public FullName Path { get; } public RootDriveInfo(DriveInfo driveInfo, IContainer container) { @@ -42,7 +42,7 @@ public partial class RootDriveInfo : IHaveAbsolutePath, INotifyPropertyChanged { } - Path = new AbsolutePath(container); + Path = container.FullName ?? throw new NullReferenceException($"Container does not have a {nameof(FullName)}"); Refresh(); } diff --git a/src/GuiApp/Avalonia/FileTime.GuiApp/Services/RootDriveInfoService.cs b/src/GuiApp/Avalonia/FileTime.GuiApp/Services/RootDriveInfoService.cs index 8512849..28c4c77 100644 --- a/src/GuiApp/Avalonia/FileTime.GuiApp/Services/RootDriveInfoService.cs +++ b/src/GuiApp/Avalonia/FileTime.GuiApp/Services/RootDriveInfoService.cs @@ -5,6 +5,7 @@ using DynamicData.Binding; using FileTime.App.Core.Models; using FileTime.App.Core.Services; using FileTime.Core.Models; +using FileTime.Core.Timeline; using FileTime.GuiApp.ViewModels; using FileTime.Providers.Local; using IContainer = FileTime.Core.Models.IContainer; @@ -14,14 +15,14 @@ namespace FileTime.GuiApp.Services; public class RootDriveInfoService : IStartupHandler { private readonly SourceList _rootDrives = new(); - private readonly IObservable> _localContentProviderStream; + private readonly IObservable> _localContentProviderStream; - public RootDriveInfoService(IGuiAppState guiAppState, ILocalContentProvider localContentProvider) + public RootDriveInfoService(IGuiAppState guiAppState, ILocalContentProvider localContentProvider, ITimelessContentProvider timelessContentProvider) { InitRootDrives(); - var localContentProviderAsList = new SourceList(); - localContentProviderAsList.Add(new AbsolutePath(localContentProvider)); + var localContentProviderAsList = new SourceList(); + localContentProviderAsList.Add(new AbsolutePath(timelessContentProvider, localContentProvider)); _localContentProviderStream = localContentProviderAsList.Connect(); var rootDriveInfos = Observable.CombineLatest( @@ -30,7 +31,7 @@ public class RootDriveInfoService : IStartupHandler (items, drives) => { return items is null - ? Observable.Empty>() + ? Observable.Empty>() : items! .Or(new[] { _localContentProviderStream }) .Transform(i => (Path: i, Drive: drives.FirstOrDefault(d => diff --git a/src/GuiApp/Avalonia/FileTime.GuiApp/ViewModels/MainWindowViewModel.cs b/src/GuiApp/Avalonia/FileTime.GuiApp/ViewModels/MainWindowViewModel.cs index 4023450..f9b838c 100644 --- a/src/GuiApp/Avalonia/FileTime.GuiApp/ViewModels/MainWindowViewModel.cs +++ b/src/GuiApp/Avalonia/FileTime.GuiApp/ViewModels/MainWindowViewModel.cs @@ -1,9 +1,11 @@ using System.Reflection; using Avalonia.Input; using FileTime.App.Core.Services; +using FileTime.App.Core.UserCommand; using FileTime.App.Core.ViewModels; using FileTime.Core.Models; using FileTime.Core.Services; +using FileTime.Core.Timeline; using FileTime.GuiApp.Services; using FileTime.Providers.Local; using Microsoft.Extensions.DependencyInjection; @@ -22,6 +24,7 @@ namespace FileTime.GuiApp.ViewModels; [Inject(typeof(LifecycleService), PropertyName = "_lifecycleService")] [Inject(typeof(IItemPreviewService), PropertyAccessModifier = AccessModifier.Public)] [Inject(typeof(IDialogService), PropertyAccessModifier = AccessModifier.Public)] +[Inject(typeof(ITimelessContentProvider), PropertyName = "_timelessContentProvider")] public partial class MainWindowViewModel : IMainWindowViewModelBase { public bool Loading => false; @@ -42,12 +45,14 @@ public partial class MainWindowViewModel : IMainWindowViewModelBase versionString += $" ({version.Revision})"; } } + Title = "FileTime " + versionString; //TODO: refactor if (AppState.Tabs.Count == 0) { - var tab = _serviceProvider.GetInitableResolver(_localContentProvider).GetRequiredService(); + var tab = _serviceProvider.GetInitableResolver(_localContentProvider) + .GetRequiredService(); var tabViewModel = _serviceProvider.GetInitableResolver(tab, 1).GetRequiredService(); _appState.AddTab(tabViewModel); @@ -58,4 +63,12 @@ public partial class MainWindowViewModel : IMainWindowViewModelBase { _keyInputHandlerService.ProcessKeyDown(key, keyModifiers, setHandled); } + + public async Task OpenContainerByFullName(FullName fullName) + { + var resolvedItem = await _timelessContentProvider.GetItemByFullNameAsync(fullName, PointInTime.Present); + if (resolvedItem is not IContainer resolvedContainer) return; + await UserCommandHandlerService.HandleCommandAsync( + new OpenContainerCommand(new AbsolutePath(_timelessContentProvider, resolvedContainer))); + } } \ No newline at end of file diff --git a/src/GuiApp/Avalonia/FileTime.GuiApp/Views/MainWindow.axaml.cs b/src/GuiApp/Avalonia/FileTime.GuiApp/Views/MainWindow.axaml.cs index 4818126..fc2c7c0 100644 --- a/src/GuiApp/Avalonia/FileTime.GuiApp/Views/MainWindow.axaml.cs +++ b/src/GuiApp/Avalonia/FileTime.GuiApp/Views/MainWindow.axaml.cs @@ -74,12 +74,12 @@ public partial class MainWindow : Window && e.GetCurrentPoint(this).Properties.IsLeftButtonPressed && sender is StyledElement control) { - IAbsolutePath? path = null; - if (control.DataContext is IHaveAbsolutePath { Path: { } } haveAbsolutePath) + FullName? path = null; + if (control.DataContext is IHaveFullPath { Path: { } } hasFullPath) { - path = haveAbsolutePath.Path; + path = hasFullPath.Path; } - else if (control.DataContext is IAbsolutePath p) + else if (control.DataContext is FullName p) { path = p; } @@ -97,10 +97,7 @@ public partial class MainWindow : Window if (path is null) return; - var resolvedItem = await path.ResolveAsync(); - if (resolvedItem is not IContainer resolvedContainer) return; - await ViewModel.UserCommandHandlerService.HandleCommandAsync( - new OpenContainerCommand(new AbsolutePath(resolvedContainer))); + await ViewModel.OpenContainerByFullName(path); e.Handled = true; } } diff --git a/src/Providers/FileTime.Providers.Local/LocalContentProvider.cs b/src/Providers/FileTime.Providers.Local/LocalContentProvider.cs index cbae7d7..7a9a022 100644 --- a/src/Providers/FileTime.Providers.Local/LocalContentProvider.cs +++ b/src/Providers/FileTime.Providers.Local/LocalContentProvider.cs @@ -6,16 +6,19 @@ using FileTime.App.Core.Models; using FileTime.Core.Enums; using FileTime.Core.Models; using FileTime.Core.Services; +using FileTime.Core.Timeline; namespace FileTime.Providers.Local; public sealed partial class LocalContentProvider : ContentProviderBase, ILocalContentProvider { - private readonly SourceList _rootDirectories = new(); + private readonly ITimelessContentProvider _timelessContentProvider; + private readonly SourceList _rootDirectories = new(); private readonly bool _isCaseInsensitive; - public LocalContentProvider() : base("local") + public LocalContentProvider(ITimelessContentProvider timelessContentProvider) : base("local") { + _timelessContentProvider = timelessContentProvider; _isCaseInsensitive = RuntimeInformation.IsOSPlatform(OSPlatform.Windows); RefreshRootDirectories(); @@ -39,12 +42,12 @@ public sealed partial class LocalContentProvider : ContentProviderBase, ILocalCo _rootDirectories.Edit(actions => { actions.Clear(); - actions.AddRange(rootDirectories.Select(DirectoryToAbsolutePath)); + actions.AddRange(rootDirectories.Select(d => DirectoryToAbsolutePath(d, PointInTime.Present))); }); } - public override Task GetItemByNativePathAsync( - NativePath nativePath, + public override Task GetItemByNativePathAsync(NativePath nativePath, + PointInTime pointInTime, bool forceResolve = false, AbsolutePathType forceResolvePathType = AbsolutePathType.Unknown, ItemInitializationSettings itemInitializationSettings = default) @@ -61,12 +64,13 @@ public sealed partial class LocalContentProvider : ContentProviderBase, ILocalCo { return Task.FromResult((IItem)DirectoryToContainer( new DirectoryInfo(path!.TrimEnd(Path.DirectorySeparatorChar) + Path.DirectorySeparatorChar), + pointInTime, !itemInitializationSettings.SkipChildInitialization) ); } else if (File.Exists(path)) { - return Task.FromResult((IItem)FileToElement(new FileInfo(path))); + return Task.FromResult((IItem)FileToElement(new FileInfo(path), pointInTime)); } var type = forceResolvePathType switch @@ -98,7 +102,11 @@ public sealed partial class LocalContentProvider : ContentProviderBase, ILocalCo return forceResolvePathType switch { AbsolutePathType.Container => Task.FromResult( - (IItem)CreateEmptyContainer(nativePath, Observable.Return(new List() { innerException })) + (IItem)CreateEmptyContainer( + nativePath, + pointInTime, + Observable.Return(new List() { innerException }) + ) ), AbsolutePathType.Element => Task.FromResult(CreateEmptyElement(nativePath)), _ => throw new Exception( @@ -108,6 +116,7 @@ public sealed partial class LocalContentProvider : ContentProviderBase, ILocalCo } private Container CreateEmptyContainer(NativePath nativePath, + PointInTime pointInTime, IObservable>? exceptions = null) { var nonNullExceptions = exceptions ?? Observable.Return(Enumerable.Empty()); @@ -116,7 +125,8 @@ public sealed partial class LocalContentProvider : ContentProviderBase, ILocalCo var parentFullName = fullName.GetParent(); var parent = new AbsolutePath( - this, + _timelessContentProvider, + pointInTime, parentFullName ?? new FullName(""), AbsolutePathType.Container); @@ -133,9 +143,10 @@ public sealed partial class LocalContentProvider : ContentProviderBase, ILocalCo false, "???", this, + pointInTime, nonNullExceptions, new ExtensionCollection().AsReadOnly(), - Observable.Return>?>(null) + Observable.Return>?>(null) ); } @@ -144,41 +155,43 @@ public sealed partial class LocalContentProvider : ContentProviderBase, ILocalCo throw new NotImplementedException(); } - public override Task> GetItemsByContainerAsync(FullName fullName) - => Task.FromResult(GetItemsByContainer(fullName)); + public override Task> GetItemsByContainerAsync(FullName fullName, PointInTime pointInTime) + => Task.FromResult(GetItemsByContainer(fullName, pointInTime)); - private List GetItemsByContainer(FullName fullName) - => GetItemsByContainer(new DirectoryInfo(GetNativePath(fullName).Path)); + private List GetItemsByContainer(FullName fullName, PointInTime pointInTime) + => GetItemsByContainer(new DirectoryInfo(GetNativePath(fullName).Path), pointInTime); - private List GetItemsByContainer(DirectoryInfo directoryInfo) + private List GetItemsByContainer(DirectoryInfo directoryInfo, PointInTime pointInTime) => directoryInfo .GetDirectories() - .Select(DirectoryToAbsolutePath) + .Select(d => DirectoryToAbsolutePath(d, pointInTime)) .Concat( directoryInfo .GetFiles() - .Select(FileToAbsolutePath) + .Select(f => FileToAbsolutePath(f, pointInTime)) ) .ToList(); - private IAbsolutePath DirectoryToAbsolutePath(DirectoryInfo directoryInfo) + private AbsolutePath DirectoryToAbsolutePath(DirectoryInfo directoryInfo, PointInTime pointInTime) { var fullName = GetFullName(directoryInfo); - return new AbsolutePath(this, fullName, AbsolutePathType.Container); + return new AbsolutePath(_timelessContentProvider, pointInTime, fullName, AbsolutePathType.Container); } - private IAbsolutePath FileToAbsolutePath(FileInfo file) + private AbsolutePath FileToAbsolutePath(FileInfo file, PointInTime pointInTime) { var fullName = GetFullName(file); - return new AbsolutePath(this, fullName, AbsolutePathType.Element); + return new AbsolutePath(_timelessContentProvider, pointInTime, fullName, AbsolutePathType.Element); } - private Container DirectoryToContainer(DirectoryInfo directoryInfo, bool initializeChildren = true) + private Container DirectoryToContainer(DirectoryInfo directoryInfo, PointInTime pointInTime, + bool initializeChildren = true) { var fullName = GetFullName(directoryInfo.FullName); var parentFullName = fullName.GetParent(); var parent = new AbsolutePath( - this, + _timelessContentProvider, + pointInTime, parentFullName ?? new FullName(""), AbsolutePathType.Container); var exceptions = new BehaviorSubject>(Enumerable.Empty()); @@ -196,20 +209,21 @@ public sealed partial class LocalContentProvider : ContentProviderBase, ILocalCo true, GetDirectoryAttributes(directoryInfo), this, + pointInTime, exceptions, new ExtensionCollection().AsReadOnly(), Observable.FromAsync(async () => await Task.Run(InitChildren)) ); - Task>?> InitChildren() + Task>?> InitChildren() { - SourceList? result = null; + SourceList? result = null; try { - var items = initializeChildren ? (List?)GetItemsByContainer(directoryInfo) : null; + var items = initializeChildren ? (List?)GetItemsByContainer(directoryInfo, pointInTime) : null; if (items != null) { - result = new SourceList(); + result = new SourceList(); result.AddRange(items); } } @@ -222,12 +236,13 @@ public sealed partial class LocalContentProvider : ContentProviderBase, ILocalCo } } - private Element FileToElement(FileInfo fileInfo) + private Element FileToElement(FileInfo fileInfo, PointInTime pointInTime) { var fullName = GetFullName(fileInfo); var parentFullName = fullName.GetParent() ?? throw new Exception($"Path does not have parent: '{fileInfo.FullName}'"); - var parent = new AbsolutePath(this, parentFullName, AbsolutePathType.Container); + var parent = new AbsolutePath(_timelessContentProvider, pointInTime, parentFullName, + AbsolutePathType.Container); var extensions = new ExtensionCollection() { @@ -247,6 +262,7 @@ public sealed partial class LocalContentProvider : ContentProviderBase, ILocalCo true, GetFileAttributes(fileInfo), this, + pointInTime, Observable.Return(Enumerable.Empty()), extensions.AsReadOnly() ); @@ -276,13 +292,14 @@ public sealed partial class LocalContentProvider : ContentProviderBase, ILocalCo if (cancellationToken.IsCancellationRequested) return null; if (!File.Exists(element.NativePath!.Path)) throw new FileNotFoundException("File does not exist", element.NativePath.Path); - - await using var reader = new FileStream(element.NativePath!.Path, FileMode.Open, FileAccess.Read, FileShare.Read, + + await using var reader = new FileStream(element.NativePath!.Path, FileMode.Open, FileAccess.Read, + FileShare.Read, bufferSize: 1, // bufferSize == 1 used to avoid unnecessary buffer in FileStream FileOptions.Asynchronous | FileOptions.SequentialScan); var realFileSize = new FileInfo(element.NativePath!.Path).Length; - + var size = maxLength ?? realFileSize switch { > int.MaxValue => int.MaxValue,