TimeTravel

This commit is contained in:
2022-01-31 23:13:39 +01:00
parent 80570d8895
commit c2dcb49016
78 changed files with 2294 additions and 363 deletions

View File

@@ -0,0 +1,27 @@
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 TimeProvider? TimeProvider { get; private set; }
public CommandTimeState(ICommand command, PointInTime? startTime)
{
Command = command;
UpdateState(startTime).Wait();
}
public async Task UpdateState(PointInTime? startPoint)
{
CanRun = startPoint == null ? CanCommandRun.False : await Command.CanRun(startPoint);
if (startPoint != null)
{
TimeProvider = startPoint.Provider as TimeProvider;
}
}
}
}

View File

@@ -1,7 +0,0 @@
namespace FileTime.Core.Timeline
{
public class ContainerSnapshot
{
}
}

View File

@@ -0,0 +1,31 @@
using FileTime.Core.Models;
using FileTime.Core.Providers;
namespace FileTime.Core.Timeline
{
public class Difference
{
public DifferenceItemType Type { get; }
public string Name { get; }
public AbsolutePath AbsolutePath { get; }
public DifferenceActionType Action { get; }
public Difference(DifferenceItemType type, DifferenceActionType action, AbsolutePath absolutePath)
{
Type = type;
AbsolutePath = absolutePath;
Action = action;
Name = absolutePath.GetName();
}
public Difference WithVirtualContentProvider(IContentProvider? virtualContentProvider)
{
return new Difference(
Type,
Action,
new AbsolutePath(AbsolutePath.ContentProvider, AbsolutePath.Path, virtualContentProvider)
);
}
}
}

View File

@@ -0,0 +1,8 @@
namespace FileTime.Core.Timeline
{
public enum DifferenceActionType
{
Create,
Delete
}
}

View File

@@ -0,0 +1,9 @@
namespace FileTime.Core.Timeline
{
public enum DifferenceItemType
{
Container,
Element,
Unknown
}
}

View File

@@ -0,0 +1,89 @@
using FileTime.Core.Command;
namespace FileTime.Core.Timeline
{
public class ParallelCommands
{
private static ushort _idCounter;
public List<CommandTimeState> _commands;
public ushort Id { get; }
public IReadOnlyList<CommandTimeState> Commands { get; }
public PointInTime? Result { get; private set; }
public ParallelCommands(PointInTime? result)
: this(new List<CommandTimeState>(), result) { }
private ParallelCommands(List<CommandTimeState> commands, PointInTime? result)
{
Id = _idCounter++;
_commands = commands;
Commands = _commands.AsReadOnly();
Result = result;
}
public static async Task<ParallelCommands> Create(PointInTime? startTime, IEnumerable<ICommand> commands)
{
var commandStates = new List<CommandTimeState>();
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<PointInTime?> 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.Forceable && 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);
}
}

View File

@@ -1,12 +1,40 @@
using System.Collections.ObjectModel;
using FileTime.Core.Providers;
namespace FileTime.Core.Timeline
{
public class PointInTime
public sealed class PointInTime
{
private readonly Dictionary<IContentProvider, RootSnapshot> snapshots = new();
private readonly List<Difference> _differences;
public IReadOnlyDictionary<IContentProvider, RootSnapshot> Snapshots => new Lazy<IReadOnlyDictionary<IContentProvider, RootSnapshot>>(() => new ReadOnlyDictionary<IContentProvider, RootSnapshot>(snapshots)).Value;
public IReadOnlyList<Difference> Differences { get; }
public IContentProvider? Provider { get; }
private PointInTime() : this(new List<Difference>(), null) { }
private PointInTime(IEnumerable<Difference> differences, IContentProvider? provider)
{
_differences = new List<Difference>(differences);
Differences = _differences.AsReadOnly();
Provider = provider;
}
private PointInTime(PointInTime previous, IEnumerable<Difference> differences, IContentProvider provider)
: this(MergeDifferences(previous.Differences, differences, provider), provider) { }
public PointInTime WithDifferences(IEnumerable<Difference> differences) => new(this, differences, new TimeProvider(this));
private static List<Difference> MergeDifferences(IEnumerable<Difference> previouses, IEnumerable<Difference> differences, IContentProvider virtualProvider)
{
var merged = new List<Difference>();
merged.AddRange(previouses.Select(p => p.WithVirtualContentProvider(virtualProvider)));
merged.AddRange(differences.Select(d => d.WithVirtualContentProvider(virtualProvider)));
return merged;
}
public static PointInTime CreateEmpty(IContentProvider? parentProvder = null) =>
parentProvder == null ? new PointInTime() : new PointInTime(new List<Difference>(), parentProvder);
}
}

