diff --git a/src/AppCommon/FileTime.App.Core.Abstraction/ViewModels/Timeline/ICommandTimeStateViewModel.cs b/src/AppCommon/FileTime.App.Core.Abstraction/ViewModels/Timeline/ICommandTimeStateViewModel.cs index d5f0aec..59d12c3 100644 --- a/src/AppCommon/FileTime.App.Core.Abstraction/ViewModels/Timeline/ICommandTimeStateViewModel.cs +++ b/src/AppCommon/FileTime.App.Core.Abstraction/ViewModels/Timeline/ICommandTimeStateViewModel.cs @@ -7,4 +7,5 @@ public interface ICommandTimeStateViewModel IObservable DisplayLabel { get; } IObservable DisplayDetailLabel { get; } IObservable IsSelected { get; } + void Cancel(); } \ No newline at end of file diff --git a/src/AppCommon/FileTime.App.Core/ViewModels/Timeline/CommandTimeStateViewModel.cs b/src/AppCommon/FileTime.App.Core/ViewModels/Timeline/CommandTimeStateViewModel.cs index 1bef90d..f6765c4 100644 --- a/src/AppCommon/FileTime.App.Core/ViewModels/Timeline/CommandTimeStateViewModel.cs +++ b/src/AppCommon/FileTime.App.Core/ViewModels/Timeline/CommandTimeStateViewModel.cs @@ -5,6 +5,7 @@ namespace FileTime.App.Core.ViewModels.Timeline; public class CommandTimeStateViewModel : ICommandTimeStateViewModel { + private readonly CommandTimeState _commandTimeState; public IObservable TotalProgress { get; } public IObservable CurrentProgress { get; } @@ -15,6 +16,7 @@ public class CommandTimeStateViewModel : ICommandTimeStateViewModel public CommandTimeStateViewModel(CommandTimeState commandTimeState) { + _commandTimeState = commandTimeState; DisplayLabel = commandTimeState.Command.DisplayLabel; DisplayDetailLabel = commandTimeState.Command.DisplayDetailLabel; TotalProgress = commandTimeState.Command.TotalProgress; @@ -22,4 +24,7 @@ public class CommandTimeStateViewModel : ICommandTimeStateViewModel //TODO IsSelected = new BehaviorSubject(false); } + + public void Cancel() + => _commandTimeState.Command.Cancel(); } \ No newline at end of file diff --git a/src/Core/FileTime.Core.Abstraction/Command/ICommand.cs b/src/Core/FileTime.Core.Abstraction/Command/ICommand.cs index 89b1c54..2fcde92 100644 --- a/src/Core/FileTime.Core.Abstraction/Command/ICommand.cs +++ b/src/Core/FileTime.Core.Abstraction/Command/ICommand.cs @@ -11,4 +11,5 @@ public interface ICommand Task CanRun(PointInTime currentTime); Task SimulateCommand(PointInTime currentTime); + void Cancel(); } \ No newline at end of file diff --git a/src/Core/FileTime.Core.Abstraction/ContentAccess/IContentWriter.cs b/src/Core/FileTime.Core.Abstraction/ContentAccess/IContentWriter.cs index 83d4e39..65da2a1 100644 --- a/src/Core/FileTime.Core.Abstraction/ContentAccess/IContentWriter.cs +++ b/src/Core/FileTime.Core.Abstraction/ContentAccess/IContentWriter.cs @@ -4,6 +4,6 @@ public interface IContentWriter : IDisposable { int PreferredBufferSize { get; } - Task WriteBytesAsync(byte[] data, int? index = null); - Task FlushAsync(); + Task WriteBytesAsync(byte[] data, int? index = null, CancellationToken cancellationToken = default); + Task FlushAsync(CancellationToken cancellationToken = default); } \ No newline at end of file diff --git a/src/Core/FileTime.Core.Command/CommandBase.cs b/src/Core/FileTime.Core.Command/CommandBase.cs index fd84413..dafab62 100644 --- a/src/Core/FileTime.Core.Command/CommandBase.cs +++ b/src/Core/FileTime.Core.Command/CommandBase.cs @@ -31,6 +31,7 @@ public abstract class CommandBase : ICommand public abstract Task CanRun(PointInTime currentTime); public abstract Task SimulateCommand(PointInTime currentTime); + public abstract void Cancel(); protected void SetDisplayLabel(string? displayLabel) => _displayLabel.OnNext(displayLabel ?? string.Empty); protected void SetDisplayDetailLabel(string? displayLabel) => _displayDetailLabel.OnNext(displayLabel ?? string.Empty); diff --git a/src/Core/FileTime.Core.Command/Copy/CopyCommand.cs b/src/Core/FileTime.Core.Command/Copy/CopyCommand.cs index d869fa9..04bf7cb 100644 --- a/src/Core/FileTime.Core.Command/Copy/CopyCommand.cs +++ b/src/Core/FileTime.Core.Command/Copy/CopyCommand.cs @@ -14,6 +14,7 @@ public class CopyCommand : CommandBase, ITransportationCommand private readonly ITimelessContentProvider _timelessContentProvider; private readonly ICommandSchedulerNotifier _commandSchedulerNotifier; private readonly ILogger _logger; + private readonly CancellationTokenSource _cancellationTokenSource = new(); private readonly List _operationProgresses = new(); private readonly BehaviorSubject _currentOperationProgress = new(null); @@ -94,6 +95,9 @@ public class CopyCommand : CommandBase, ITransportationCommand return currentTime.WithDifferences(simulateOperation.NewDiffs); } + public override void Cancel() + => _cancellationTokenSource.Cancel(); + public async Task ExecuteAsync(CopyFunc copy) { var currentTime = PointInTime.Present; @@ -163,6 +167,8 @@ public class CopyCommand : CommandBase, ITransportationCommand { foreach (var source in sources) { + if (_cancellationTokenSource.IsCancellationRequested) return; + var resolvedTarget = (IContainer) await target.ResolveAsync() ?? throw new Exception(); var item = await _timelessContentProvider.GetItemByFullNameAsync(source, currentTime); @@ -188,12 +194,13 @@ public class CopyCommand : CommandBase, ITransportationCommand var currentProgress = _operationProgresses.Find(o => o.Key == element.FullName!.Path); _currentOperationProgress.OnNext(currentProgress); - await copyOperation.CopyAsync(new AbsolutePath(_timelessContentProvider, element), newElementPath, new CopyCommandContext(UpdateProgress, currentProgress)); + await copyOperation.CopyAsync(new AbsolutePath(_timelessContentProvider, element), newElementPath, new CopyCommandContext(UpdateProgress, currentProgress, _cancellationTokenSource.Token)); } } } private readonly object _updateProgressLock = new(); + private Task UpdateProgress() { lock (_updateProgressLock) diff --git a/src/Core/FileTime.Core.Command/Copy/CopyCommandContext.cs b/src/Core/FileTime.Core.Command/Copy/CopyCommandContext.cs index 986c008..e575e76 100644 --- a/src/Core/FileTime.Core.Command/Copy/CopyCommandContext.cs +++ b/src/Core/FileTime.Core.Command/Copy/CopyCommandContext.cs @@ -4,13 +4,18 @@ public class CopyCommandContext { private readonly Func _updateProgress; - public CopyCommandContext(Func updateProgress, OperationProgress? currentProgress) + public CopyCommandContext( + Func updateProgress, + OperationProgress? currentProgress, + CancellationToken cancellationToken) { _updateProgress = updateProgress; + CancellationToken = cancellationToken; CurrentProgress = currentProgress; } public OperationProgress? CurrentProgress { get; } + public CancellationToken CancellationToken { get; } public async Task UpdateProgressAsync() => await _updateProgress.Invoke(); } \ No newline at end of file diff --git a/src/Core/FileTime.Core.Command/CreateContainer/CreateContainerCommand.cs b/src/Core/FileTime.Core.Command/CreateContainer/CreateContainerCommand.cs index 89a5afb..0172cd6 100644 --- a/src/Core/FileTime.Core.Command/CreateContainer/CreateContainerCommand.cs +++ b/src/Core/FileTime.Core.Command/CreateContainer/CreateContainerCommand.cs @@ -23,4 +23,8 @@ public class CreateContainerCommand : CreateItemBase await itemCreator.CreateContainerAsync(resolvedParent.Provider, Parent!.GetChild(NewItemName!)); await _commandSchedulerNotifier.RefreshContainer(Parent); } + + public override void Cancel() + { + } } \ No newline at end of file diff --git a/src/Core/FileTime.Core.Command/CreateElement/CreateElementCommand.cs b/src/Core/FileTime.Core.Command/CreateElement/CreateElementCommand.cs index c72c6a8..9ad18f3 100644 --- a/src/Core/FileTime.Core.Command/CreateElement/CreateElementCommand.cs +++ b/src/Core/FileTime.Core.Command/CreateElement/CreateElementCommand.cs @@ -10,7 +10,7 @@ public class CreateElementCommand : CreateItemBase private readonly ICommandSchedulerNotifier _commandSchedulerNotifier; public CreateElementCommand( - ITimelessContentProvider timelessContentProvider, + ITimelessContentProvider timelessContentProvider, IContentAccessorFactory contentAccessorFactory, ICommandSchedulerNotifier commandSchedulerNotifier) : base(timelessContentProvider, contentAccessorFactory) @@ -23,4 +23,8 @@ public class CreateElementCommand : CreateItemBase await itemCreator.CreateElementAsync(resolvedParent.Provider, Parent!.GetChild(NewItemName!)); await _commandSchedulerNotifier.RefreshContainer(Parent); } + + public override void Cancel() + { + } } \ No newline at end of file diff --git a/src/Core/FileTime.Core.Command/Delete/DeleteCommand.cs b/src/Core/FileTime.Core.Command/Delete/DeleteCommand.cs index eafb704..2209d4e 100644 --- a/src/Core/FileTime.Core.Command/Delete/DeleteCommand.cs +++ b/src/Core/FileTime.Core.Command/Delete/DeleteCommand.cs @@ -35,6 +35,11 @@ public class DeleteCommand : CommandBase, IExecutableCommand return Task.FromResult(currentTime); } + public override void Cancel() + { + //TODO: Implement + } + public async Task Execute() { //Calculate diff --git a/src/Core/FileTime.Core.Command/Move/MoveCommand.cs b/src/Core/FileTime.Core.Command/Move/MoveCommand.cs index 6763dab..5a946af 100644 --- a/src/Core/FileTime.Core.Command/Move/MoveCommand.cs +++ b/src/Core/FileTime.Core.Command/Move/MoveCommand.cs @@ -41,6 +41,11 @@ public class MoveCommand : CommandBase, IExecutableCommand return Task.FromResult(currentTime); } + public override void Cancel() + { + //TODO: Implement + } + public async Task Execute() { Calculate(); diff --git a/src/Core/FileTime.Core.CommandHandlers/StreamCopyCommandHandler.cs b/src/Core/FileTime.Core.CommandHandlers/StreamCopyCommandHandler.cs index 1d3bc81..c6cda5f 100644 --- a/src/Core/FileTime.Core.CommandHandlers/StreamCopyCommandHandler.cs +++ b/src/Core/FileTime.Core.CommandHandlers/StreamCopyCommandHandler.cs @@ -50,6 +50,8 @@ public class StreamCopyCommandHandler : ICommandHandler public async Task CopyElement(AbsolutePath sourcePath, AbsolutePath targetPath, CopyCommandContext copyCommandContext) { + if (copyCommandContext.CancellationToken.IsCancellationRequested) return; + var parent = (IContainer?) (await targetPath.GetParent()!.ResolveAsync())!; var elementName = targetPath.Path; var parentChildren = parent.Items.ToList(); @@ -70,11 +72,12 @@ public class StreamCopyCommandHandler : ICommandHandler do { + if (copyCommandContext.CancellationToken.IsCancellationRequested) return; dataRead = await reader.ReadBytesAsync(writer.PreferredBufferSize); if (dataRead.Length > 0) { - await writer.WriteBytesAsync(dataRead); - await writer.FlushAsync(); + await writer.WriteBytesAsync(dataRead, cancellationToken: copyCommandContext.CancellationToken); + await writer.FlushAsync(copyCommandContext.CancellationToken); currentProgress += dataRead.LongLength; if (copyCommandContext.CurrentProgress is not null) { diff --git a/src/GuiApp/Avalonia/FileTime.GuiApp/Assets/material/close-octagon.svg b/src/GuiApp/Avalonia/FileTime.GuiApp/Assets/material/close-octagon.svg new file mode 100644 index 0000000..73f5c47 --- /dev/null +++ b/src/GuiApp/Avalonia/FileTime.GuiApp/Assets/material/close-octagon.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/src/GuiApp/Avalonia/FileTime.GuiApp/Views/MainWindow.axaml b/src/GuiApp/Avalonia/FileTime.GuiApp/Views/MainWindow.axaml index b49a0a0..6956fd3 100644 --- a/src/GuiApp/Avalonia/FileTime.GuiApp/Views/MainWindow.axaml +++ b/src/GuiApp/Avalonia/FileTime.GuiApp/Views/MainWindow.axaml @@ -343,7 +343,18 @@ Padding="5" VerticalAlignment="Top"> - + + + + + 10 * 1024 * 1024; - public async Task WriteBytesAsync(byte[] data, int? index = null) + public async Task WriteBytesAsync(byte[] data, int? index = null, CancellationToken cancellationToken = default) { if (!_isRemoteWriterInitialized) await InitializeRemoteWriter(_nativePath); - await _remoteConnection.WriteBytesAsync(_transactionId, data, index); + await _remoteConnection.WriteBytesAsync(_transactionId, data, index, cancellationToken); } - public async Task FlushAsync() + public async Task FlushAsync(CancellationToken cancellationToken = default) { if (!_isRemoteWriterInitialized) return; - await _remoteConnection.FlushWriterAsync(_transactionId); + await _remoteConnection.FlushWriterAsync(_transactionId, cancellationToken); } private async Task InitializeRemoteWriter(NativePath nativePath) diff --git a/src/Server/FileTime.Server.Common.Abstractions/IRemoteConnection.cs b/src/Server/FileTime.Server.Common.Abstractions/IRemoteConnection.cs index 01c9419..8a62096 100644 --- a/src/Server/FileTime.Server.Common.Abstractions/IRemoteConnection.cs +++ b/src/Server/FileTime.Server.Common.Abstractions/IRemoteConnection.cs @@ -10,7 +10,7 @@ public interface IRemoteConnection Task DeleteItemAsync(string contentProviderId, FullName fullName); Task MoveItemAsync(string contentProviderId, FullName fullName, FullName newPath); Task InitializeRemoteWriter(string contentProviderId, string transactionId, NativePath nativePath); - Task WriteBytesAsync(string transactionId, byte[] data, int? index); - Task FlushWriterAsync(string transactionId); + Task WriteBytesAsync(string transactionId, byte[] data, int? index, CancellationToken cancellationToken = default); + Task FlushWriterAsync(string transactionId, CancellationToken cancellationToken = default); Task CloseWriterAsync(string transactionId); } \ No newline at end of file diff --git a/src/Server/FileTime.Server.Common/Connections/SignalR/ISignalRHub.cs b/src/Server/FileTime.Server.Common/Connections/SignalR/ISignalRHub.cs index a2d5283..2daf5d1 100644 --- a/src/Server/FileTime.Server.Common/Connections/SignalR/ISignalRHub.cs +++ b/src/Server/FileTime.Server.Common/Connections/SignalR/ISignalRHub.cs @@ -7,8 +7,12 @@ public interface ISignalRHub Task CreateElementAsync(string contentProviderId, string fullName); Task DeleteItemAsync(string contentProviderId, string fullName); Task MoveItemAsync(string contentProviderId, string fullName, string newPath); + + //TODO: CancellationToken https://github.com/nenoNaninu/TypedSignalR.Client/issues/120 Task FlushWriterAsync(string transactionId); Task InitializeRemoteWriter(string contentProviderId, string transactionId, string nativePath); + + //TODO: CancellationToken https://github.com/nenoNaninu/TypedSignalR.Client/issues/120 Task WriteBytesAsync(string transactionId, string data, int index); Task CloseWriterAsync(string transactionId); } \ No newline at end of file diff --git a/src/Server/FileTime.Server.Common/Connections/SignalR/SignalRConnection.cs b/src/Server/FileTime.Server.Common/Connections/SignalR/SignalRConnection.cs index 8ec3c94..6f1ef54 100644 --- a/src/Server/FileTime.Server.Common/Connections/SignalR/SignalRConnection.cs +++ b/src/Server/FileTime.Server.Common/Connections/SignalR/SignalRConnection.cs @@ -68,10 +68,10 @@ public class SignalRConnection : IRemoteConnection, IAsyncInitable public async Task MoveItemAsync(string contentProviderId, FullName fullName, FullName newPath) => await _client.MoveItemAsync(contentProviderId, fullName.Path, newPath.Path); - public async Task WriteBytesAsync(string transactionId, byte[] data, int? index) + public async Task WriteBytesAsync(string transactionId, byte[] data, int? index, CancellationToken cancellationToken = default) => await _client.WriteBytesAsync(transactionId, Convert.ToBase64String(data), index ?? -1); - public async Task FlushWriterAsync(string transactionId) + public async Task FlushWriterAsync(string transactionId, CancellationToken cancellationToken = default) => await _client.FlushWriterAsync(transactionId); public async Task InitializeRemoteWriter(string contentProviderId, string transactionId, NativePath nativePath)