Command execution, CreateContainer command WIP
This commit is contained in:
@@ -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
|
||||
{
|
||||
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
|
||||
{
|
||||
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" />
|
||||
</ItemGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<Folder Include="ContentAccess" />
|
||||
</ItemGroup>
|
||||
|
||||
</Project>
|
||||
|
||||
@@ -4,8 +4,6 @@ public record FullName(string Path)
|
||||
{
|
||||
public FullName? GetParent()
|
||||
{
|
||||
if (Path is null) return null;
|
||||
|
||||
var pathParts = Path.TrimEnd(Constants.SeparatorChar).Split(Constants.SeparatorChar);
|
||||
return pathParts.Length switch
|
||||
{
|
||||
@@ -13,4 +11,10 @@ public record FullName(string Path)
|
||||
_ => 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;
|
||||
|
||||
public interface ITab : IInitable<IContainer>
|
||||
public interface ITab : IInitable<IContainer>, IDisposable
|
||||
{
|
||||
IObservable<IContainer?> CurrentLocation { get; }
|
||||
IObservable<AbsolutePath?> CurrentSelectedItem { get; }
|
||||
|
||||
@@ -7,6 +7,7 @@ public class CommandTimeState
|
||||
public ICommand Command { get; }
|
||||
public CanCommandRun CanRun { get; private set; } = CanCommandRun.False;
|
||||
public bool ForceRun { get; set; }
|
||||
public ExecutionState ExecutionState { get; set; }
|
||||
|
||||
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;
|
||||
|
||||
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
|
||||
{
|
||||
private static ushort _idCounter;
|
||||
public List<CommandTimeState> _commands;
|
||||
private List<CommandTimeState> _commands;
|
||||
public ushort Id { get; }
|
||||
public IReadOnlyList<CommandTimeState> Commands { get; }
|
||||
public PointInTime? Result { get; private set; }
|
||||
|
||||
@@ -2,10 +2,11 @@ namespace FileTime.Core.Timeline;
|
||||
|
||||
public class PointInTime
|
||||
{
|
||||
private readonly List<Difference> _differences;
|
||||
public static readonly PointInTime Eternal = new PointInTime();
|
||||
public static readonly PointInTime Present = new PointInTime();
|
||||
|
||||
private readonly List<Difference> _differences;
|
||||
|
||||
public IReadOnlyList<Difference> Differences { get; }
|
||||
|
||||
private PointInTime() : this(new List<Difference>())
|
||||
@@ -26,6 +27,14 @@ public class PointInTime
|
||||
public PointInTime WithDifferences(IEnumerable<Difference> 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,
|
||||
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();
|
||||
}
|
||||
|
||||
public Task<PointInTime?> SimulateCommand(PointInTime? currentTime)
|
||||
public Task<PointInTime> SimulateCommand(PointInTime currentTime)
|
||||
{
|
||||
throw new NotImplementedException();
|
||||
}
|
||||
|
||||
@@ -1,21 +1,76 @@
|
||||
using FileTime.Core.Enums;
|
||||
using FileTime.Core.Extensions;
|
||||
using FileTime.Core.Models;
|
||||
using FileTime.Core.Timeline;
|
||||
|
||||
namespace FileTime.Core.Command.CreateContainer;
|
||||
|
||||
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)
|
||||
{
|
||||
throw new NotImplementedException();
|
||||
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
|
||||
{
|
||||
}
|
||||
|
||||
return CanCommandRun.False;
|
||||
}
|
||||
|
||||
public Task Execute(ICommandScheduler commandScheduler)
|
||||
public Task<PointInTime> SimulateCommand(PointInTime currentTime)
|
||||
{
|
||||
throw new NotImplementedException();
|
||||
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" />
|
||||
</ItemGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<PackageReference Include="Microsoft.Extensions.Logging.Abstractions" Version="6.0.1" />
|
||||
</ItemGroup>
|
||||
|
||||
<PropertyGroup>
|
||||
<EnforceCodeStyleInBuild>true</EnforceCodeStyleInBuild>
|
||||
</PropertyGroup>
|
||||
|
||||
@@ -109,4 +109,11 @@ public class Tab : ITab
|
||||
if (resolvedSelectedItem is not IContainer resolvedContainer) return;
|
||||
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;
|
||||
|
||||
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