View File

@@ -0,0 +1,18 @@
using FileTime.Core.Command;
namespace FileTime.Core.Timeline
{
public class ReadOnlyCommandTimeState
{
public CanCommandRun CanRun { get; }
public bool ForceRun { get; }
public ICommand Command { get; }
public ReadOnlyCommandTimeState(CommandTimeState commandTimeState)
{
CanRun = commandTimeState.CanRun;
ForceRun = commandTimeState.ForceRun;
Command = commandTimeState.Command;
}
}
}

View File

@@ -0,0 +1,11 @@
namespace FileTime.Core.Timeline
{
public class ReadOnlyParallelCommands
{
public IReadOnlyList<ReadOnlyCommandTimeState> Commands { get; }
public ReadOnlyParallelCommands(ParallelCommands parallelCommands)
{
Commands = parallelCommands.Commands.Select(c => new ReadOnlyCommandTimeState(c)).ToList().AsReadOnly();
}
}
}

View File

@@ -1,7 +0,0 @@
namespace FileTime.Core.Timeline
{
public class RootSnapshot
{
}
}

View File

@@ -0,0 +1,121 @@
using AsyncEvent;
using FileTime.Core.Models;
using FileTime.Core.Providers;
namespace FileTime.Core.Timeline
{
public class TimeContainer : IContainer
{
private readonly IContainer? _parent;
private readonly PointInTime _pointInTime;
public bool IsLoaded => true;
public AsyncEventHandler Refreshed { get; } = new AsyncEventHandler();
public string Name { get; }
public string? FullName { get; }
public bool IsHidden => false;
public bool CanDelete => true;
public bool CanRename => true;
public IContentProvider Provider { get; }
public IContentProvider VirtualProvider { get; }
public TimeContainer(string name, IContainer parent, IContentProvider contentProvider, IContentProvider virtualContentProvider, PointInTime pointInTime)
{
_parent = parent;
_pointInTime = pointInTime;
Name = name;
Provider = contentProvider;
VirtualProvider = virtualContentProvider;
FullName = parent?.FullName == null ? Name : parent.FullName + Constants.SeparatorChar + Name;
}
public async Task<IContainer> Clone() => new TimeContainer(Name, await _parent!.Clone(), Provider, VirtualProvider, _pointInTime);
public Task<IContainer> CreateContainer(string name) => Task.FromResult((IContainer)new TimeContainer(name, this, Provider, VirtualProvider, _pointInTime));
public Task<IElement> CreateElement(string name) => Task.FromResult((IElement)new TimeElement(name, this, Provider, VirtualProvider));
public Task Delete() => Task.CompletedTask;
public async Task<IItem?> GetByPath(string path)
{
var paths = path.Split(Constants.SeparatorChar);
var item = (await GetItems())!.FirstOrDefault(i => i.Name == paths[0]);
if (paths.Length == 1)
{
return item;
}
if (item is IContainer container)
{
return await container.GetByPath(string.Join(Constants.SeparatorChar, paths.Skip(1)));
}
return null;
}
public Task<IReadOnlyList<IContainer>?> GetContainers(CancellationToken token = default) =>
Task.FromResult(
(IReadOnlyList<IContainer>?)_pointInTime
.Differences
.Where(d =>
d.Type == DifferenceItemType.Container
&& GetParentPath(d.AbsolutePath.Path) == FullName)
.Select(MapContainer)
.ToList()
.AsReadOnly()
);
public Task<IReadOnlyList<IElement>?> GetElements(CancellationToken token = default) =>
Task.FromResult(
(IReadOnlyList<IElement>?)_pointInTime
.Differences
.Where(d =>
d.Type == DifferenceItemType.Element
&& GetParentPath(d.AbsolutePath.Path) == FullName)
.Select(MapElement)
.ToList()
.AsReadOnly()
);
public async Task<IReadOnlyList<IItem>?> GetItems(CancellationToken token = default)
{
var containers = (await GetContainers(token))!;
var elements = (await GetElements(token))!;
return containers.Cast<IItem>().Concat(elements).ToList().AsReadOnly();
}
public IContainer? GetParent() => _parent;
public async Task<bool> IsExists(string name) => (await GetItems())?.Any(i => i.Name == name) ?? false;
public async Task Refresh() => await Refreshed.InvokeAsync(this, AsyncEventArgs.Empty);
public Task Rename(string newName) => Task.CompletedTask;
private static string GetParentPath(string path) => string.Join(Constants.SeparatorChar, path.Split(Constants.SeparatorChar).Take(-1));
private IContainer MapContainer(Difference containerDiff)
{
if (containerDiff.Type != DifferenceItemType.Container) throw new ArgumentException($"{nameof(containerDiff)}'s {nameof(Difference.Type)} property is not {DifferenceItemType.Container}.");
return new TimeContainer(containerDiff.Name, this, Provider, containerDiff.AbsolutePath.VirtualContentProvider ?? containerDiff.AbsolutePath.ContentProvider, _pointInTime);
}
private IElement MapElement(Difference elementDiff)
{
if (elementDiff.Type != DifferenceItemType.Container) throw new ArgumentException($"{elementDiff}'s {nameof(Difference.Type)} property is not {DifferenceItemType.Element}.");
return new TimeElement(elementDiff.Name, this, Provider, elementDiff.AbsolutePath.VirtualContentProvider ?? elementDiff.AbsolutePath.ContentProvider);
}
}
}

