Command refactor, fixes, improvements

This commit is contained in:
2022-02-18 23:28:33 +01:00
parent 502f80313a
commit 5a88911ca7
42 changed files with 456 additions and 292 deletions

View File

@@ -0,0 +1,53 @@
using AsyncEvent;
using FileTime.Core.Models;
using FileTime.Core.Timeline;
namespace FileTime.Core.Command
{
public abstract class CommandBase : ICommand
{
private readonly List<string> _canRunMessages = new();
public Dictionary<AbsolutePath, List<OperationProgress>> OperationStatuses { get; set; } = new();
protected OperationProgress? CurrentOperationProgress { get; set; }
public virtual string DisplayLabel { get; protected set; }
public virtual IReadOnlyList<string> CanRunMessages { get; protected set; }
public virtual int Progress { get; protected set; }
public virtual int CurrentProgress { get; protected set; }
public virtual AsyncEventHandler ProgressChanged { get; } = new AsyncEventHandler();
public abstract Task<CanCommandRun> CanRun(PointInTime startPoint);
public abstract Task<PointInTime> SimulateCommand(PointInTime startPoint);
protected CommandBase()
{
CanRunMessages = _canRunMessages.AsReadOnly();
DisplayLabel = "";
}
public async Task UpdateProgress()
{
var total = 0L;
var current = 0L;
foreach (var folder in OperationStatuses.Values)
{
foreach (var item in folder)
{
current += item.Progress;
total += item.TotalCount;
}
}
Progress = total == 0 ? 0 : (int)(current * 100 / total);
if (CurrentOperationProgress == null)
{
CurrentProgress = 0;
}
else
{
CurrentProgress = CurrentOperationProgress.TotalCount == 0 ? 0 : (int)(CurrentOperationProgress.Progress * 100 / CurrentOperationProgress.TotalCount);
}
await ProgressChanged.InvokeAsync(this, AsyncEventArgs.Empty);
}
}
}

View File

@@ -0,0 +1,45 @@
using System.Collections.ObjectModel;
using FileTime.Core.Models;
namespace FileTime.Core.Command.Copy
{
public class CalculateOperation : ICopyOperation
{
private readonly Dictionary<AbsolutePath, List<OperationProgress>> _operationStatuses;
public IReadOnlyDictionary<AbsolutePath, List<OperationProgress>> OperationStatuses { get; }
public CalculateOperation()
{
_operationStatuses = new Dictionary<AbsolutePath, List<OperationProgress>>();
OperationStatuses = new ReadOnlyDictionary<AbsolutePath, List<OperationProgress>>(_operationStatuses);
}
public Task ContainerCopyDoneAsync(AbsolutePath path)
{
return Task.CompletedTask;
}
public async Task CopyAsync(AbsolutePath from, AbsolutePath to, OperationProgress? operation, CopyCommandContext context)
{
var parentPath = to.GetParent();
List<OperationProgress> operationsByFolder;
if (_operationStatuses.ContainsKey(parentPath))
{
operationsByFolder = _operationStatuses[parentPath];
}
else
{
operationsByFolder = new List<OperationProgress>();
_operationStatuses.Add(parentPath, operationsByFolder);
}
var resolvedFrom = await from.ResolveAsync();
operationsByFolder.Add(new OperationProgress(from.Path, resolvedFrom is IElement element ? await element.GetElementSize() : 0L));
}
public Task CreateContainerAsync(IContainer target, string name)
{
return Task.CompletedTask;
}
}
}

View File

