Command execution, CreateContainer command WIP
This commit is contained in:
@@ -0,0 +1,9 @@
|
|||||||
|
using System.Collections.ObjectModel;
|
||||||
|
|
||||||
|
namespace FileTime.App.Core.Extensions;
|
||||||
|
|
||||||
|
public static class DisposableExtensions
|
||||||
|
{
|
||||||
|
public static void AddToDisposables(this IDisposable disposable, ICollection<IDisposable> collection)
|
||||||
|
=> collection.Add(disposable);
|
||||||
|
}
|
||||||
@@ -7,7 +7,7 @@ using InitableService;
|
|||||||
|
|
||||||
namespace FileTime.App.Core.ViewModels;
|
namespace FileTime.App.Core.ViewModels;
|
||||||
|
|
||||||
public interface ITabViewModel : IInitable<ITab, int>
|
public interface ITabViewModel : IInitable<ITab, int>, IDisposable
|
||||||
{
|
{
|
||||||
ITab? Tab { get; }
|
ITab? Tab { get; }
|
||||||
int TabNumber { get; }
|
int TabNumber { get; }
|
||||||
|
|||||||
@@ -22,6 +22,7 @@ public class ItemManipulationUserCommandHandlerService : UserCommandHandlerServi
|
|||||||
private readonly IInputInterface _inputInterface;
|
private readonly IInputInterface _inputInterface;
|
||||||
private readonly ILogger<ItemManipulationUserCommandHandlerService> _logger;
|
private readonly ILogger<ItemManipulationUserCommandHandlerService> _logger;
|
||||||
private readonly ITimelessContentProvider _timelessContentProvider;
|
private readonly ITimelessContentProvider _timelessContentProvider;
|
||||||
|
private readonly ICommandScheduler _commandScheduler;
|
||||||
private readonly BindedCollection<FullName>? _markedItems;
|
private readonly BindedCollection<FullName>? _markedItems;
|
||||||
private PointInTime _currentPointInTime;
|
private PointInTime _currentPointInTime;
|
||||||
|
|
||||||
@@ -31,13 +32,15 @@ public class ItemManipulationUserCommandHandlerService : UserCommandHandlerServi
|
|||||||
IClipboardService clipboardService,
|
IClipboardService clipboardService,
|
||||||
IInputInterface inputInterface,
|
IInputInterface inputInterface,
|
||||||
ILogger<ItemManipulationUserCommandHandlerService> logger,
|
ILogger<ItemManipulationUserCommandHandlerService> logger,
|
||||||
ITimelessContentProvider timelessContentProvider) : base(appState, timelessContentProvider)
|
ITimelessContentProvider timelessContentProvider,
|
||||||
|
ICommandScheduler commandScheduler) : base(appState, timelessContentProvider)
|
||||||
{
|
{
|
||||||
_userCommandHandlerService = userCommandHandlerService;
|
_userCommandHandlerService = userCommandHandlerService;
|
||||||
_clipboardService = clipboardService;
|
_clipboardService = clipboardService;
|
||||||
_inputInterface = inputInterface;
|
_inputInterface = inputInterface;
|
||||||
_logger = logger;
|
_logger = logger;
|
||||||
_timelessContentProvider = timelessContentProvider;
|
_timelessContentProvider = timelessContentProvider;
|
||||||
|
_commandScheduler = commandScheduler;
|
||||||
_currentPointInTime = null!;
|
_currentPointInTime = null!;
|
||||||
|
|
||||||
SaveSelectedTab(t => _selectedTab = t);
|
SaveSelectedTab(t => _selectedTab = t);
|
||||||
@@ -128,5 +131,6 @@ public class ItemManipulationUserCommandHandlerService : UserCommandHandlerServi
|
|||||||
var newContainerName = containerNameInput.Value;
|
var newContainerName = containerNameInput.Value;
|
||||||
|
|
||||||
var command = new CreateContainerCommand();
|
var command = new CreateContainerCommand();
|
||||||
|
await _commandScheduler.AddCommand(command);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -67,7 +67,8 @@ public class NavigationUserCommandHandlerService : UserCommandHandlerServiceBase
|
|||||||
|
|
||||||
private Task OpenSelected()
|
private Task OpenSelected()
|
||||||
{
|
{
|
||||||
if (_currentSelectedItem is not IContainerViewModel containerViewModel || containerViewModel.Container is null) return Task.CompletedTask;
|
if (_currentSelectedItem is not IContainerViewModel containerViewModel || containerViewModel.Container is null)
|
||||||
|
return Task.CompletedTask;
|
||||||
|
|
||||||
_selectedTab?.Tab?.SetCurrentLocation(containerViewModel.Container);
|
_selectedTab?.Tab?.SetCurrentLocation(containerViewModel.Container);
|
||||||
return Task.CompletedTask;
|
return Task.CompletedTask;
|
||||||
@@ -75,7 +76,8 @@ public class NavigationUserCommandHandlerService : UserCommandHandlerServiceBase
|
|||||||
|
|
||||||
private async Task GoUp()
|
private async Task GoUp()
|
||||||
{
|
{
|
||||||
if (_currentLocation?.Parent is not AbsolutePath 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);
|
_selectedTab?.Tab?.SetCurrentLocation(newContainer);
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -125,7 +127,8 @@ public class NavigationUserCommandHandlerService : UserCommandHandlerServiceBase
|
|||||||
}
|
}
|
||||||
else if (tabViewModel == null)
|
else if (tabViewModel == null)
|
||||||
{
|
{
|
||||||
var tab = _serviceProvider.GetInitableResolver<IContainer>(_currentLocation ?? _localContentProvider).GetRequiredService<ITab>();
|
var tab = _serviceProvider.GetInitableResolver<IContainer>(_currentLocation ?? _localContentProvider)
|
||||||
|
.GetRequiredService<ITab>();
|
||||||
var newTabViewModel = _serviceProvider.GetInitableResolver(tab, number).GetRequiredService<ITabViewModel>();
|
var newTabViewModel = _serviceProvider.GetInitableResolver(tab, number).GetRequiredService<ITabViewModel>();
|
||||||
|
|
||||||
_appState.AddTab(newTabViewModel);
|
_appState.AddTab(newTabViewModel);
|
||||||
@@ -144,9 +147,18 @@ public class NavigationUserCommandHandlerService : UserCommandHandlerServiceBase
|
|||||||
|
|
||||||
private Task CloseTab()
|
private Task CloseTab()
|
||||||
{
|
{
|
||||||
if (_appState.Tabs.Count < 2) return Task.CompletedTask;
|
if (_appState.Tabs.Count < 2 || _selectedTab == null) return Task.CompletedTask;
|
||||||
|
|
||||||
_appState.RemoveTab(_selectedTab!);
|
var tabToRemove = _selectedTab;
|
||||||
|
_appState.RemoveTab(tabToRemove!);
|
||||||
|
|
||||||
|
try
|
||||||
|
{
|
||||||
|
tabToRemove.Dispose();
|
||||||
|
}
|
||||||
|
catch
|
||||||
|
{
|
||||||
|
}
|
||||||
|
|
||||||
return Task.CompletedTask;
|
return Task.CompletedTask;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -13,7 +13,7 @@ using MvvmGen;
|
|||||||
namespace FileTime.App.Core.ViewModels;
|
namespace FileTime.App.Core.ViewModels;
|
||||||
|
|
||||||
[ViewModel]
|
[ViewModel]
|
||||||
public partial class TabViewModel : ITabViewModel, IDisposable
|
public partial class TabViewModel : ITabViewModel
|
||||||
{
|
{
|
||||||
private readonly IServiceProvider _serviceProvider;
|
private readonly IServiceProvider _serviceProvider;
|
||||||
private readonly IItemNameConverterService _itemNameConverterService;
|
private readonly IItemNameConverterService _itemNameConverterService;
|
||||||
@@ -70,6 +70,8 @@ public partial class TabViewModel : ITabViewModel, IDisposable
|
|||||||
Tab = tab;
|
Tab = tab;
|
||||||
TabNumber = tabNumber;
|
TabNumber = tabNumber;
|
||||||
|
|
||||||
|
tab.AddToDisposables(_disposables);
|
||||||
|
|
||||||
CurrentLocation = tab.CurrentLocation.AsObservable();
|
CurrentLocation = tab.CurrentLocation.AsObservable();
|
||||||
CurrentItems = tab.CurrentItems
|
CurrentItems = tab.CurrentItems
|
||||||
.Select(items => items?.Transform(i => MapItemToViewModel(i, ItemViewModelType.Main)))
|
.Select(items => items?.Transform(i => MapItemToViewModel(i, ItemViewModelType.Main)))
|
||||||
@@ -105,7 +107,7 @@ public partial class TabViewModel : ITabViewModel, IDisposable
|
|||||||
ParentsChildrenCollection = new(ParentsChildren);
|
ParentsChildrenCollection = new(ParentsChildren);
|
||||||
SelectedsChildrenCollection = new(SelectedsChildren);
|
SelectedsChildrenCollection = new(SelectedsChildren);
|
||||||
|
|
||||||
tab.CurrentLocation.Subscribe((_) => _markedItems.Clear());
|
tab.CurrentLocation.Subscribe((_) => _markedItems.Clear()).AddToDisposables(_disposables);
|
||||||
|
|
||||||
IObservable<IObservable<IChangeSet<IItemViewModel>>?> InitSelectedsChildren()
|
IObservable<IObservable<IChangeSet<IItemViewModel>>?> InitSelectedsChildren()
|
||||||
{
|
{
|
||||||
|
|||||||
@@ -1,8 +1,10 @@
|
|||||||
using FileTime.App.Core;
|
using FileTime.App.Core;
|
||||||
|
using FileTime.Core.Command;
|
||||||
using FileTime.Core.Services;
|
using FileTime.Core.Services;
|
||||||
using FileTime.Core.Timeline;
|
using FileTime.Core.Timeline;
|
||||||
using FileTime.Providers.Local;
|
using FileTime.Providers.Local;
|
||||||
using Microsoft.Extensions.DependencyInjection;
|
using Microsoft.Extensions.DependencyInjection;
|
||||||
|
using ICommandExecutor = FileTime.Core.Timeline.ICommandExecutor;
|
||||||
|
|
||||||
namespace FileTime.App.DependencyInjection;
|
namespace FileTime.App.DependencyInjection;
|
||||||
|
|
||||||
@@ -13,9 +15,11 @@ public static class DependencyInjection
|
|||||||
serviceCollection ??= new ServiceCollection();
|
serviceCollection ??= new ServiceCollection();
|
||||||
|
|
||||||
return serviceCollection
|
return serviceCollection
|
||||||
.AddTransient<ITab, Tab>()
|
|
||||||
.AddSingleton<ICommandScheduler, CommandScheduler>()
|
.AddSingleton<ICommandScheduler, CommandScheduler>()
|
||||||
.AddSingleton<ITimelessContentProvider, TimelessContentProvider>()
|
.AddSingleton<ITimelessContentProvider, TimelessContentProvider>()
|
||||||
|
.AddSingleton<ICommandRunner, CommandRunner>()
|
||||||
|
.AddTransient<ITab, Tab>()
|
||||||
|
.AddTransient<ILocalCommandExecutor, LocalCommandExecutor>()
|
||||||
.AddCoreAppServices()
|
.AddCoreAppServices()
|
||||||
.AddLocalServices();
|
.AddLocalServices();
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -0,0 +1,8 @@
|
|||||||
|
namespace FileTime.Core.Command;
|
||||||
|
|
||||||
|
public enum ExecutionState
|
||||||
|
{
|
||||||
|
Waiting,
|
||||||
|
Running,
|
||||||
|
Finished
|
||||||
|
}
|
||||||
@@ -5,5 +5,5 @@ namespace FileTime.Core.Command;
|
|||||||
public interface ICommand
|
public interface ICommand
|
||||||
{
|
{
|
||||||
Task<CanCommandRun> CanRun(PointInTime currentTime);
|
Task<CanCommandRun> CanRun(PointInTime currentTime);
|
||||||
Task<PointInTime?> SimulateCommand(PointInTime? currentTime);
|
Task<PointInTime> SimulateCommand(PointInTime currentTime);
|
||||||
}
|
}
|
||||||
@@ -0,0 +1,9 @@
|
|||||||
|
using FileTime.Core.Timeline;
|
||||||
|
|
||||||
|
namespace FileTime.Core.Command;
|
||||||
|
|
||||||
|
public interface ICommandHandler
|
||||||
|
{
|
||||||
|
bool CanHandle(ICommand command);
|
||||||
|
Task ExecuteAsync(ICommand command);
|
||||||
|
}
|
||||||
@@ -0,0 +1,8 @@
|
|||||||
|
using FileTime.Core.Timeline;
|
||||||
|
|
||||||
|
namespace FileTime.Core.Command;
|
||||||
|
|
||||||
|
public interface ICommandRunner
|
||||||
|
{
|
||||||
|
Task RunCommandAsync(ICommand command);
|
||||||
|
}
|
||||||
@@ -4,5 +4,5 @@ namespace FileTime.Core.Command;
|
|||||||
|
|
||||||
public interface IExecutableCommand : ICommand
|
public interface IExecutableCommand : ICommand
|
||||||
{
|
{
|
||||||
Task Execute(ICommandScheduler commandScheduler);
|
Task Execute();
|
||||||
}
|
}
|
||||||
@@ -0,0 +1,73 @@
|
|||||||
|
using System.Reactive.Linq;
|
||||||
|
using DynamicData;
|
||||||
|
using FileTime.Core.Models;
|
||||||
|
|
||||||
|
namespace FileTime.Core.Extensions;
|
||||||
|
|
||||||
|
public static class DynamicDataExtensions
|
||||||
|
{
|
||||||
|
private class DisposableContext<TParam, TTaskResult>
|
||||||
|
{
|
||||||
|
private readonly Func<TParam, TTaskResult> _transformResult;
|
||||||
|
private readonly TaskCompletionSource<TTaskResult?> _taskCompletionSource;
|
||||||
|
public IDisposable? Disposable { get; set; }
|
||||||
|
|
||||||
|
public DisposableContext(Func<TParam, TTaskResult> transformResult,
|
||||||
|
TaskCompletionSource<TTaskResult?> taskCompletionSource, IDisposable? disposable = null)
|
||||||
|
{
|
||||||
|
_transformResult = transformResult;
|
||||||
|
_taskCompletionSource = taskCompletionSource;
|
||||||
|
Disposable = disposable;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void OnNext(TParam param)
|
||||||
|
{
|
||||||
|
Disposable?.Dispose();
|
||||||
|
var result = _transformResult(param);
|
||||||
|
_taskCompletionSource.SetResult(result);
|
||||||
|
}
|
||||||
|
|
||||||
|
public void OnError(Exception ex)
|
||||||
|
{
|
||||||
|
Disposable?.Dispose();
|
||||||
|
_taskCompletionSource.SetException(ex);
|
||||||
|
}
|
||||||
|
|
||||||
|
public void OnCompleted()
|
||||||
|
{
|
||||||
|
Disposable?.Dispose();
|
||||||
|
_taskCompletionSource.SetResult(default(TTaskResult));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public static async Task<IEnumerable<AbsolutePath>?> GetItemsAsync(
|
||||||
|
this IObservable<IObservable<IChangeSet<AbsolutePath>>?> stream)
|
||||||
|
=> await GetItemsAsync(stream
|
||||||
|
.Select(s =>
|
||||||
|
s is null
|
||||||
|
? new SourceList<AbsolutePath>().Connect().StartWithEmpty().ToCollection()
|
||||||
|
: s.ToCollection())
|
||||||
|
.Switch());
|
||||||
|
|
||||||
|
public static async Task<IEnumerable<AbsolutePath>?> GetItemsAsync(
|
||||||
|
this IObservable<IChangeSet<AbsolutePath>> stream)
|
||||||
|
=> await GetItemsAsync(stream.ToCollection());
|
||||||
|
|
||||||
|
private static Task<IEnumerable<AbsolutePath>?> GetItemsAsync(
|
||||||
|
this IObservable<IReadOnlyCollection<AbsolutePath>> stream)
|
||||||
|
{
|
||||||
|
var taskCompletionSource = new TaskCompletionSource<IEnumerable<AbsolutePath>?>();
|
||||||
|
var context = new DisposableContext<IReadOnlyCollection<AbsolutePath>, IEnumerable<AbsolutePath>?>(r => r,
|
||||||
|
taskCompletionSource);
|
||||||
|
|
||||||
|
var disposable = stream
|
||||||
|
.Subscribe(
|
||||||
|
context.OnNext,
|
||||||
|
context.OnError,
|
||||||
|
context.OnCompleted
|
||||||
|
);
|
||||||
|
context.Disposable = disposable;
|
||||||
|
|
||||||
|
return taskCompletionSource.Task;
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -24,4 +24,8 @@
|
|||||||
<ProjectReference Include="..\..\Library\InitableService\InitableService.csproj" />
|
<ProjectReference Include="..\..\Library\InitableService\InitableService.csproj" />
|
||||||
</ItemGroup>
|
</ItemGroup>
|
||||||
|
|
||||||
|
<ItemGroup>
|
||||||
|
<Folder Include="ContentAccess" />
|
||||||
|
</ItemGroup>
|
||||||
|
|
||||||
</Project>
|
</Project>
|
||||||
|
|||||||
@@ -4,8 +4,6 @@ public record FullName(string Path)
|
|||||||
{
|
{
|
||||||
public FullName? GetParent()
|
public FullName? GetParent()
|
||||||
{
|
{
|
||||||
if (Path is null) return null;
|
|
||||||
|
|
||||||
var pathParts = Path.TrimEnd(Constants.SeparatorChar).Split(Constants.SeparatorChar);
|
var pathParts = Path.TrimEnd(Constants.SeparatorChar).Split(Constants.SeparatorChar);
|
||||||
return pathParts.Length switch
|
return pathParts.Length switch
|
||||||
{
|
{
|
||||||
@@ -13,4 +11,10 @@ public record FullName(string Path)
|
|||||||
_ => null
|
_ => null
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public string GetName()
|
||||||
|
=> Path.Split(Constants.SeparatorChar).Last();
|
||||||
|
|
||||||
|
public FullName GetChild(string childName)
|
||||||
|
=> new FullName(Path + Constants.SeparatorChar + childName);
|
||||||
}
|
}
|
||||||
@@ -4,7 +4,7 @@ using InitableService;
|
|||||||
|
|
||||||
namespace FileTime.Core.Services;
|
namespace FileTime.Core.Services;
|
||||||
|
|
||||||
public interface ITab : IInitable<IContainer>
|
public interface ITab : IInitable<IContainer>, IDisposable
|
||||||
{
|
{
|
||||||
IObservable<IContainer?> CurrentLocation { get; }
|
IObservable<IContainer?> CurrentLocation { get; }
|
||||||
IObservable<AbsolutePath?> CurrentSelectedItem { get; }
|
IObservable<AbsolutePath?> CurrentSelectedItem { get; }
|
||||||
|
|||||||
@@ -7,6 +7,7 @@ public class CommandTimeState
|
|||||||
public ICommand Command { get; }
|
public ICommand Command { get; }
|
||||||
public CanCommandRun CanRun { get; private set; } = CanCommandRun.False;
|
public CanCommandRun CanRun { get; private set; } = CanCommandRun.False;
|
||||||
public bool ForceRun { get; set; }
|
public bool ForceRun { get; set; }
|
||||||
|
public ExecutionState ExecutionState { get; set; }
|
||||||
|
|
||||||
public CommandTimeState(ICommand command, PointInTime? startTime)
|
public CommandTimeState(ICommand command, PointInTime? startTime)
|
||||||
{
|
{
|
||||||
|
|||||||
@@ -0,0 +1,9 @@
|
|||||||
|
using FileTime.Core.Command;
|
||||||
|
|
||||||
|
namespace FileTime.Core.Timeline;
|
||||||
|
|
||||||
|
public interface ICommandExecutor
|
||||||
|
{
|
||||||
|
void ExecuteCommand(ICommand command);
|
||||||
|
event EventHandler<ICommand> CommandFinished;
|
||||||
|
}
|
||||||
@@ -1,6 +1,8 @@
|
|||||||
|
using FileTime.Core.Command;
|
||||||
|
|
||||||
namespace FileTime.Core.Timeline;
|
namespace FileTime.Core.Timeline;
|
||||||
|
|
||||||
public interface ICommandScheduler
|
public interface ICommandScheduler
|
||||||
{
|
{
|
||||||
|
Task AddCommand(ICommand command, int? batchId = null, bool toNewBatch = false);
|
||||||
}
|
}
|
||||||
@@ -0,0 +1,6 @@
|
|||||||
|
namespace FileTime.Core.Timeline;
|
||||||
|
|
||||||
|
public interface ILocalCommandExecutor : ICommandExecutor
|
||||||
|
{
|
||||||
|
|
||||||
|
}
|
||||||
@@ -5,7 +5,7 @@ namespace FileTime.Core.Timeline;
|
|||||||
public class ParallelCommands
|
public class ParallelCommands
|
||||||
{
|
{
|
||||||
private static ushort _idCounter;
|
private static ushort _idCounter;
|
||||||
public List<CommandTimeState> _commands;
|
private List<CommandTimeState> _commands;
|
||||||
public ushort Id { get; }
|
public ushort Id { get; }
|
||||||
public IReadOnlyList<CommandTimeState> Commands { get; }
|
public IReadOnlyList<CommandTimeState> Commands { get; }
|
||||||
public PointInTime? Result { get; private set; }
|
public PointInTime? Result { get; private set; }
|
||||||
|
|||||||
@@ -2,10 +2,11 @@ namespace FileTime.Core.Timeline;
|
|||||||
|
|
||||||
public class PointInTime
|
public class PointInTime
|
||||||
{
|
{
|
||||||
private readonly List<Difference> _differences;
|
|
||||||
public static readonly PointInTime Eternal = new PointInTime();
|
public static readonly PointInTime Eternal = new PointInTime();
|
||||||
public static readonly PointInTime Present = new PointInTime();
|
public static readonly PointInTime Present = new PointInTime();
|
||||||
|
|
||||||
|
private readonly List<Difference> _differences;
|
||||||
|
|
||||||
public IReadOnlyList<Difference> Differences { get; }
|
public IReadOnlyList<Difference> Differences { get; }
|
||||||
|
|
||||||
private PointInTime() : this(new List<Difference>())
|
private PointInTime() : this(new List<Difference>())
|
||||||
@@ -26,6 +27,14 @@ public class PointInTime
|
|||||||
public PointInTime WithDifferences(IEnumerable<Difference> differences) =>
|
public PointInTime WithDifferences(IEnumerable<Difference> differences) =>
|
||||||
new(this, differences);
|
new(this, differences);
|
||||||
|
|
||||||
|
public PointInTime WithDifferences(Func<PointInTime, IEnumerable<Difference>> differenceGenerator)
|
||||||
|
{
|
||||||
|
var newPointInTime = new PointInTime();
|
||||||
|
newPointInTime._differences.AddRange(differenceGenerator(newPointInTime));
|
||||||
|
|
||||||
|
return newPointInTime;
|
||||||
|
}
|
||||||
|
|
||||||
private static List<Difference> MergeDifferences(IEnumerable<Difference> previouses,
|
private static List<Difference> MergeDifferences(IEnumerable<Difference> previouses,
|
||||||
IEnumerable<Difference> differences)
|
IEnumerable<Difference> differences)
|
||||||
{
|
{
|
||||||
|
|||||||
37
src/Core/FileTime.Core.Command/CommandRunner.cs
Normal file
37
src/Core/FileTime.Core.Command/CommandRunner.cs
Normal file
@@ -0,0 +1,37 @@
|
|||||||
|
using Microsoft.Extensions.Logging;
|
||||||
|
|
||||||
|
namespace FileTime.Core.Command;
|
||||||
|
|
||||||
|
public class CommandRunner : ICommandRunner
|
||||||
|
{
|
||||||
|
private readonly List<ICommandHandler> _commandHandlers;
|
||||||
|
private readonly ILogger<CommandRunner> _logger;
|
||||||
|
|
||||||
|
public CommandRunner(
|
||||||
|
IEnumerable<ICommandHandler> commandHandlers,
|
||||||
|
ILogger<CommandRunner> logger)
|
||||||
|
{
|
||||||
|
_commandHandlers = commandHandlers.ToList();
|
||||||
|
_logger = logger;
|
||||||
|
}
|
||||||
|
|
||||||
|
public async Task RunCommandAsync(ICommand command)
|
||||||
|
{
|
||||||
|
if (command is IExecutableCommand executableCommand)
|
||||||
|
{
|
||||||
|
await executableCommand.Execute();
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
var commandHandler = _commandHandlers.Find(c => c.CanHandle(command));
|
||||||
|
if (commandHandler != null)
|
||||||
|
{
|
||||||
|
await commandHandler.ExecuteAsync(command);
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
_logger.LogError("No command handler for command {Command}", command.GetType().Name);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -9,7 +9,7 @@ public class CopyCommand : ITransportationCommand
|
|||||||
throw new NotImplementedException();
|
throw new NotImplementedException();
|
||||||
}
|
}
|
||||||
|
|
||||||
public Task<PointInTime?> SimulateCommand(PointInTime? currentTime)
|
public Task<PointInTime> SimulateCommand(PointInTime currentTime)
|
||||||
{
|
{
|
||||||
throw new NotImplementedException();
|
throw new NotImplementedException();
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,21 +1,76 @@
|
|||||||
|
using FileTime.Core.Enums;
|
||||||
|
using FileTime.Core.Extensions;
|
||||||
|
using FileTime.Core.Models;
|
||||||
using FileTime.Core.Timeline;
|
using FileTime.Core.Timeline;
|
||||||
|
|
||||||
namespace FileTime.Core.Command.CreateContainer;
|
namespace FileTime.Core.Command.CreateContainer;
|
||||||
|
|
||||||
public class CreateContainerCommand : IExecutableCommand
|
public class CreateContainerCommand : IExecutableCommand
|
||||||
{
|
{
|
||||||
public Task<CanCommandRun> CanRun(PointInTime currentTime)
|
private readonly ITimelessContentProvider _timelessContentProvider;
|
||||||
|
public FullName Parent { get; }
|
||||||
|
public string NewContainerName { get; }
|
||||||
|
|
||||||
|
public CreateContainerCommand(
|
||||||
|
FullName parent,
|
||||||
|
string newContainerName,
|
||||||
|
ITimelessContentProvider timelessContentProvider)
|
||||||
{
|
{
|
||||||
throw new NotImplementedException();
|
_timelessContentProvider = timelessContentProvider;
|
||||||
|
Parent = parent;
|
||||||
|
NewContainerName = newContainerName;
|
||||||
}
|
}
|
||||||
|
|
||||||
public Task<PointInTime?> SimulateCommand(PointInTime? currentTime)
|
public async Task<CanCommandRun> CanRun(PointInTime currentTime)
|
||||||
|
{
|
||||||
|
try
|
||||||
|
{
|
||||||
|
var parent = await ResolveParentAsync();
|
||||||
|
if (parent is not IContainer parentContainer) return CanCommandRun.False;
|
||||||
|
|
||||||
|
var items = await parentContainer.Items.GetItemsAsync();
|
||||||
|
if (items is null) return CanCommandRun.Forcable;
|
||||||
|
|
||||||
|
var existingItem = items.FirstOrDefault(i => i.Path.GetName() == NewContainerName);
|
||||||
|
|
||||||
|
return existingItem switch
|
||||||
|
{
|
||||||
|
null => CanCommandRun.True,
|
||||||
|
{ Type: AbsolutePathType.Container } => CanCommandRun.Forcable,
|
||||||
|
_ => CanCommandRun.False
|
||||||
|
};
|
||||||
|
}
|
||||||
|
catch
|
||||||
{
|
{
|
||||||
throw new NotImplementedException();
|
|
||||||
}
|
}
|
||||||
|
|
||||||
public Task Execute(ICommandScheduler commandScheduler)
|
return CanCommandRun.False;
|
||||||
{
|
|
||||||
throw new NotImplementedException();
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public Task<PointInTime> SimulateCommand(PointInTime currentTime)
|
||||||
|
{
|
||||||
|
return Task.FromResult(
|
||||||
|
currentTime.WithDifferences(newPointInTime =>
|
||||||
|
new List<Difference>()
|
||||||
|
{
|
||||||
|
new(
|
||||||
|
DifferenceActionType.Create,
|
||||||
|
new AbsolutePath(_timelessContentProvider,
|
||||||
|
newPointInTime,
|
||||||
|
Parent.GetChild(NewContainerName),
|
||||||
|
AbsolutePathType.Container
|
||||||
|
)
|
||||||
|
)
|
||||||
|
}
|
||||||
|
)
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
public Task Execute()
|
||||||
|
{
|
||||||
|
return Task.CompletedTask;
|
||||||
|
}
|
||||||
|
|
||||||
|
private async Task<IItem> ResolveParentAsync()
|
||||||
|
=> await _timelessContentProvider.GetItemByFullNameAsync(Parent, PointInTime.Present);
|
||||||
}
|
}
|
||||||
@@ -4,6 +4,10 @@
|
|||||||
<ProjectReference Include="..\FileTime.Core.Abstraction\FileTime.Core.Abstraction.csproj" />
|
<ProjectReference Include="..\FileTime.Core.Abstraction\FileTime.Core.Abstraction.csproj" />
|
||||||
</ItemGroup>
|
</ItemGroup>
|
||||||
|
|
||||||
|
<ItemGroup>
|
||||||
|
<PackageReference Include="Microsoft.Extensions.Logging.Abstractions" Version="6.0.1" />
|
||||||
|
</ItemGroup>
|
||||||
|
|
||||||
<PropertyGroup>
|
<PropertyGroup>
|
||||||
<EnforceCodeStyleInBuild>true</EnforceCodeStyleInBuild>
|
<EnforceCodeStyleInBuild>true</EnforceCodeStyleInBuild>
|
||||||
</PropertyGroup>
|
</PropertyGroup>
|
||||||
|
|||||||
@@ -109,4 +109,11 @@ public class Tab : ITab
|
|||||||
if (resolvedSelectedItem is not IContainer resolvedContainer) return;
|
if (resolvedSelectedItem is not IContainer resolvedContainer) return;
|
||||||
SetCurrentLocation(resolvedContainer);
|
SetCurrentLocation(resolvedContainer);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public void Dispose()
|
||||||
|
{
|
||||||
|
_currentLocation.Dispose();
|
||||||
|
_currentSelectedItem.Dispose();
|
||||||
|
_itemFilters.Dispose();
|
||||||
|
}
|
||||||
}
|
}
|
||||||
13
src/Core/FileTime.Core.Timeline/CommandRunnerContext.cs
Normal file
13
src/Core/FileTime.Core.Timeline/CommandRunnerContext.cs
Normal file
@@ -0,0 +1,13 @@
|
|||||||
|
using FileTime.Core.Command;
|
||||||
|
|
||||||
|
namespace FileTime.Core.Timeline;
|
||||||
|
|
||||||
|
public class CommandRunnerContext
|
||||||
|
{
|
||||||
|
public ICommand Command { get; }
|
||||||
|
|
||||||
|
public CommandRunnerContext(ICommand command)
|
||||||
|
{
|
||||||
|
Command = command;
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -1,6 +1,165 @@
|
|||||||
|
using DynamicData;
|
||||||
|
using FileTime.Core.Command;
|
||||||
|
using Microsoft.Extensions.DependencyInjection;
|
||||||
|
|
||||||
namespace FileTime.Core.Timeline;
|
namespace FileTime.Core.Timeline;
|
||||||
|
|
||||||
public class CommandScheduler : ICommandScheduler
|
public class CommandScheduler : ICommandScheduler
|
||||||
{
|
{
|
||||||
|
private readonly IServiceProvider _serviceProvider;
|
||||||
|
private readonly SourceList<ParallelCommands> _commandsToRun = new();
|
||||||
|
private readonly List<ICommandExecutor> _commandExecutors = new();
|
||||||
|
|
||||||
|
private readonly object _guard = new();
|
||||||
|
private bool _enableRunning = true;
|
||||||
|
private bool _resourceIsInUse;
|
||||||
|
|
||||||
|
public bool EnableRunning
|
||||||
|
{
|
||||||
|
get
|
||||||
|
{
|
||||||
|
bool result = true;
|
||||||
|
RunWithLock(() => result = _enableRunning);
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
|
||||||
|
set { RunWithLock(() => _enableRunning = value); }
|
||||||
|
}
|
||||||
|
|
||||||
|
public CommandScheduler(IServiceProvider serviceProvider)
|
||||||
|
{
|
||||||
|
_serviceProvider = serviceProvider;
|
||||||
|
var localExecutor = serviceProvider.GetRequiredService<ILocalCommandExecutor>();
|
||||||
|
localExecutor.CommandFinished += LocalExecutorOnCommandFinished;
|
||||||
|
_commandExecutors.Add(localExecutor);
|
||||||
|
}
|
||||||
|
|
||||||
|
public async Task AddCommand(ICommand command, int? batchId = null, bool toNewBatch = false)
|
||||||
|
{
|
||||||
|
await RunWithLockAsync(async () =>
|
||||||
|
{
|
||||||
|
ParallelCommands batchToAdd;
|
||||||
|
|
||||||
|
if (_commandsToRun.Count == 0)
|
||||||
|
{
|
||||||
|
batchToAdd = new ParallelCommands(PointInTime.CreateEmpty());
|
||||||
|
_commandsToRun.Add(batchToAdd);
|
||||||
|
}
|
||||||
|
else if (toNewBatch)
|
||||||
|
{
|
||||||
|
batchToAdd = new ParallelCommands(_commandsToRun.Items.Last().Result);
|
||||||
|
_commandsToRun.Add(batchToAdd);
|
||||||
|
}
|
||||||
|
else if (batchId != null && _commandsToRun.Items.First(b => b.Id == batchId) is { } parallelCommands)
|
||||||
|
{
|
||||||
|
batchToAdd = parallelCommands;
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
batchToAdd = _commandsToRun.Items.First();
|
||||||
|
}
|
||||||
|
|
||||||
|
await batchToAdd.AddCommand(command);
|
||||||
|
|
||||||
|
await RefreshCommands();
|
||||||
|
|
||||||
|
ExecuteCommands();
|
||||||
|
|
||||||
|
/*if (_commandRunners.Count == 0)
|
||||||
|
{
|
||||||
|
StartCommandRunner();
|
||||||
|
}
|
||||||
|
|
||||||
|
await UpdateReadOnlyCommands();*/
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
private void ExecuteCommands()
|
||||||
|
{
|
||||||
|
if (!_enableRunning) return;
|
||||||
|
|
||||||
|
var parallelCommandsToExecute = _commandsToRun.Items.FirstOrDefault();
|
||||||
|
if (parallelCommandsToExecute is null ||
|
||||||
|
parallelCommandsToExecute.Commands.All(c => c.ExecutionState != ExecutionState.Waiting)) return;
|
||||||
|
|
||||||
|
var commandsToExecute = parallelCommandsToExecute.Commands;
|
||||||
|
|
||||||
|
foreach (var commandToExecute in commandsToExecute)
|
||||||
|
{
|
||||||
|
if (commandToExecute.ExecutionState != ExecutionState.Waiting) continue;
|
||||||
|
|
||||||
|
var commandExecutor = GetCommandExecutor();
|
||||||
|
|
||||||
|
commandExecutor.ExecuteCommand(commandToExecute.Command);
|
||||||
|
|
||||||
|
commandToExecute.ExecutionState = ExecutionState.Running;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private ICommandExecutor GetCommandExecutor()
|
||||||
|
{
|
||||||
|
//TODO
|
||||||
|
return _commandExecutors[0];
|
||||||
|
}
|
||||||
|
|
||||||
|
private void LocalExecutorOnCommandFinished(object? sender, ICommand command)
|
||||||
|
{
|
||||||
|
var parallelCommandsToExecute = _commandsToRun.Items.FirstOrDefault();
|
||||||
|
if (parallelCommandsToExecute is null) return;
|
||||||
|
|
||||||
|
var state = parallelCommandsToExecute.Commands.FirstOrDefault(c => c.Command == command);
|
||||||
|
if (state is null) return;
|
||||||
|
|
||||||
|
state.ExecutionState = ExecutionState.Finished;
|
||||||
|
}
|
||||||
|
|
||||||
|
private async Task RefreshCommands()
|
||||||
|
{
|
||||||
|
var currentTime = PointInTime.CreateEmpty();
|
||||||
|
|
||||||
|
foreach (var batch in _commandsToRun.Items)
|
||||||
|
{
|
||||||
|
currentTime = await batch.RefreshResult(currentTime);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private void RunWithLock(Action action) => Task.Run(async () => await RunWithLockAsync(action)).Wait();
|
||||||
|
|
||||||
|
private async Task RunWithLockAsync(Action action)
|
||||||
|
{
|
||||||
|
await RunWithLockAsync(() =>
|
||||||
|
{
|
||||||
|
action();
|
||||||
|
return Task.CompletedTask;
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
private async Task RunWithLockAsync(Func<Task> func)
|
||||||
|
{
|
||||||
|
while (true)
|
||||||
|
{
|
||||||
|
lock (_guard)
|
||||||
|
{
|
||||||
|
if (!_resourceIsInUse)
|
||||||
|
{
|
||||||
|
_resourceIsInUse = true;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
await Task.Delay(1);
|
||||||
|
}
|
||||||
|
|
||||||
|
try
|
||||||
|
{
|
||||||
|
await func();
|
||||||
|
}
|
||||||
|
finally
|
||||||
|
{
|
||||||
|
lock (_guard)
|
||||||
|
{
|
||||||
|
_resourceIsInUse = false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
34
src/Core/FileTime.Core.Timeline/LocalCommandExecutor.cs
Normal file
34
src/Core/FileTime.Core.Timeline/LocalCommandExecutor.cs
Normal file
@@ -0,0 +1,34 @@
|
|||||||
|
using FileTime.Core.Command;
|
||||||
|
|
||||||
|
namespace FileTime.Core.Timeline;
|
||||||
|
|
||||||
|
public class LocalCommandExecutor : ILocalCommandExecutor
|
||||||
|
{
|
||||||
|
private readonly ICommandRunner _commandRunner;
|
||||||
|
public event EventHandler<ICommand> CommandFinished;
|
||||||
|
|
||||||
|
public LocalCommandExecutor(ICommandRunner commandRunner)
|
||||||
|
{
|
||||||
|
_commandRunner = commandRunner;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void ExecuteCommand(ICommand command)
|
||||||
|
{
|
||||||
|
var context = new CommandRunnerContext(command);
|
||||||
|
var thread = new Thread(new ParameterizedThreadStart(RunCommand));
|
||||||
|
thread.Start(context);
|
||||||
|
}
|
||||||
|
|
||||||
|
private async void RunCommand(object? contextObj)
|
||||||
|
{
|
||||||
|
if (contextObj is not CommandRunnerContext context)
|
||||||
|
throw new ArgumentException($"Parameter must be of type {typeof(CommandRunnerContext)}");
|
||||||
|
try
|
||||||
|
{
|
||||||
|
await _commandRunner.RunCommandAsync(context.Command);
|
||||||
|
}
|
||||||
|
catch(Exception ex){}
|
||||||
|
|
||||||
|
CommandFinished.Invoke(this, context.Command);
|
||||||
|
}
|
||||||
|
}
|
||||||
Reference in New Issue
Block a user