View File

@@ -0,0 +1,42 @@
using FileTime.Core.Models;
using FileTime.Core.Providers;
namespace FileTime.Core.Timeline
{
public class TimeElement : IElement
{
private readonly IContainer _parent;
public TimeElement(string name, TimeContainer parent, IContentProvider contentProvider, IContentProvider virtualContentProvider)
{
_parent = parent;
Name = name;
FullName = parent?.FullName == null ? Name : parent.FullName + Constants.SeparatorChar + Name;
Provider = contentProvider;
VirtualProvider = virtualContentProvider;
}
public bool IsSpecial => false;
public string Name { get; }
public string? FullName { get; }
public bool IsHidden => false;
public bool CanDelete => true;
public bool CanRename => true;
public IContentProvider Provider { get; }
public IContentProvider VirtualProvider { get; }
public Task Delete() => Task.CompletedTask;
public IContainer? GetParent() => _parent;
public string GetPrimaryAttributeText() => "";
public Task Rename(string newName) => Task.CompletedTask;
}
}

View File

@@ -0,0 +1,89 @@
using AsyncEvent;
using FileTime.Core.Models;
using FileTime.Core.Providers;
namespace FileTime.Core.Timeline
{
public class TimeProvider : IContentProvider
{
public bool IsLoaded => true;
public AsyncEventHandler Refreshed { get; } = new();
public string Name => "time";
public string? FullName => null;
public bool IsHidden => false;
public bool CanDelete => false;
public bool CanRename => false;
public IContentProvider Provider => this;
private readonly PointInTime _pointInTime;
public TimeProvider(PointInTime pointInTime)
{
_pointInTime = pointInTime;
}
public bool CanHandlePath(string path)
{
throw new NotImplementedException();
}
public Task<IContainer> Clone() => Task.FromResult((IContainer)this);
public Task<IContainer> CreateContainer(string name)
{
throw new NotImplementedException();
}
public Task<IElement> CreateElement(string name)
{
throw new NotImplementedException();
}
public Task Delete() => throw new NotSupportedException();
public Task<IItem?> GetByPath(string path)
{
throw new NotImplementedException();
}
public Task<IReadOnlyList<IContainer>?> GetContainers(CancellationToken token = default)
{
throw new NotImplementedException();
}
public Task<IReadOnlyList<IElement>?> GetElements(CancellationToken token = default)
{
throw new NotImplementedException();
}
public Task<IReadOnlyList<IItem>?> GetItems(CancellationToken token = default)
{
throw new NotImplementedException();
}
public IContainer? GetParent() => null;
public Task<IReadOnlyList<IContainer>> GetRootContainers(CancellationToken token = default)
{
throw new NotImplementedException();
}
public Task<bool> IsExists(string name)
{
throw new NotImplementedException();
}
public Task Refresh() => Task.CompletedTask;
public Task Rename(string newName) => throw new NotSupportedException();
public void SetParent(IContainer container) { }
}
}