@@ -0,0 +1,119 @@
using FileTime.Core.Interactions;
using FileTime.Core.Models;
using FileTime.Core.Timeline;
namespace FileTime.Core.Command.Copy
{
public class CopyCommand : CommandBase, ITransportationCommand
{
public IList<AbsolutePath> Sources { get; } = new List<AbsolutePath>();
public AbsolutePath? Target { get; set; }
public TransportMode? TransportMode { get; set; } = Command.TransportMode.Merge;
internal TimeRunner? TimeRunner { get; private set; }
public bool TargetIsContainer => true;
public List<InputElement> Inputs { get; } = new();
public List<object>? InputResults { get; set; }
public CopyCommand()
{
DisplayLabel = "Copy";
}
public override async Task<PointInTime> SimulateCommand(PointInTime startPoint)
{
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 simulateOperation = new SimulateOperation();
await TraverseTree(Sources, Target, TransportMode.Value, simulateOperation);
return startPoint.WithDifferences(simulateOperation.NewDiffs);
}
public async Task Execute(Func<AbsolutePath, AbsolutePath, OperationProgress?, CopyCommandContext, Task> copy, TimeRunner timeRunner)
{
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");
TimeRunner = timeRunner;
await CalculateProgress();
var copyOperation = new CopyOperation(copy, this);
await TraverseTree(Sources, Target, TransportMode.Value, copyOperation);
await TimeRunner.RefreshContainer.InvokeAsync(this, Target);
}
private async Task CalculateProgress()
{
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 calculateOperation = new CalculateOperation();
await TraverseTree(Sources, Target, TransportMode.Value, calculateOperation);
OperationStatuses = new Dictionary<AbsolutePath, List<OperationProgress>>(calculateOperation.OperationStatuses);
}
private async Task TraverseTree(
IEnumerable<AbsolutePath> sources,
AbsolutePath target,
TransportMode transportMode,
ICopyOperation copyOperation)
{
var resolvedTarget = (IContainer?)await target.ResolveAsync();
foreach (var source in sources)
{
var item = await source.ResolveAsync();
if (item is IContainer container)
{
var targetContainer = target.GetChild(item.Name, AbsolutePathType.Container);
if (resolvedTarget != null)
{
await resolvedTarget.RefreshAsync();
if (!await resolvedTarget.IsExistsAsync(item.Name))
{
await copyOperation.CreateContainerAsync(resolvedTarget, container.Name);
}
}
await TraverseTree((await container.GetItems())!.Select(i => new AbsolutePath(i)), targetContainer, transportMode, copyOperation);
await copyOperation.ContainerCopyDoneAsync(new AbsolutePath(container));
}
else if (item is IElement element)
{
var newElementName = await Helper.CommandHelper.GetNewNameAsync(resolvedTarget, element.Name, transportMode);
if (newElementName == null) continue;
OperationProgress? operation = null;
var newElementPath = target.GetChild(newElementName, AbsolutePathType.Element);
if (OperationStatuses.TryGetValue(target, out var targetPathOperations))
{
var path = new AbsolutePath(element).Path;
operation = targetPathOperations.Find(o => o.Key == path);
}
CurrentOperationProgress = operation;
await copyOperation.CopyAsync(new AbsolutePath(element), newElementPath, operation, new CopyCommandContext(UpdateProgress));
}
}
}
public override Task<CanCommandRun> CanRun(PointInTime startPoint)
{
//TODO: implement
return Task.FromResult(CanCommandRun.True);
}
}
}

View File

