TimeTravel
This commit is contained in:
@@ -1,6 +1,6 @@
|
||||
namespace AsyncEvent
|
||||
{
|
||||
public class AsyncEventHandler<TSender, TArg> where TArg : AsyncEventArgs
|
||||
public class AsyncEventHandler<TSender, TArg>
|
||||
{
|
||||
private readonly List<Func<TSender, TArg, Task>> _handlers;
|
||||
private readonly Action<Func<TSender, TArg, Task>> _add;
|
||||
@@ -56,7 +56,7 @@
|
||||
return obj;
|
||||
}
|
||||
}
|
||||
public class AsyncEventHandler<TArg> : AsyncEventHandler<object?, AsyncEventArgs> where TArg : AsyncEventArgs
|
||||
public class AsyncEventHandler<TArg> : AsyncEventHandler<object?, TArg>
|
||||
{
|
||||
public AsyncEventHandler(Action<Func<object?, TArg, Task>>? add = null, Action<Func<object?, TArg, Task>>? remove = null) : base(add, remove) { }
|
||||
}
|
||||
|
||||
9
src/Core/FileTime.Core/Command/CanCommandRun.cs
Normal file
9
src/Core/FileTime.Core/Command/CanCommandRun.cs
Normal file
@@ -0,0 +1,9 @@
|
||||
namespace FileTime.Core.Command
|
||||
{
|
||||
public enum CanCommandRun
|
||||
{
|
||||
True,
|
||||
False,
|
||||
Forceable
|
||||
}
|
||||
}
|
||||
@@ -1,3 +1,5 @@
|
||||
using FileTime.Core.Timeline;
|
||||
|
||||
namespace FileTime.Core.Command
|
||||
{
|
||||
public class CommandExecutor
|
||||
@@ -9,15 +11,15 @@ namespace FileTime.Core.Command
|
||||
_commandHandlers = commandHandlers.ToList();
|
||||
}
|
||||
|
||||
public void ExecuteCommand(ICommand command)
|
||||
public async Task ExecuteCommandAsync(ICommand command, TimeRunner timeRunner)
|
||||
{
|
||||
if (command is IExecutableCommand executableCommand)
|
||||
{
|
||||
executableCommand.Execute();
|
||||
await executableCommand.Execute(timeRunner);
|
||||
}
|
||||
else
|
||||
{
|
||||
_commandHandlers.Find(c => c.CanHandle(command))?.Execute(command);
|
||||
await _commandHandlers.Find(c => c.CanHandle(command))?.ExecuteAsync(command, timeRunner);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -5,59 +5,115 @@ namespace FileTime.Core.Command
|
||||
{
|
||||
public class CopyCommand : ITransportationCommand
|
||||
{
|
||||
public IList<IAbsolutePath> Sources { get; } = new List<IAbsolutePath>();
|
||||
private Action<AbsolutePath, AbsolutePath>? _copyOperation;
|
||||
private Func<IContainer, string, Task<IContainer>>? _createContainer;
|
||||
private TimeRunner? _timeRunner;
|
||||
|
||||
public IList<AbsolutePath>? Sources { get; } = new List<AbsolutePath>();
|
||||
|
||||
public IContainer? Target { get; set; }
|
||||
|
||||
public TransportMode TransportMode { get; set; } = TransportMode.Merge;
|
||||
public TransportMode? TransportMode { get; set; } = Command.TransportMode.Merge;
|
||||
|
||||
public PointInTime SimulateCommand(PointInTime delta)
|
||||
public async Task<PointInTime> SimulateCommand(PointInTime startPoint)
|
||||
{
|
||||
throw new NotImplementedException();
|
||||
if (Sources == null) throw new ArgumentException(nameof(Sources) + " can not be null");
|
||||
if (Target == null) throw new ArgumentException(nameof(Target) + " can not be null");
|
||||
if (TransportMode == null) throw new ArgumentException(nameof(TransportMode) + " can not be null");
|
||||
|
||||
var newDiffs = new List<Difference>();
|
||||
|
||||
_copyOperation = (_, to) =>
|
||||
{
|
||||
var target = to.GetParentAsAbsolutePath().Resolve();
|
||||
newDiffs.Add(new Difference(
|
||||
target is IElement
|
||||
? DifferenceItemType.Element
|
||||
: DifferenceItemType.Container,
|
||||
DifferenceActionType.Create,
|
||||
to
|
||||
));
|
||||
};
|
||||
|
||||
_createContainer = async (IContainer target, string name) =>
|
||||
{
|
||||
var newContainerDiff = new Difference(
|
||||
DifferenceItemType.Container,
|
||||
DifferenceActionType.Create,
|
||||
AbsolutePath.FromParentAndChildName(target, name)
|
||||
);
|
||||
|
||||
newDiffs.Add(newContainerDiff);
|
||||
|
||||
return (IContainer)(await newContainerDiff.AbsolutePath.Resolve())!;
|
||||
};
|
||||
|
||||
await DoCopy(Sources, Target, TransportMode.Value);
|
||||
|
||||
return startPoint.WithDifferences(newDiffs);
|
||||
}
|
||||
|
||||
public async Task Execute(Action<IAbsolutePath, IAbsolutePath> copy)
|
||||
public async Task Execute(Action<AbsolutePath, AbsolutePath> copy, TimeRunner timeRunner)
|
||||
{
|
||||
await DoCopy(Sources, Target, TransportMode, copy);
|
||||
if (Sources == null) throw new ArgumentException(nameof(Sources) + " can not be null");
|
||||
if (Target == null) throw new ArgumentException(nameof(Target) + " can not be null");
|
||||
if (TransportMode == null) throw new ArgumentException(nameof(TransportMode) + " can not be null");
|
||||
|
||||
_copyOperation = copy;
|
||||
_createContainer = async (IContainer target, string name) => await target.CreateContainer(name);
|
||||
_timeRunner = timeRunner;
|
||||
|
||||
await DoCopy(Sources, Target, TransportMode.Value);
|
||||
}
|
||||
|
||||
private async Task DoCopy(IEnumerable<IAbsolutePath> sources, IContainer target, TransportMode transportMode, Action<IAbsolutePath, IAbsolutePath> copy)
|
||||
private async Task DoCopy(
|
||||
IEnumerable<AbsolutePath> sources,
|
||||
IContainer target,
|
||||
TransportMode transportMode)
|
||||
{
|
||||
if (_copyOperation == null) throw new ArgumentException("No copy operation were given.");
|
||||
if (_createContainer == null) throw new ArgumentException("No container creation function were given.");
|
||||
|
||||
foreach (var source in sources)
|
||||
{
|
||||
var item = await source.ContentProvider.GetByPath(source.Path);
|
||||
var item = await source.Resolve();
|
||||
|
||||
if (item is IContainer container)
|
||||
{
|
||||
var targetContainer = (await target.GetContainers())?.FirstOrDefault(d => d.Name == container.Name) ?? (await target.CreateContainer(container.Name)!);
|
||||
var targetContainer = (await target.GetContainers())?.FirstOrDefault(d => d.Name == container.Name) ?? (await _createContainer?.Invoke(target, container.Name)!);
|
||||
|
||||
var childDirectories = (await container.GetContainers())!.Select(d => new AbsolutePath(item.Provider, d.FullName!));
|
||||
var childFiles = (await container.GetElements())!.Select(f => new AbsolutePath(item.Provider, f.FullName!));
|
||||
var childDirectories = (await container.GetContainers())!.Select(d => new AbsolutePath(d));
|
||||
var childFiles = (await container.GetElements())!.Select(f => new AbsolutePath(f));
|
||||
|
||||
await DoCopy(childDirectories.Concat(childFiles), targetContainer, transportMode, copy);
|
||||
await DoCopy(childDirectories.Concat(childFiles), targetContainer, transportMode);
|
||||
_timeRunner?.RefreshContainer.InvokeAsync(this, new AbsolutePath(container));
|
||||
}
|
||||
else if (item is IElement element)
|
||||
{
|
||||
var targetName = element.Name;
|
||||
|
||||
var targetNameExists = await target.IsExists(targetName);
|
||||
if (transportMode == TransportMode.Merge)
|
||||
if (transportMode == Command.TransportMode.Merge)
|
||||
{
|
||||
for (var i = 0; targetNameExists; i++)
|
||||
{
|
||||
targetName = element.Name + (i == 0 ? "_" : $"_{i}");
|
||||
}
|
||||
}
|
||||
else if (transportMode == TransportMode.Skip && targetNameExists)
|
||||
else if (transportMode == Command.TransportMode.Skip && targetNameExists)
|
||||
{
|
||||
continue;
|
||||
}
|
||||
|
||||
var targetPath = target.FullName + Constants.SeparatorChar + targetName;
|
||||
|
||||
copy(new AbsolutePath(source.ContentProvider, element.FullName!), new AbsolutePath(target.Provider, targetPath));
|
||||
_copyOperation?.Invoke(new AbsolutePath(element), AbsolutePath.FromParentAndChildName(target, targetName));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public Task<CanCommandRun> CanRun(PointInTime startPoint)
|
||||
{
|
||||
//TODO: implement
|
||||
return Task.FromResult(CanCommandRun.True);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,12 +1,51 @@
|
||||
using FileTime.Core.Models;
|
||||
using FileTime.Core.Timeline;
|
||||
|
||||
namespace FileTime.Core.Command
|
||||
{
|
||||
public class CreateContainerCommand : ICommand
|
||||
public class CreateContainerCommand : IExecutableCommand
|
||||
{
|
||||
public PointInTime SimulateCommand(PointInTime delta)
|
||||
public AbsolutePath Container { get; }
|
||||
public string NewContainerName { get; }
|
||||
|
||||
public CreateContainerCommand(AbsolutePath container, string newContainerName)
|
||||
{
|
||||
throw new NotImplementedException();
|
||||
Container = container;
|
||||
NewContainerName = newContainerName;
|
||||
}
|
||||
|
||||
public async Task Execute(TimeRunner timeRunner)
|
||||
{
|
||||
var possibleContainer = await Container.Resolve();
|
||||
if (possibleContainer is IContainer container)
|
||||
{
|
||||
await container.CreateContainer(NewContainerName);
|
||||
await timeRunner.RefreshContainer.InvokeAsync(this, new AbsolutePath(container));
|
||||
}
|
||||
//TODO: else
|
||||
}
|
||||
|
||||
public Task<PointInTime> SimulateCommand(PointInTime startPoint)
|
||||
{
|
||||
var newDifferences = new List<Difference>()
|
||||
{
|
||||
new Difference(DifferenceItemType.Container, DifferenceActionType.Create, new AbsolutePath(Container.ContentProvider, Container.Path + Constants.SeparatorChar + NewContainerName, Container.VirtualContentProvider))
|
||||
};
|
||||
return Task.FromResult(startPoint.WithDifferences(newDifferences));
|
||||
}
|
||||
|
||||
public async Task<CanCommandRun> CanRun(PointInTime startPoint)
|
||||
{
|
||||
var resolvedContainer = await Container.Resolve();
|
||||
if (resolvedContainer == null) return CanCommandRun.Forceable;
|
||||
|
||||
if (resolvedContainer is not IContainer container
|
||||
|| await container.IsExists(NewContainerName))
|
||||
{
|
||||
return CanCommandRun.False;
|
||||
}
|
||||
|
||||
return CanCommandRun.True;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,12 +1,50 @@
|
||||
using FileTime.Core.Models;
|
||||
using FileTime.Core.Timeline;
|
||||
|
||||
namespace FileTime.Core.Command
|
||||
{
|
||||
public class CreateElementCommand : ICommand
|
||||
public class CreateElementCommand : IExecutableCommand
|
||||
{
|
||||
public PointInTime SimulateCommand(PointInTime delta)
|
||||
public AbsolutePath Container { get; }
|
||||
public string NewElementName { get; }
|
||||
|
||||
public CreateElementCommand(AbsolutePath container, string newElementName)
|
||||
{
|
||||
throw new NotImplementedException();
|
||||
Container = container;
|
||||
NewElementName = newElementName;
|
||||
}
|
||||
|
||||
public async Task Execute(TimeRunner timeRunner)
|
||||
{
|
||||
var possibleContainer = await Container.Resolve();
|
||||
if (possibleContainer is IContainer container)
|
||||
{
|
||||
await container.CreateElement(NewElementName);
|
||||
await timeRunner.RefreshContainer.InvokeAsync(this, new AbsolutePath(container));
|
||||
}
|
||||
}
|
||||
|
||||
public Task<PointInTime> SimulateCommand(PointInTime startPoint)
|
||||
{
|
||||
var newDifferences = new List<Difference>()
|
||||
{
|
||||
new Difference(DifferenceItemType.Element, DifferenceActionType.Create, new AbsolutePath(Container.ContentProvider, Container.Path + Constants.SeparatorChar + NewElementName, Container.VirtualContentProvider))
|
||||
};
|
||||
return Task.FromResult(startPoint.WithDifferences(newDifferences));
|
||||
}
|
||||
|
||||
public async Task<CanCommandRun> CanRun(PointInTime startPoint)
|
||||
{
|
||||
var resolvedContainer = Container.Resolve();
|
||||
if (resolvedContainer == null) return CanCommandRun.Forceable;
|
||||
|
||||
if (resolvedContainer is not IContainer container
|
||||
|| await container.IsExists(NewElementName))
|
||||
{
|
||||
return CanCommandRun.False;
|
||||
}
|
||||
|
||||
return CanCommandRun.True;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,3 +1,4 @@
|
||||
using FileTime.Core.Extensions;
|
||||
using FileTime.Core.Models;
|
||||
using FileTime.Core.Timeline;
|
||||
|
||||
@@ -5,37 +6,64 @@ namespace FileTime.Core.Command
|
||||
{
|
||||
public class DeleteCommand : IExecutableCommand
|
||||
{
|
||||
public IList<IAbsolutePath> ItemsToDelete { get; } = new List<IAbsolutePath>();
|
||||
public IList<AbsolutePath> ItemsToDelete { get; } = new List<AbsolutePath>();
|
||||
|
||||
public PointInTime SimulateCommand(PointInTime delta)
|
||||
public async Task<PointInTime> SimulateCommand(PointInTime startPoint)
|
||||
{
|
||||
throw new NotImplementedException();
|
||||
var newDifferences = new List<Difference>();
|
||||
|
||||
foreach (var itemToDelete in ItemsToDelete)
|
||||
{
|
||||
var item = await itemToDelete.Resolve();
|
||||
newDifferences.Add(new Difference(
|
||||
item.ToDifferenceItemType(),
|
||||
DifferenceActionType.Delete,
|
||||
itemToDelete
|
||||
));
|
||||
}
|
||||
return startPoint.WithDifferences(newDifferences);
|
||||
}
|
||||
|
||||
public async Task Execute()
|
||||
public async Task Execute(TimeRunner timeRunner)
|
||||
{
|
||||
foreach (var item in ItemsToDelete)
|
||||
{
|
||||
await DoDelete(await item.ContentProvider.GetByPath(item.Path)!);
|
||||
await DoDelete((await item.Resolve())!, timeRunner);
|
||||
}
|
||||
}
|
||||
|
||||
private async Task DoDelete(IItem item)
|
||||
private async Task DoDelete(IItem item, TimeRunner timeRunner)
|
||||
{
|
||||
if (item is IContainer container)
|
||||
{
|
||||
foreach (var child in await container.GetItems())
|
||||
foreach (var child in (await container.GetItems())!)
|
||||
{
|
||||
await DoDelete(child);
|
||||
await DoDelete(child, timeRunner);
|
||||
await child.Delete();
|
||||
}
|
||||
|
||||
await item.Delete();
|
||||
await timeRunner.RefreshContainer.InvokeAsync(this, new AbsolutePath(container));
|
||||
}
|
||||
else if(item is IElement element)
|
||||
else if (item is IElement element)
|
||||
{
|
||||
await element.Delete();
|
||||
}
|
||||
}
|
||||
|
||||
public async Task<CanCommandRun> CanRun(PointInTime startPoint)
|
||||
{
|
||||
var result = CanCommandRun.True;
|
||||
foreach (var itemPath in ItemsToDelete)
|
||||
{
|
||||
var resolvedItem = await itemPath.Resolve();
|
||||
if (!(resolvedItem?.CanDelete ?? true))
|
||||
{
|
||||
result = CanCommandRun.Forceable;
|
||||
}
|
||||
}
|
||||
|
||||
return result;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -4,6 +4,7 @@ namespace FileTime.Core.Command
|
||||
{
|
||||
public interface ICommand
|
||||
{
|
||||
PointInTime SimulateCommand(PointInTime moment);
|
||||
Task<CanCommandRun> CanRun(PointInTime startPoint);
|
||||
Task<PointInTime> SimulateCommand(PointInTime startPoint);
|
||||
}
|
||||
}
|
||||
@@ -1,8 +1,10 @@
|
||||
using FileTime.Core.Timeline;
|
||||
|
||||
namespace FileTime.Core.Command
|
||||
{
|
||||
public interface ICommandHandler
|
||||
{
|
||||
bool CanHandle(object command);
|
||||
void Execute(object command);
|
||||
Task ExecuteAsync(object command, TimeRunner timeRunner);
|
||||
}
|
||||
}
|
||||
@@ -1,7 +1,9 @@
|
||||
using FileTime.Core.Timeline;
|
||||
|
||||
namespace FileTime.Core.Command
|
||||
{
|
||||
public interface IExecutableCommand : ICommand
|
||||
{
|
||||
Task Execute();
|
||||
Task Execute(TimeRunner timeRunner);
|
||||
}
|
||||
}
|
||||
@@ -4,8 +4,8 @@ namespace FileTime.Core.Command
|
||||
{
|
||||
public interface ITransportationCommand : ICommand
|
||||
{
|
||||
IList<IAbsolutePath> Sources { get; }
|
||||
IContainer Target { get; set;}
|
||||
TransportMode TransportMode { get; set; }
|
||||
IList<AbsolutePath>? Sources { get; }
|
||||
IContainer? Target { get; set;}
|
||||
TransportMode? TransportMode { get; set; }
|
||||
}
|
||||
}
|
||||
@@ -5,12 +5,17 @@ namespace FileTime.Core.Command
|
||||
{
|
||||
public class MoveCommand : ITransportationCommand
|
||||
{
|
||||
public IList<IAbsolutePath> Sources { get; } = new List<IAbsolutePath>();
|
||||
public IList<AbsolutePath>? Sources { get; } = new List<AbsolutePath>();
|
||||
|
||||
public IContainer? Target { get; set; }
|
||||
public TransportMode TransportMode { get; set; } = TransportMode.Merge;
|
||||
public TransportMode? TransportMode { get; set; } = Command.TransportMode.Merge;
|
||||
|
||||
public PointInTime SimulateCommand(PointInTime delta)
|
||||
public Task<CanCommandRun> CanRun(PointInTime startPoint)
|
||||
{
|
||||
throw new NotImplementedException();
|
||||
}
|
||||
|
||||
public Task<PointInTime> SimulateCommand(PointInTime startPoint)
|
||||
{
|
||||
throw new NotImplementedException();
|
||||
}
|
||||
|
||||
37
src/Core/FileTime.Core/Command/RenameCommand.cs
Normal file
37
src/Core/FileTime.Core/Command/RenameCommand.cs
Normal file
@@ -0,0 +1,37 @@
|
||||
using FileTime.Core.Models;
|
||||
using FileTime.Core.Timeline;
|
||||
|
||||
namespace FileTime.Core.Command
|
||||
{
|
||||
public class RenameCommand : IExecutableCommand
|
||||
{
|
||||
public AbsolutePath Source { get; }
|
||||
public string Target { get; }
|
||||
|
||||
public RenameCommand(AbsolutePath source, string target)
|
||||
{
|
||||
Source = source;
|
||||
Target = target;
|
||||
}
|
||||
|
||||
public async Task Execute(TimeRunner timeRunner)
|
||||
{
|
||||
var itemToRename = await Source.Resolve();
|
||||
if (itemToRename != null)
|
||||
{
|
||||
await itemToRename.Rename(Target);
|
||||
timeRunner.RefreshContainer?.InvokeAsync(this, new AbsolutePath(itemToRename.GetParent()!));
|
||||
}
|
||||
}
|
||||
|
||||
public Task<PointInTime> SimulateCommand(PointInTime startPoint)
|
||||
{
|
||||
throw new NotImplementedException();
|
||||
}
|
||||
|
||||
public Task<CanCommandRun> CanRun(PointInTime startPoint)
|
||||
{
|
||||
throw new NotImplementedException();
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -7,7 +7,8 @@ namespace FileTime.Core.Components
|
||||
{
|
||||
private IItem? _currentSelectedItem;
|
||||
private IContainer _currentLocation;
|
||||
|
||||
private string? _lastPath;
|
||||
|
||||
public int CurrentSelectedIndex { get; private set; }
|
||||
|
||||
public AsyncEventHandler CurrentLocationChanged = new();
|
||||
@@ -33,10 +34,10 @@ namespace FileTime.Core.Components
|
||||
}
|
||||
|
||||
_currentLocation = value;
|
||||
await CurrentLocationChanged?.InvokeAsync(this, AsyncEventArgs.Empty);
|
||||
await CurrentLocationChanged.InvokeAsync(this, AsyncEventArgs.Empty);
|
||||
|
||||
var currentLocationItems = (await (await GetCurrentLocation()).GetItems())!;
|
||||
await SetCurrentSelectedItem(currentLocationItems.Count > 0 ? currentLocationItems[0] : null);
|
||||
await SetCurrentSelectedItem(await GetItemByLastPath() ?? (currentLocationItems.Count > 0 ? currentLocationItems[0] : null));
|
||||
_currentLocation.Refreshed.Add(HandleCurrentLocationRefresh);
|
||||
}
|
||||
}
|
||||
@@ -58,14 +59,71 @@ namespace FileTime.Core.Components
|
||||
}
|
||||
|
||||
_currentSelectedItem = itemToSelect;
|
||||
_lastPath = GetCommonPath(_lastPath, itemToSelect?.FullName);
|
||||
CurrentSelectedIndex = await GetItemIndex(itemToSelect);
|
||||
await CurrentSelectedItemChanged?.InvokeAsync(this, AsyncEventArgs.Empty);
|
||||
await CurrentSelectedItemChanged.InvokeAsync(this, AsyncEventArgs.Empty);
|
||||
}
|
||||
}
|
||||
public async Task<IItem?> GetItemByLastPath(IContainer? container = null)
|
||||
{
|
||||
container ??= _currentLocation;
|
||||
var containerFullName = container.FullName;
|
||||
|
||||
if (_lastPath == null
|
||||
|| !container.IsLoaded
|
||||
|| (containerFullName != null && !_lastPath.StartsWith(containerFullName))
|
||||
)
|
||||
{
|
||||
return null;
|
||||
}
|
||||
|
||||
|
||||
var itemNameToSelect = _lastPath
|
||||
.Split(Constants.SeparatorChar)
|
||||
.Skip(
|
||||
containerFullName == null
|
||||
? 0
|
||||
: containerFullName
|
||||
.Split(Constants.SeparatorChar)
|
||||
.Count())
|
||||
.FirstOrDefault();
|
||||
|
||||
return (await container.GetItems())?.FirstOrDefault(i => i.Name == itemNameToSelect);
|
||||
}
|
||||
|
||||
private string GetCommonPath(string? oldPath, string? newPath)
|
||||
{
|
||||
var oldPathParts = oldPath?.Split(Constants.SeparatorChar) ?? new string[0];
|
||||
var newPathParts = newPath?.Split(Constants.SeparatorChar) ?? new string[0];
|
||||
|
||||
var commonPathParts = new List<string>();
|
||||
|
||||
var max = oldPathParts.Length > newPathParts.Length ? oldPathParts.Length : newPathParts.Length;
|
||||
|
||||
for (var i = 0; i < max; i++)
|
||||
{
|
||||
if (newPathParts.Length <= i)
|
||||
{
|
||||
commonPathParts.AddRange(oldPathParts.Skip(i));
|
||||
break;
|
||||
}
|
||||
else if (oldPathParts.Length <= i || oldPathParts[i] != newPathParts[i])
|
||||
{
|
||||
commonPathParts.AddRange(newPathParts.Skip(i));
|
||||
break;
|
||||
}
|
||||
else if (oldPathParts[i] == newPathParts[i])
|
||||
{
|
||||
commonPathParts.Add(oldPathParts[i]);
|
||||
}
|
||||
}
|
||||
|
||||
return string.Join(Constants.SeparatorChar, commonPathParts);
|
||||
}
|
||||
|
||||
private async Task HandleCurrentLocationRefresh(object? sender, AsyncEventArgs e)
|
||||
{
|
||||
var currentSelectedName = (await GetCurrentSelectedItem())?.FullName;
|
||||
var currentSelectedName = (await GetCurrentSelectedItem())?.FullName ?? (await GetItemByLastPath()).FullName;
|
||||
var currentLocationItems = (await (await GetCurrentLocation()).GetItems())!;
|
||||
if (currentSelectedName != null)
|
||||
{
|
||||
|
||||
15
src/Core/FileTime.Core/Extensions/TimelineExtensions.cs
Normal file
15
src/Core/FileTime.Core/Extensions/TimelineExtensions.cs
Normal file
@@ -0,0 +1,15 @@
|
||||
using FileTime.Core.Models;
|
||||
using FileTime.Core.Timeline;
|
||||
|
||||
namespace FileTime.Core.Extensions
|
||||
{
|
||||
public static class TimelineExtensions
|
||||
{
|
||||
public static DifferenceItemType ToDifferenceItemType(this IItem? item)
|
||||
{
|
||||
if (item is IContainer) return DifferenceItemType.Container;
|
||||
else if (item is IElement) return DifferenceItemType.Element;
|
||||
else return DifferenceItemType.Unknown;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -4,11 +4,13 @@ namespace FileTime.Core.Interactions
|
||||
{
|
||||
public string Text { get; }
|
||||
public InputType InputType { get; }
|
||||
public string? DefaultValue { get; }
|
||||
|
||||
public InputElement(string text, InputType inputType)
|
||||
public InputElement(string text, InputType inputType, string? defaultValue = null)
|
||||
{
|
||||
Text = text;
|
||||
InputType = inputType;
|
||||
DefaultValue = defaultValue;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -3,6 +3,7 @@ namespace FileTime.Core.Interactions
|
||||
public enum InputType
|
||||
{
|
||||
Text,
|
||||
Password
|
||||
Password,
|
||||
Bool
|
||||
}
|
||||
}
|
||||
@@ -1,17 +1,97 @@
|
||||
using FileTime.Core.Providers;
|
||||
using FileTime.Core.Timeline;
|
||||
|
||||
namespace FileTime.Core.Models
|
||||
{
|
||||
public class AbsolutePath : IAbsolutePath
|
||||
public sealed class AbsolutePath
|
||||
{
|
||||
public IContentProvider ContentProvider { get; }
|
||||
public IContentProvider? VirtualContentProvider { get; }
|
||||
|
||||
public string Path { get; }
|
||||
|
||||
public AbsolutePath(IContentProvider contentProvider, string path)
|
||||
public AbsolutePath(AbsolutePath from)
|
||||
{
|
||||
ContentProvider = from.ContentProvider;
|
||||
Path = from.Path;
|
||||
VirtualContentProvider = from.VirtualContentProvider;
|
||||
}
|
||||
|
||||
public AbsolutePath(IContentProvider contentProvider, string path, IContentProvider? virtualContentProvider)
|
||||
{
|
||||
ContentProvider = contentProvider;
|
||||
Path = path;
|
||||
VirtualContentProvider = virtualContentProvider;
|
||||
}
|
||||
|
||||
public AbsolutePath(IItem item)
|
||||
{
|
||||
if (item is TimeContainer timeContainer)
|
||||
{
|
||||
ContentProvider = timeContainer.Provider;
|
||||
VirtualContentProvider = timeContainer.VirtualProvider;
|
||||
Path = timeContainer.FullName!;
|
||||
}
|
||||
else if (item is TimeElement timeElement)
|
||||
{
|
||||
ContentProvider = timeElement.Provider;
|
||||
VirtualContentProvider = timeElement.VirtualProvider;
|
||||
Path = timeElement.FullName!;
|
||||
}
|
||||
else
|
||||
{
|
||||
ContentProvider = item.Provider;
|
||||
Path = item.FullName!;
|
||||
}
|
||||
}
|
||||
|
||||
public static AbsolutePath FromParentAndChildName(IContainer parent, string childName)
|
||||
{
|
||||
IContentProvider? contentProvider;
|
||||
IContentProvider? virtualContentProvider;
|
||||
string? path;
|
||||
|
||||
if (parent is TimeContainer timeContainer)
|
||||
{
|
||||
contentProvider = timeContainer.Provider;
|
||||
virtualContentProvider = timeContainer.VirtualProvider;
|
||||
path = timeContainer.FullName! + Constants.SeparatorChar + childName;
|
||||
}
|
||||
else
|
||||
{
|
||||
contentProvider = parent.Provider;
|
||||
path = parent.FullName! + Constants.SeparatorChar + childName;
|
||||
virtualContentProvider = null;
|
||||
}
|
||||
|
||||
return new AbsolutePath(contentProvider, path, virtualContentProvider);
|
||||
}
|
||||
|
||||
public bool IsEqual(AbsolutePath path)
|
||||
{
|
||||
//TODO: sure??
|
||||
return path.ContentProvider == ContentProvider && path.Path == Path;
|
||||
}
|
||||
|
||||
public async Task<IItem?> Resolve()
|
||||
{
|
||||
var result = VirtualContentProvider != null && (await VirtualContentProvider.IsExists(Path))
|
||||
? await VirtualContentProvider.GetByPath(Path)
|
||||
: null;
|
||||
|
||||
result ??= await ContentProvider.GetByPath(Path);
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
public string GetParent()
|
||||
{
|
||||
var pathParts = Path.Split(Constants.SeparatorChar);
|
||||
return string.Join(Constants.SeparatorChar, pathParts);
|
||||
}
|
||||
|
||||
public AbsolutePath GetParentAsAbsolutePath() => new(ContentProvider, GetParent(), VirtualContentProvider);
|
||||
|
||||
public string GetName() => Path.Split(Constants.SeparatorChar).Last();
|
||||
}
|
||||
}
|
||||
@@ -1,10 +0,0 @@
|
||||
using FileTime.Core.Providers;
|
||||
|
||||
namespace FileTime.Core.Models
|
||||
{
|
||||
public interface IAbsolutePath
|
||||
{
|
||||
IContentProvider ContentProvider { get; }
|
||||
string Path { get; }
|
||||
}
|
||||
}
|
||||
@@ -9,7 +9,6 @@ namespace FileTime.Core.Models
|
||||
Task<IReadOnlyList<IElement>?> GetElements(CancellationToken token = default);
|
||||
|
||||
Task Refresh();
|
||||
IContainer? GetParent();
|
||||
Task<IItem?> GetByPath(string path);
|
||||
Task<IContainer> CreateContainer(string name);
|
||||
Task<IElement> CreateElement(string name);
|
||||
@@ -18,6 +17,8 @@ namespace FileTime.Core.Models
|
||||
|
||||
Task<IContainer> Clone();
|
||||
|
||||
bool IsLoaded { get; }
|
||||
|
||||
AsyncEventHandler Refreshed { get; }
|
||||
}
|
||||
}
|
||||
@@ -7,7 +7,11 @@ namespace FileTime.Core.Models
|
||||
string Name { get; }
|
||||
string? FullName { get; }
|
||||
bool IsHidden { get; }
|
||||
bool CanDelete { get; }
|
||||
bool CanRename { get; }
|
||||
IContentProvider Provider { get; }
|
||||
Task Delete();
|
||||
Task Rename(string newName);
|
||||
IContainer? GetParent();
|
||||
}
|
||||
}
|
||||
@@ -24,6 +24,9 @@ namespace FileTime.Core.Models
|
||||
public string? FullName => BaseContainer.FullName;
|
||||
|
||||
public bool IsHidden => BaseContainer.IsHidden;
|
||||
public bool IsLoaded => BaseContainer.IsLoaded;
|
||||
public bool CanDelete => BaseContainer.CanDelete;
|
||||
public bool CanRename => BaseContainer.CanRename;
|
||||
|
||||
public IContentProvider Provider => BaseContainer.Provider;
|
||||
|
||||
@@ -159,5 +162,7 @@ namespace FileTime.Core.Models
|
||||
VirtualContainerName
|
||||
);
|
||||
}
|
||||
|
||||
public async Task Rename(string newName) => await BaseContainer.Rename(newName);
|
||||
}
|
||||
}
|
||||
@@ -1,4 +1,5 @@
|
||||
|
||||
using System;
|
||||
using AsyncEvent;
|
||||
using FileTime.Core.Models;
|
||||
|
||||
@@ -16,8 +17,11 @@ namespace FileTime.Core.Providers
|
||||
public string? FullName => null;
|
||||
|
||||
public bool IsHidden => false;
|
||||
public bool IsLoaded => true;
|
||||
|
||||
public IContentProvider Provider => null;
|
||||
public bool CanDelete => false;
|
||||
public bool CanRename => false;
|
||||
|
||||
public AsyncEventHandler Refreshed { get; } = new();
|
||||
|
||||
@@ -52,5 +56,7 @@ namespace FileTime.Core.Providers
|
||||
public Task<IReadOnlyList<IElement>?> GetElements(CancellationToken token = default) => Task.FromResult(_elements);
|
||||
|
||||
public Task<IContainer> Clone() => Task.FromResult((IContainer)this);
|
||||
|
||||
public Task Rename(string newName) => throw new NotSupportedException();
|
||||
}
|
||||
}
|
||||
27
src/Core/FileTime.Core/Timeline/CommandTimeState.cs
Normal file
27
src/Core/FileTime.Core/Timeline/CommandTimeState.cs
Normal 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;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,7 +0,0 @@
|
||||
namespace FileTime.Core.Timeline
|
||||
{
|
||||
public class ContainerSnapshot
|
||||
{
|
||||
|
||||
}
|
||||
}
|
||||
31
src/Core/FileTime.Core/Timeline/Difference.cs
Normal file
31
src/Core/FileTime.Core/Timeline/Difference.cs
Normal 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)
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
8
src/Core/FileTime.Core/Timeline/DifferenceActionType.cs
Normal file
8
src/Core/FileTime.Core/Timeline/DifferenceActionType.cs
Normal file
@@ -0,0 +1,8 @@
|
||||
namespace FileTime.Core.Timeline
|
||||
{
|
||||
public enum DifferenceActionType
|
||||
{
|
||||
Create,
|
||||
Delete
|
||||
}
|
||||
}
|
||||
9
src/Core/FileTime.Core/Timeline/DifferenceItemType.cs
Normal file
9
src/Core/FileTime.Core/Timeline/DifferenceItemType.cs
Normal file
@@ -0,0 +1,9 @@
|
||||
namespace FileTime.Core.Timeline
|
||||
{
|
||||
public enum DifferenceItemType
|
||||
{
|
||||
Container,
|
||||
Element,
|
||||
Unknown
|
||||
}
|
||||
}
|
||||
89
src/Core/FileTime.Core/Timeline/ParallelCommands.cs
Normal file
89
src/Core/FileTime.Core/Timeline/ParallelCommands.cs
Normal 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);
|
||||
}
|
||||
}
|
||||
@@ -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);
|
||||
}
|
||||
}
|
||||
18
src/Core/FileTime.Core/Timeline/ReadOnlyCommandTimeState.cs
Normal file
18
src/Core/FileTime.Core/Timeline/ReadOnlyCommandTimeState.cs
Normal 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;
|
||||
}
|
||||
}
|
||||
}
|
||||
11
src/Core/FileTime.Core/Timeline/ReadOnlyParallelCommands.cs
Normal file
11
src/Core/FileTime.Core/Timeline/ReadOnlyParallelCommands.cs
Normal 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();
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,7 +0,0 @@
|
||||
namespace FileTime.Core.Timeline
|
||||
{
|
||||
public class RootSnapshot
|
||||
{
|
||||
|
||||
}
|
||||
}
|
||||
121
src/Core/FileTime.Core/Timeline/TimeContainer.cs
Normal file
121
src/Core/FileTime.Core/Timeline/TimeContainer.cs
Normal 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);
|
||||
}
|
||||
}
|
||||
}
|
||||
42
src/Core/FileTime.Core/Timeline/TimeElement.cs
Normal file
42
src/Core/FileTime.Core/Timeline/TimeElement.cs
Normal 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;
|
||||
}
|
||||
}
|
||||
89
src/Core/FileTime.Core/Timeline/TimeProvider.cs
Normal file
89
src/Core/FileTime.Core/Timeline/TimeProvider.cs
Normal 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) { }
|
||||
}
|
||||
}
|
||||
217
src/Core/FileTime.Core/Timeline/TimeRunner.cs
Normal file
217
src/Core/FileTime.Core/Timeline/TimeRunner.cs
Normal 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();
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user