View File

@@ -0,0 +1,217 @@
using AsyncEvent;
using FileTime.Core.Command;
using FileTime.Core.Models;
namespace FileTime.Core.Timeline
{
public class TimeRunner
{
private readonly CommandExecutor _commandExecutor;
private readonly List<ParallelCommands> _commandsToRun = new();
private readonly object _guard = new();
private bool _resourceIsInUse;
private readonly List<Thread> _commandRunners = new();
private bool _enableRunning = true;
public bool EnableRunning
{
get
{
bool result = true;
RunWithLock(() => result = _enableRunning);
return result;
}
set
{
RunWithLock(() => _enableRunning = value);
}
}
public IReadOnlyList<ReadOnlyParallelCommands> ParallelCommands { get; private set; } = new List<ReadOnlyParallelCommands>().AsReadOnly();
public AsyncEventHandler<AbsolutePath> RefreshContainer { get; } = new AsyncEventHandler<AbsolutePath>();
public event EventHandler? CommandsChanged;
public TimeRunner(CommandExecutor commandExecutor)
{
_commandExecutor = commandExecutor;
}
public async Task AddCommand(ICommand command, ParallelCommands? batch = 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.Last().Result);
_commandsToRun.Add(batchToAdd);
}
else if (batch != null && _commandsToRun.Contains(batch))
{
batchToAdd = batch;
}
else
{
batchToAdd = _commandsToRun[0];
}
await batchToAdd.AddCommand(command);
await RefreshCommands();
if (_commandRunners.Count == 0)
{
StartCommandRunner();
}
});
UpdateReadOnlyCommands();
}
public async Task TryStartCommandRunner()
{
await RunWithLockAsync(() =>
{
if (_commandRunners.Count == 0 && _commandsToRun.Count > 0)
{
StartCommandRunner();
}
});
}
private void StartCommandRunner()
{
if (_enableRunning)
{
RunCommands();
}
}
private void RunCommands()
{
if (_commandsToRun.Count > 0)
{
foreach (var command in _commandsToRun[0].Commands)
{
if (command.CanRun == CanCommandRun.True || (command.CanRun == CanCommandRun.Forceable && command.ForceRun))
{
var thread = new Thread(new ParameterizedThreadStart(RunCommand));
thread.Start(command);
}
else
{
break;
}
}
}
}
private void RunCommand(object? arg)
{
CommandTimeState? commandToRun = null;
try
{
if (arg is CommandTimeState commandToRun2)
{
commandToRun = commandToRun2;
_commandExecutor.ExecuteCommandAsync(commandToRun.Command, this).Wait();
}
}
finally
{
DisposeCommandThread(Thread.CurrentThread, commandToRun).Wait();
}
}
private async Task DisposeCommandThread(Thread thread, CommandTimeState? command)
{
await RunWithLockAsync(() =>
{
if (command != null)
{
_commandsToRun[0].Remove(command);
}
_commandRunners.Remove(thread);
});
await UpdateReadOnlyCommands();
await TryStartCommandRunner();
}
public async Task Refresh()
{
await RunWithLockAsync(async () =>
{
await RefreshCommands(PointInTime.CreateEmpty());
});
await UpdateReadOnlyCommands();
}
private async Task RefreshCommands(PointInTime? fullStartTime = null)
{
var curretnTime = fullStartTime ?? _commandsToRun[0].Result;
var startIndex = fullStartTime == null ? 1 : 0;
for (var i = startIndex; i < _commandsToRun.Count; i++)
{
curretnTime = await _commandsToRun[i].RefreshResult(curretnTime);
}
}
private async Task UpdateReadOnlyCommands()
{
await RunWithLockAsync(() =>
{
ParallelCommands = _commandsToRun.ConvertAll(c => new ReadOnlyParallelCommands(c)).AsReadOnly();
});
CommandsChanged?.Invoke(this, EventArgs.Empty);
}
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;
}
}
}
private void RunWithLock(Action action) => RunWithLockAsync(action).Wait();
}
}