@@ -1,4 +1,4 @@
namespace FileTime.Core.Command
namespace FileTime.Core.Command.Copy
{
public class CopyCommandContext
{

View File

@@ -0,0 +1,44 @@
using FileTime.Core.Models;
namespace FileTime.Core.Command.Copy
{
public class CopyOperation : ICopyOperation
{
private readonly Func<AbsolutePath, AbsolutePath, OperationProgress?, CopyCommandContext, Task> _copy;
private readonly CopyCommand _copyCommand;
public CopyOperation(Func<AbsolutePath, AbsolutePath, OperationProgress?, CopyCommandContext, Task> copy, CopyCommand copyCommand)
{
_copy = copy;
_copyCommand = copyCommand;
}
public async Task CopyAsync(AbsolutePath from, AbsolutePath to, OperationProgress? operation, CopyCommandContext context)
{
await _copy(from, to, operation, context);
if (operation != null)
{
operation.Progress = operation.TotalCount;
}
await _copyCommand.UpdateProgress();
}
public async Task CreateContainerAsync(IContainer target, string name) => await target.CreateContainerAsync(name);
public async Task ContainerCopyDoneAsync(AbsolutePath path)
{
if (_copyCommand.OperationStatuses.ContainsKey(path))
{
foreach (var item in _copyCommand.OperationStatuses[path])
{
item.Progress = item.TotalCount;
}
}
if (_copyCommand.TimeRunner != null)
{
await _copyCommand.TimeRunner.RefreshContainer.InvokeAsync(this, path);
}
}
}
}

View File

@@ -0,0 +1,11 @@
using FileTime.Core.Models;
namespace FileTime.Core.Command.Copy
{
public interface ICopyOperation
{
Task ContainerCopyDoneAsync(AbsolutePath path);
Task CopyAsync(AbsolutePath from, AbsolutePath to, OperationProgress? operation, CopyCommandContext context);
Task CreateContainerAsync(IContainer target, string name);
}
}

View File

@@ -0,0 +1,39 @@
using FileTime.Core.Models;
using FileTime.Core.Timeline;
namespace FileTime.Core.Command.Copy
{
public class SimulateOperation : ICopyOperation
{
private readonly List<Difference> _newDiffs;
public IReadOnlyList<Difference> NewDiffs { get; }
public SimulateOperation()
{
_newDiffs = new List<Difference>();
NewDiffs = _newDiffs.AsReadOnly();
}
public Task ContainerCopyDoneAsync(AbsolutePath path)
{
return Task.CompletedTask;
}
public Task CopyAsync(AbsolutePath from, AbsolutePath to, OperationProgress? operation, CopyCommandContext context)
{
_newDiffs.Add(new Difference(DifferenceActionType.Create, to));
return Task.CompletedTask;
}
public Task CreateContainerAsync(IContainer target, string name)
{
var newContainerDiff = new Difference(
DifferenceActionType.Create,
AbsolutePath.FromParentAndChildName(target, name, AbsolutePathType.Container)
);
_newDiffs.Add(newContainerDiff);
return Task.FromResult((IContainer)null!);
}
}
}

View File

@@ -1,211 +0,0 @@
using AsyncEvent;
using FileTime.Core.Interactions;
using FileTime.Core.Models;
using FileTime.Core.Timeline;
namespace FileTime.Core.Command
{
public class CopyCommand : ITransportationCommand
{
private Func<AbsolutePath, AbsolutePath, OperationProgress?, CopyCommandContext, Task>? _copyOperation;
private Dictionary<AbsolutePath, List<OperationProgress>> _operationStatuses = new();
private Func<IContainer, string, Task<IContainer>>? _createContainer;
private Func<AbsolutePath, Task>? _containerCopyDone;
private OperationProgress? _currentOperationProgress;
public IList<AbsolutePath> Sources { get; } = new List<AbsolutePath>();
public AbsolutePath? Target { get; set; }
public TransportMode? TransportMode { get; set; } = Command.TransportMode.Merge;
public int Progress { get; private set; }
public int CurrentProgress { get; private set; }
public AsyncEventHandler ProgressChanged { get; } = new();
public string DisplayLabel { get; } = "Copy";
public IReadOnlyList<string> CanRunMessages { get; } = new List<string>().AsReadOnly();
public bool TargetIsContainer => true;
public List<InputElement> Inputs { get; } = new();
public List<object>? InputResults { get; set; }
private async Task UpdateProgress()
{
var total = 0L;
var current = 0L;
foreach (var folder in _operationStatuses.Values)
{
foreach (var item in folder)
{
current += item.Progress;
total += item.TotalCount;
}
}
Progress = (int)(current * 100 / total);
if (_currentOperationProgress == null)
{
CurrentProgress = 0;
}
else
{
CurrentProgress = (int)(_currentOperationProgress.Progress * 100 / _currentOperationProgress.TotalCount);
}
await ProgressChanged.InvokeAsync(this, AsyncEventArgs.Empty);
}
public async Task<PointInTime> SimulateCommand(PointInTime startPoint)
{
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, _, _) =>
{
newDiffs.Add(new Difference(DifferenceActionType.Create, to));
return Task.CompletedTask;
};
_createContainer = (IContainer target, string name) =>
{
var newContainerDiff = new Difference(
DifferenceActionType.Create,
AbsolutePath.FromParentAndChildName(target, name, AbsolutePathType.Container)
);
newDiffs.Add(newContainerDiff);
return Task.FromResult((IContainer)null!);
};
await TraverseTree(Sources, Target, TransportMode.Value);
return startPoint.WithDifferences(newDiffs);
}
public async Task Execute(Func<AbsolutePath, AbsolutePath, OperationProgress?, CopyCommandContext, Task> copy, TimeRunner timeRunner)
{
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");
await CalculateProgress();
_copyOperation = async (from, to, operation, context) =>
{
await copy(from, to, operation, context);
if (operation != null)
{
operation.Progress = operation.TotalCount;
}
await UpdateProgress();
};
_createContainer = async (IContainer target, string name) => await target.CreateContainerAsync(name);
_containerCopyDone = async (path) =>
{
foreach (var item in _operationStatuses[path])
{
item.Progress = item.TotalCount;
}
if (timeRunner != null)
{
await timeRunner.RefreshContainer.InvokeAsync(this, path);
}
};
await TraverseTree(Sources, Target, TransportMode.Value);
}
private async Task CalculateProgress()
{
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 operationStatuses = new Dictionary<AbsolutePath, List<OperationProgress>>();
_copyOperation = async (from, to, _, _) =>
{
var parentPath = to.GetParent();
List<OperationProgress> operationsByFolder;
if (operationStatuses.ContainsKey(parentPath))
{
operationsByFolder = operationStatuses[parentPath];
}
else
{
var resolvedFrom = await from.ResolveAsync();
operationsByFolder = new List<OperationProgress>();
operationStatuses.Add(parentPath, operationsByFolder);
operationsByFolder.Add(new OperationProgress(from.Path, resolvedFrom is IElement element ? await element.GetElementSize() : 0L));
}
};
await TraverseTree(Sources, Target, TransportMode.Value);
_operationStatuses = operationStatuses;
}
private async Task TraverseTree(
IEnumerable<AbsolutePath> sources,
AbsolutePath 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.");
var resolvedTarget = (IContainer?)await target.ResolveAsync();
foreach (var source in sources)
{
var item = await source.ResolveAsync();
if (item is IContainer container)
{
var targetContainer = target.GetChild(item.Name, AbsolutePathType.Container);
if (_createContainer != null
&& resolvedTarget != null
&& !await resolvedTarget.IsExistsAsync(item.Name))
{
await _createContainer.Invoke(resolvedTarget, container.Name);
}
var childDirectories = (await container.GetContainers())!.Select(d => new AbsolutePath(d));
var childFiles = (await container.GetElements())!.Select(f => new AbsolutePath(f));
await TraverseTree(childDirectories.Concat(childFiles), targetContainer, transportMode);
if (_containerCopyDone != null) await _containerCopyDone.Invoke(new AbsolutePath(container));
}
else if (item is IElement element)
{
var targetName = await Helper.CommandHelper.GetNewNameAsync(resolvedTarget, element.Name, transportMode);
if (targetName == null) continue;
OperationProgress? operation = null;
var targetFolderPath = new AbsolutePath(target);
var targetElementPath = target.GetChild(targetName, AbsolutePathType.Element);
if (_operationStatuses.TryGetValue(targetFolderPath, out var targetPathOperations))
{
var path = new AbsolutePath(element).Path;
operation = targetPathOperations.Find(o => o.Key == path);
}
_currentOperationProgress = operation;
if (_copyOperation != null) await _copyOperation.Invoke(new AbsolutePath(element), targetElementPath, operation, new CopyCommandContext(UpdateProgress));
}
}
}
public Task<CanCommandRun> CanRun(PointInTime startPoint)
{
//TODO: implement
return Task.FromResult(CanCommandRun.True);
}
}
}

View File

@@ -2,7 +2,7 @@ using AsyncEvent;
using FileTime.Core.Models;
using FileTime.Core.Timeline;
namespace FileTime.Core.Command
namespace FileTime.Core.Command.CreateContainer
{
public class CreateContainerCommand : IExecutableCommand
{
@@ -16,7 +16,6 @@ namespace FileTime.Core.Command
public string DisplayLabel { get; }
public IReadOnlyList<string> CanRunMessages { get; } = new List<string>().AsReadOnly();
public CreateContainerCommand(AbsolutePath container, string newContainerName)
{
Container = container;

View File

@@ -2,7 +2,7 @@ using AsyncEvent;
using FileTime.Core.Models;
using FileTime.Core.Timeline;
namespace FileTime.Core.Command
namespace FileTime.Core.Command.CreateElement
{
public class CreateElementCommand : IExecutableCommand
{

View File

@@ -2,7 +2,7 @@ using AsyncEvent;
using FileTime.Core.Models;
using FileTime.Core.Timeline;
namespace FileTime.Core.Command
namespace FileTime.Core.Command.Delete
{
public class DeleteCommand : IExecutableCommand
{
@@ -64,6 +64,11 @@ namespace FileTime.Core.Command
{
await TraverseTree((await item.ResolveAsync())!);
}
foreach(var updatedParent in ItemsToDelete.Select(i => i.GetParent()).Distinct())
{
await timeRunner.RefreshContainer.InvokeAsync(this, updatedParent);
}
}
private async Task TraverseTree(IItem item)

View File

@@ -3,7 +3,7 @@ using FileTime.Core.Interactions;
using FileTime.Core.Models;
using FileTime.Core.Timeline;
namespace FileTime.Core.Command
namespace FileTime.Core.Command.Move
{
public class MoveCommand : ITransportationCommand
{

View File

@@ -2,7 +2,7 @@ using AsyncEvent;
using FileTime.Core.Models;
using FileTime.Core.Timeline;
namespace FileTime.Core.Command
namespace FileTime.Core.Command.Rename
{
public class RenameCommand : IExecutableCommand
{
@@ -27,7 +27,7 @@ namespace FileTime.Core.Command
if (itemToRename != null)
{
await itemToRename.Rename(Target);
if (timeRunner.RefreshContainer != null) await timeRunner.RefreshContainer.InvokeAsync(this, new AbsolutePath(itemToRename.GetParent()!));
await timeRunner.RefreshContainer.InvokeAsync(this, new AbsolutePath(itemToRename.GetParent()!));
}
}

View File

@@ -1,4 +1,5 @@
using FileTime.Core.Command;
using FileTime.Core.Command.Copy;
using FileTime.Core.Models;
using FileTime.Core.Timeline;

View File

@@ -61,42 +61,51 @@ namespace FileTime.Core.Components
{
if (_currentlySelecting) return false;
IItem? itemToSelect = null;
if (value != null)
try
{
itemToSelect = (await _currentLocation.GetItems(token))?.FirstOrDefault(i =>
i.FullName == null && value?.FullName == null
? i.Name == value?.Name
: i.FullName == value?.FullName);
if (itemToSelect == null) throw new IndexOutOfRangeException($"Provided item ({value.FullName ?? "unknwon"}) does not exists in the current container ({_currentLocation.FullName ?? "unknwon"}).");
}
_currentlySelecting = true;
CancellationToken newToken;
lock (_guardSetCurrentSelectedItemCTS)
IItem? itemToSelect = null;
if (value != null)
{
itemToSelect = (await _currentLocation.GetItems(token))?.FirstOrDefault(i =>
i.FullName == null && value?.FullName == null
? i.Name == value?.Name
: i.FullName == value?.FullName);
if (itemToSelect == null) throw new IndexOutOfRangeException($"Provided item ({value.FullName ?? "unknwon"}) does not exists in the current container ({_currentLocation.FullName ?? "unknwon"}).");
}
CancellationToken newToken;
lock (_guardSetCurrentSelectedItemCTS)
{
if (token.IsCancellationRequested) return false;
_setCurrentSelectedItemCTS?.Cancel();
if (token.IsCancellationRequested)
{
_setCurrentSelectedItemCTS = new CancellationTokenSource();
newToken = _setCurrentSelectedItemCTS.Token;
}
else
{
_setCurrentSelectedItemCTS = new CancellationTokenSource();
newToken = CancellationTokenSource.CreateLinkedTokenSource(_setCurrentSelectedItemCTS.Token, token).Token;
}
}
_currentSelectedItem = itemToSelect;
_lastPath = GetCommonPath(_lastPath, itemToSelect?.FullName);
var newCurrentSelectedIndex = await GetItemIndex(itemToSelect, CancellationToken.None);
CurrentSelectedIndex = newCurrentSelectedIndex;
await CurrentSelectedItemChanged.InvokeAsync(this, AsyncEventArgs.Empty, newToken);
return !newToken.IsCancellationRequested;
}
finally
{
if (token.IsCancellationRequested) return false;
_setCurrentSelectedItemCTS?.Cancel();
if (token.IsCancellationRequested)
{
_setCurrentSelectedItemCTS = new CancellationTokenSource();
newToken = _setCurrentSelectedItemCTS.Token;
}
else
{
_setCurrentSelectedItemCTS = new CancellationTokenSource();
newToken = CancellationTokenSource.CreateLinkedTokenSource(_setCurrentSelectedItemCTS.Token, token).Token;
}
_currentlySelecting = false;
}
_currentSelectedItem = itemToSelect;
_lastPath = GetCommonPath(_lastPath, itemToSelect?.FullName);
var newCurrentSelectedIndex = await GetItemIndex(itemToSelect, CancellationToken.None);
CurrentSelectedIndex = newCurrentSelectedIndex;
await CurrentSelectedItemChanged.InvokeAsync(this, AsyncEventArgs.Empty, newToken);
return !newToken.IsCancellationRequested;
}
public async Task<IItem?> GetItemByLastPath(IContainer? container = null)
{
@@ -211,30 +220,35 @@ namespace FileTime.Core.Components
{
if (token.IsCancellationRequested) return;
_currentlySelecting = true;
if (AutoRefresh && currentLocation != null)
{
await currentLocation.RefreshAsync(token);
if (token.IsCancellationRequested) return;
}
IItem? newSelectedItem = null;
foreach (var item in currentPossibleItems)
try
{
if (currentLocationItems.FirstOrDefault(i => i.Name == item.Name) is var possibleNewSelectedItem
&& possibleNewSelectedItem is not null)
_currentlySelecting = true;
if (AutoRefresh && currentLocation != null)
{
newSelectedItem = possibleNewSelectedItem;
break;
await currentLocation.RefreshAsync(token);
if (token.IsCancellationRequested) return;
}
foreach (var item in currentPossibleItems)
{
if (currentLocationItems.FirstOrDefault(i => i.Name == item.Name) is var possibleNewSelectedItem
&& possibleNewSelectedItem is not null)
{
newSelectedItem = possibleNewSelectedItem;
break;
}
}
if (newSelectedItem != null)
{
newSelectedItem = (await (await GetCurrentLocation(token)).GetItems(token))?.FirstOrDefault(i => i.Name == newSelectedItem.Name);
}
}
if (newSelectedItem != null)
finally
{
newSelectedItem = (await (await GetCurrentLocation(token)).GetItems(token))?.FirstOrDefault(i => i.Name == newSelectedItem.Name);
_currentlySelecting = false;
}
_currentlySelecting = false;
await SetCurrentSelectedItem(newSelectedItem ?? (currentLocationItems.Count > 0 ? currentLocationItems[0] : null), token: token);
}
else

View File

@@ -10,7 +10,7 @@ namespace FileTime.Core.Models
Task<IReadOnlyList<IElement>?> GetElements(CancellationToken token = default);
Task RefreshAsync(CancellationToken token = default);
async Task<IItem?> GetByPath(string path, bool acceptDeepestMatch = false)
public async Task<IItem?> GetByPath(string path, bool acceptDeepestMatch = false)
{
if (path == null) return this;
var paths = path.Split(Constants.SeparatorChar);

View File

@@ -9,6 +9,7 @@ namespace FileTime.Core.Models
string? NativePath { get; }
bool IsHidden { get; }
bool IsDestroyed { get; }
bool IsExists { get; }
SupportsDelete CanDelete { get; }
bool CanRename { get; }
IContentProvider Provider { get; }

View File

@@ -37,6 +37,7 @@ namespace FileTime.Core.Models
public AsyncEventHandler Refreshed { get; }
public bool IsDestroyed => BaseContainer.IsDestroyed;
public bool IsExists => BaseContainer.IsExists;
private void RefreshAddBase(Func<object?, AsyncEventArgs, CancellationToken, Task> handler)
{

View File

@@ -37,12 +37,14 @@ namespace FileTime.Core.Providers
IContentProvider IItem.Provider => Provider;
public abstract bool IsExists { get; }
protected AbstractContainer(TProvider provider, IContainer parent, string name)
{
_parent = parent;
Provider = provider;
Name = name;
FullName = (parent?.FullName ?? Name) + Constants.SeparatorChar + Name;
FullName = parent.FullName == null ? Name : parent.FullName + Constants.SeparatorChar + Name;
Exceptions = _exceptions.AsReadOnly();
}

View File

@@ -33,6 +33,7 @@ namespace FileTime.Core.Providers
public bool SupportsDirectoryLevelSoftDelete => false;
public bool IsDestroyed => false;
public bool IsExists => true;
public TopContainer(IEnumerable<IContentProvider> contentProviders)
{

View File

@@ -33,6 +33,7 @@ namespace FileTime.Core.Timeline
//FIXME: currently this can be different of the real items NativePath, should be fixed
public string? NativePath => FullName;
public bool IsExists => true;
public TimeContainer(string name, IContainer parent, IContentProvider contentProvider, IContentProvider virtualContentProvider, PointInTime pointInTime)
{

View File

@@ -35,6 +35,7 @@ namespace FileTime.Core.Timeline
public IContentProvider VirtualProvider { get; }
public bool IsDestroyed { get; private set; }
public bool IsExists => true;
public Task Delete(bool hardDelete = false) => Task.CompletedTask;

View File

@@ -33,6 +33,7 @@ namespace FileTime.Core.Timeline
public bool SupportsContentStreams => false;
public string Protocol => "time2://";
public bool IsExists => true;
public TimeProvider(PointInTime pointInTime)
{

View File

@@ -24,8 +24,8 @@ namespace FileTime.Avalonia
.BuildServiceProvider()
.InitSerilog();
var _logger = ServiceProvider.GetService<ILogger<App>>();
_logger?.LogInformation("App initialization completed.");
var logger = ServiceProvider.GetService<ILogger<App>>();
logger?.LogInformation("App initialization completed.");
}
public override void Initialize()

View File

@@ -11,6 +11,7 @@ using System.Linq;
using System.Threading.Tasks;
using FileTime.App.Core.Tab;
using System.Threading;
using FileTime.Core.Timeline;
namespace FileTime.Avalonia.Application
{
@@ -18,6 +19,7 @@ namespace FileTime.Avalonia.Application
[Inject(typeof(ItemNameConverterService))]
[Inject(typeof(LocalContentProvider))]
[Inject(typeof(Tab))]
[Inject(typeof(TimeRunner), propertyName: "_timeRunner")]
public partial class TabContainer : INewItemProcessor
{
private bool _updateFromCode;
@@ -77,6 +79,21 @@ namespace FileTime.Avalonia.Application
partial void OnInitialize()
{
_tabState = new TabState(Tab);
_timeRunner.RefreshContainer.Add(TimeRunnerContainerRefreshed);
}
private async Task TimeRunnerContainerRefreshed(object? sender, AbsolutePath container, CancellationToken token = default)
{
var currentLocation = await Tab.GetCurrentLocation();
if (currentLocation != null)
{
var currentLocationPath = new AbsolutePath(currentLocation);
if (currentLocationPath == container)
{
await currentLocation.RefreshAsync();
}
}
}
public async Task Init(int tabNumber)

View File

@@ -12,6 +12,12 @@ using FileTime.Avalonia.Misc;
using FileTime.Avalonia.Models;
using FileTime.Avalonia.ViewModels;
using FileTime.Core.Command;
using FileTime.Core.Command.Copy;
using FileTime.Core.Command.CreateContainer;
using FileTime.Core.Command.CreateElement;
using FileTime.Core.Command.Delete;
using FileTime.Core.Command.Move;
using FileTime.Core.Command.Rename;
using FileTime.Core.Components;
using FileTime.Core.Interactions;
using FileTime.Core.Models;
@@ -229,7 +235,7 @@ namespace FileTime.Avalonia.Services
var newTab = new Tab();
await newTab.Init(newContainer);
tabContainer = new TabContainer(newTab, _localContentProvider, _itemNameConverterService);
tabContainer = new TabContainer(_timeRunner, newTab, _localContentProvider, _itemNameConverterService);
await tabContainer.Init(number);
var i = 0;

View File

@@ -1,6 +1,4 @@
using System.Linq;
using System.Net;
using System.Text;
using System.Collections.Generic;
using System.IO;
using System.Text.Json;
@@ -13,6 +11,7 @@ using FileTime.Core.Providers;
using FileTime.Providers.Local;
using FileTime.Core.Models;
using Microsoft.Extensions.Logging;
using FileTime.Core.Timeline;
namespace FileTime.Avalonia.Services
{
@@ -25,13 +24,15 @@ namespace FileTime.Avalonia.Services
private readonly IEnumerable<IContentProvider> _contentProviders;
private readonly LocalContentProvider _localContentProvider;
private readonly ILogger<StatePersistenceService> _logger;
private readonly TimeRunner _timeRunner;
public StatePersistenceService(
AppState appState,
ItemNameConverterService itemNameConverterService,
IEnumerable<IContentProvider> contentProviders,
LocalContentProvider localContentProvider,
ILogger<StatePersistenceService> logger)
ILogger<StatePersistenceService> logger,
TimeRunner timeRunner)
{
_appState = appState;
_itemNameConverterService = itemNameConverterService;
@@ -45,6 +46,7 @@ namespace FileTime.Avalonia.Services
PropertyNameCaseInsensitive = true,
WriteIndented = true
};
this._timeRunner = timeRunner;
}
public async Task LoadStatesAsync()
@@ -151,7 +153,7 @@ namespace FileTime.Avalonia.Services
}
}
var newTabContainer = new TabContainer(newTab, _localContentProvider, _itemNameConverterService);
var newTabContainer = new TabContainer(_timeRunner, newTab, _localContentProvider, _itemNameConverterService);
await newTabContainer.Init(tab.Number);
_appState.Tabs.Add(newTabContainer);
}

View File

@@ -154,10 +154,10 @@ namespace FileTime.Avalonia.ViewModels
{
_isRefreshing = true;
List<ContainerViewModel> newContainers = new List<ContainerViewModel>();
List<ElementViewModel> newElements = new List<ElementViewModel>();
List<ContainerViewModel> newContainers = new();
List<ElementViewModel> newElements = new();
if (await _container.GetContainers() is IReadOnlyList<IContainer> containers)
if (await _container.GetContainers(token) is IReadOnlyList<IContainer> containers)
{
foreach (var container in containers)
{
@@ -165,7 +165,7 @@ namespace FileTime.Avalonia.ViewModels
}
}
if (await _container.GetElements() is IReadOnlyList<IElement> elements)
if (await _container.GetElements(token) is IReadOnlyList<IElement> elements)
{
foreach (var element in elements)
{
@@ -216,7 +216,7 @@ namespace FileTime.Avalonia.ViewModels
_exceptions.Add(e);
}
await _newItemProcessor.UpdateMarkedItems(this);
await _newItemProcessor.UpdateMarkedItems(this, CancellationToken.None);
_isRefreshing = false;
}

View File

@@ -84,7 +84,7 @@ namespace FileTime.Avalonia.ViewModels
var tab = new Tab();
await tab.Init(LocalContentProvider);
var tabContainer = new TabContainer(tab, LocalContentProvider, ItemNameConverterService);
var tabContainer = new TabContainer(_timeRunner, tab, LocalContentProvider, ItemNameConverterService);
await tabContainer.Init(1);
tabContainer.IsSelected = true;
AppState.Tabs.Add(tabContainer);

View File

@@ -87,7 +87,7 @@ namespace FileTime.Avalonia.Views
private void InputText_AttachedToVisualTree(object? sender, VisualTreeAttachmentEventArgs e)
{
if (sender is TextBox inputText && inputText.DataContext is InputElementWrapper inputElementWrapper && inputElementWrapper == ViewModel!.AppState.Inputs.First())
if (sender is TextBox inputText && inputText.IsVisible && inputText.DataContext is InputElementWrapper inputElementWrapper && inputElementWrapper == ViewModel!.AppState.Inputs[0])
{
inputText.Focus();
}

View File

@@ -38,6 +38,7 @@ namespace FileTime.Providers.Local
public bool IsDestroyed => false;
public bool SupportsContentStreams => true;
public bool IsExists => true;
public LocalContentProvider(ILogger<LocalContentProvider> logger)
{
@@ -49,8 +50,6 @@ namespace FileTime.Providers.Local
? new DirectoryInfo("/").GetDirectories()
: Environment.GetLogicalDrives().Select(d => new DirectoryInfo(d));
FullName = RuntimeInformation.IsOSPlatform(OSPlatform.Linux) ? "" : null;
_rootContainers = rootDirectories.Select(d => new LocalFolder(d, this, this)).OrderBy(d => d.Name).ToList().AsReadOnly();
_items = _rootContainers.Cast<IItem>().ToList().AsReadOnly();
}

View File

@@ -15,6 +15,7 @@ namespace FileTime.Providers.Local
public string FullName { get; }
public string? NativePath => File.FullName;
public bool IsExists => File.Exists;
public IContentProvider Provider { get; }

View File

@@ -5,13 +5,14 @@ using FileTime.Providers.Local.Interop;
namespace FileTime.Providers.Local
{
public class LocalFolder : AbstractContainer<LocalContentProvider>
public class LocalFolder : AbstractContainer<LocalContentProvider>, IContainer
{
public DirectoryInfo Directory { get; }
public string Attributes => GetAttributes();
public DateTime CreatedAt => Directory.CreationTime;
public override bool IsExists => Directory.Exists;
public LocalFolder(DirectoryInfo directory, LocalContentProvider contentProvider, IContainer parent)
: base(contentProvider, parent, directory.Name.TrimEnd(Path.DirectorySeparatorChar))
@@ -52,7 +53,7 @@ namespace FileTime.Providers.Local
return Task.FromResult(Enumerable.Empty<IItem>());
}
public async Task<IItem?> GetByPath(string path, bool acceptDeepestMatch = false)
async Task<IItem?> IContainer.GetByPath(string path, bool acceptDeepestMatch)
{
var paths = path.Split(Constants.SeparatorChar);
@@ -87,7 +88,7 @@ namespace FileTime.Providers.Local
return (await GetElements())!.FirstOrDefault(e => Provider.NormalizePath(e.Name) == Provider.NormalizePath(name))!;
}
public override async Task<bool> IsExistsAsync(string name) => (await GetItems())?.Any(i => Provider.NormalizePath(i.Name) == Provider.NormalizePath(name)) ?? false;
public override async Task<bool> IsExistsAsync(string name) => (await GetItems())?.Any(i => i.IsExists && Provider.NormalizePath(i.Name) == Provider.NormalizePath(name)) ?? false;
public override Task Delete(bool hardDelete = false)
{

View File

@@ -43,6 +43,7 @@ namespace FileTime.Providers.Sftp
public IContentProvider Provider => this;
public string Protocol => "sftp://";
public bool IsExists => true;
public SftpContentProvider(IInputInterface inputInterface, ILogger<SftpContentProvider> logger)
{

View File

@@ -23,6 +23,8 @@ namespace FileTime.Providers.Sftp
public IContentProvider Provider => throw new NotImplementedException();
public bool IsExists => throw new NotImplementedException();
public Task Delete(bool hardDelete = false)
{
throw new NotImplementedException();

View File

@@ -6,6 +6,7 @@ namespace FileTime.Providers.Sftp
public class SftpFolder : AbstractContainer<SftpContentProvider>
{
private readonly SftpServer _server;
public override bool IsExists => true;
public SftpFolder(SftpContentProvider provider, SftpServer server, IContainer parent, string path) : base(provider, parent, path)
{

View File

@@ -18,6 +18,7 @@ namespace FileTime.Providers.Sftp
public string? Username { get; private set; }
public string? Password { get; private set; }
public override bool IsExists => true;
public SftpServer(string name, SftpContentProvider sftpContentProvider, IInputInterface inputInterface, string? username = null, string? password = null)
: base(sftpContentProvider, sftpContentProvider, name)

View File

@@ -42,6 +42,7 @@ namespace FileTime.Providers.Smb
public bool IsDestroyed => false;
public bool SupportsContentStreams => true;
public bool IsExists => true;
public SmbContentProvider(IInputInterface inputInterface, Persistence.PersistenceService persistenceService, ILogger<SmbContentProvider> logger)
{

View File

@@ -24,6 +24,8 @@ namespace FileTime.Providers.Smb
public IContentProvider Provider { get; }
public bool IsDestroyed { get; private set; }
//TODO: implement
public bool IsExists => true;
public SmbFile(string name, SmbContentProvider provider, SmbShare smbShare, IContainer parent, SmbClientContext smbClientContext)
{

View File

@@ -8,8 +8,9 @@ namespace FileTime.Providers.Smb
{
private readonly SmbClientContext _smbClientContext;
public SmbShare SmbShare { get; }
//TODO: implement
public override bool IsExists => true;
public SmbFolder(string name, SmbContentProvider contentProvider, SmbShare smbShare, IContainer parent, SmbClientContext smbClientContext)
: base(contentProvider, parent, name)

View File

@@ -8,7 +8,7 @@ using SMBLibrary.Client;
namespace FileTime.Providers.Smb
{
public class SmbServer : AbstractContainer<SmbContentProvider>
public class SmbServer : AbstractContainer<SmbContentProvider>, IContainer
{
internal const int MAXRETRIES = 5;
@@ -21,6 +21,7 @@ namespace FileTime.Providers.Smb
public string? Username { get; private set; }
public string? Password { get; private set; }
public override bool IsExists => true;
public SmbServer(string name, SmbContentProvider contentProvider, IInputInterface inputInterface, string? username = null, string? password = null)
: base(contentProvider, contentProvider, name)
@@ -52,7 +53,7 @@ namespace FileTime.Providers.Smb
return Task.CompletedTask;
}
public async Task<IItem?> GetByPath(string path, bool acceptDeepestMatch = false)
async Task<IItem?> IContainer.GetByPath(string path, bool acceptDeepestMatch)
{
var paths = path.Split(Constants.SeparatorChar);

View File

@@ -8,6 +8,7 @@ namespace FileTime.Providers.Smb
public class SmbShare : AbstractContainer<SmbContentProvider>
{
private readonly SmbClientContext _smbClientContext;
public override bool IsExists => true;
public SmbShare(string name, SmbContentProvider contentProvider, IContainer parent, SmbClientContext smbClientContext)
: base(contentProvider, parent, name)