diff --git a/src/AppCommon/FileTime.App.Core/Clipboard/Clipboard.cs b/src/AppCommon/FileTime.App.Core/Clipboard/Clipboard.cs index 3542798..c13e00f 100644 --- a/src/AppCommon/FileTime.App.Core/Clipboard/Clipboard.cs +++ b/src/AppCommon/FileTime.App.Core/Clipboard/Clipboard.cs @@ -6,20 +6,19 @@ namespace FileTime.App.Core.Clipboard public class Clipboard : IClipboard { private List _content; - public IReadOnlyList Content { get; } + public IReadOnlyList Content { get; private set; } public Type? CommandType { get; private set; } public Clipboard() { - _content = new List(); - Content = _content.AsReadOnly(); + ResetContent(); } public void AddContent(AbsolutePath absolutePath) { foreach (var content in _content) { - if (content.IsEqual(absolutePath)) return; + if (content.Equals(absolutePath)) return; } _content.Add(new AbsolutePath(absolutePath)); @@ -29,7 +28,7 @@ namespace FileTime.App.Core.Clipboard { for (var i = 0; i < _content.Count; i++) { - if (_content[i].IsEqual(absolutePath)) + if (_content[i].Equals(absolutePath)) { _content.RemoveAt(i--); } @@ -38,7 +37,7 @@ namespace FileTime.App.Core.Clipboard public void Clear() { - _content = new List(); + ResetContent(); CommandType = null; } @@ -46,5 +45,11 @@ namespace FileTime.App.Core.Clipboard { CommandType = typeof(T); } + + private void ResetContent() + { + _content = new List(); + Content = _content.AsReadOnly(); + } } } \ No newline at end of file diff --git a/src/AppCommon/FileTime.App.Core/Tab/TabState.cs b/src/AppCommon/FileTime.App.Core/Tab/TabState.cs index c7056de..c18f062 100644 --- a/src/AppCommon/FileTime.App.Core/Tab/TabState.cs +++ b/src/AppCommon/FileTime.App.Core/Tab/TabState.cs @@ -35,7 +35,7 @@ namespace FileTime.App.Core.Tab foreach (var content in _markedItems[container]) { - if (content.IsEqual(path)) return; + if (content.Equals(path)) return; } var tabItem = new AbsolutePath(path); @@ -50,7 +50,7 @@ namespace FileTime.App.Core.Tab var markedItems = _markedItems[container]; for (var i = 0; i < markedItems.Count; i++) { - if (markedItems[i].IsEqual(path)) + if (markedItems[i].Equals(path)) { await ItemUnmarked.InvokeAsync(this, markedItems[i]); markedItems.RemoveAt(i--); diff --git a/src/AppCommon/FileTime.App.DependencyInjection/DependencyInjection.cs b/src/AppCommon/FileTime.App.DependencyInjection/DependencyInjection.cs index 024e62a..20f4e94 100644 --- a/src/AppCommon/FileTime.App.DependencyInjection/DependencyInjection.cs +++ b/src/AppCommon/FileTime.App.DependencyInjection/DependencyInjection.cs @@ -1,7 +1,6 @@ using FileTime.App.Core.Clipboard; using FileTime.Core.Command; using FileTime.Core.Providers; -using FileTime.Core.StateManagement; using FileTime.Core.Timeline; using FileTime.Providers.Local; using FileTime.Providers.Smb; @@ -21,7 +20,6 @@ namespace FileTime.App.Core .AddSingleton() .AddSingleton(sp => sp.GetService() ?? throw new Exception($"No {nameof(LocalContentProvider)} instance found")) .AddSingleton() - .AddSingleton() .AddSingleton() .AddSingleton(); } diff --git a/src/Core/FileTime.Core/Command/CopyCommand.cs b/src/Core/FileTime.Core/Command/CopyCommand.cs index 61fd2e4..7b0bdd3 100644 --- a/src/Core/FileTime.Core/Command/CopyCommand.cs +++ b/src/Core/FileTime.Core/Command/CopyCommand.cs @@ -1,3 +1,4 @@ +using AsyncEvent; using FileTime.Core.Models; using FileTime.Core.Timeline; @@ -5,9 +6,10 @@ namespace FileTime.Core.Command { public class CopyCommand : ITransportationCommand { - private Action? _copyOperation; + private Func? _copyOperation; + private Dictionary _operationStatuses = new(); private Func>? _createContainer; - private TimeRunner? _timeRunner; + private Func? _containerCopyDone; public IList Sources { get; } = new List(); @@ -15,6 +17,27 @@ namespace FileTime.Core.Command public TransportMode? TransportMode { get; set; } = Command.TransportMode.Merge; + public int Progress { get; private set; } + + public AsyncEventHandler ProgressChanged { get; } = new(); + + public string DisplayLabel { get; } = "Copy"; + + private async Task UpdateProgress() + { + var total = 0; + var current = 0; + + foreach (var item in _operationStatuses.Values) + { + current += item.Progress; + total += item.TotalCount; + } + + Progress = current * 100 / total; + await ProgressChanged.InvokeAsync(this, AsyncEventArgs.Empty); + } + public async Task SimulateCommand(PointInTime startPoint) { if (Sources == null) throw new ArgumentException(nameof(Sources) + " can not be null"); @@ -33,6 +56,8 @@ namespace FileTime.Core.Command DifferenceActionType.Create, to )); + + return Task.CompletedTask; }; _createContainer = async (IContainer target, string name) => @@ -59,13 +84,62 @@ namespace FileTime.Core.Command 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; + await CalculateProgress(); + + _copyOperation = async (from, to) => + { + copy(from, to); + var parentPath = to.GetParentAsAbsolutePath(); + if (_operationStatuses.ContainsKey(parentPath)) + { + _operationStatuses[parentPath].Progress++; + } + await UpdateProgress(); + }; + _createContainer = async (IContainer target, string name) => await target.CreateContainer(name); - _timeRunner = timeRunner; + _containerCopyDone = async (path) => + { + _operationStatuses[path].Progress = _operationStatuses[path].TotalCount; + if (timeRunner != null) + { + await timeRunner.RefreshContainer.InvokeAsync(this, path); + } + }; await DoCopy(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(); + + _copyOperation = (_, to) => + { + var parentPath = to.GetParentAsAbsolutePath(); + OperationProgress operation; + if (operationStatuses.ContainsKey(parentPath)) + { + operation = operationStatuses[parentPath]; + } + else + { + operation = new OperationProgress(); + operationStatuses.Add(parentPath, operation); + } + operation.TotalCount++; + + return Task.CompletedTask; + }; + + await DoCopy(Sources, Target, TransportMode.Value); + _operationStatuses = operationStatuses; + } + private async Task DoCopy( IEnumerable sources, IContainer target, @@ -86,7 +160,7 @@ namespace FileTime.Core.Command var childFiles = (await container.GetElements())!.Select(f => new AbsolutePath(f)); await DoCopy(childDirectories.Concat(childFiles), targetContainer, transportMode); - _timeRunner?.RefreshContainer.InvokeAsync(this, new AbsolutePath(container)); + if (_containerCopyDone != null) await _containerCopyDone.Invoke(new AbsolutePath(container)); } else if (item is IElement element) { diff --git a/src/Core/FileTime.Core/Command/CreateContainerCommand.cs b/src/Core/FileTime.Core/Command/CreateContainerCommand.cs index b847a17..fa845c6 100644 --- a/src/Core/FileTime.Core/Command/CreateContainerCommand.cs +++ b/src/Core/FileTime.Core/Command/CreateContainerCommand.cs @@ -1,3 +1,4 @@ +using AsyncEvent; using FileTime.Core.Models; using FileTime.Core.Timeline; @@ -8,6 +9,11 @@ namespace FileTime.Core.Command public AbsolutePath Container { get; } public string NewContainerName { get; } + public int Progress => 100; + + public AsyncEventHandler ProgressChanged { get; } = new(); + public string DisplayLabel { get; } = "CreateContainer"; + public CreateContainerCommand(AbsolutePath container, string newContainerName) { Container = container; diff --git a/src/Core/FileTime.Core/Command/CreateElementCommand.cs b/src/Core/FileTime.Core/Command/CreateElementCommand.cs index 38e081c..a5c94a3 100644 --- a/src/Core/FileTime.Core/Command/CreateElementCommand.cs +++ b/src/Core/FileTime.Core/Command/CreateElementCommand.cs @@ -1,3 +1,4 @@ +using AsyncEvent; using FileTime.Core.Models; using FileTime.Core.Timeline; @@ -8,6 +9,10 @@ namespace FileTime.Core.Command public AbsolutePath Container { get; } public string NewElementName { get; } + public int Progress => 100; + public AsyncEventHandler ProgressChanged { get; } = new(); + public string DisplayLabel { get; } = "CreateElement"; + public CreateElementCommand(AbsolutePath container, string newElementName) { Container = container; diff --git a/src/Core/FileTime.Core/Command/DeleteCommand.cs b/src/Core/FileTime.Core/Command/DeleteCommand.cs index ec4e943..12c028c 100644 --- a/src/Core/FileTime.Core/Command/DeleteCommand.cs +++ b/src/Core/FileTime.Core/Command/DeleteCommand.cs @@ -1,3 +1,4 @@ +using AsyncEvent; using FileTime.Core.Extensions; using FileTime.Core.Models; using FileTime.Core.Timeline; @@ -6,7 +7,12 @@ namespace FileTime.Core.Command { public class DeleteCommand : IExecutableCommand { + public int Progress => 100; + + public AsyncEventHandler ProgressChanged { get; } = new(); + public IList ItemsToDelete { get; } = new List(); + public string DisplayLabel { get; } = "DeleteCommand"; public async Task SimulateCommand(PointInTime startPoint) { diff --git a/src/Core/FileTime.Core/Command/ICommand.cs b/src/Core/FileTime.Core/Command/ICommand.cs index 0e0fd54..534da50 100644 --- a/src/Core/FileTime.Core/Command/ICommand.cs +++ b/src/Core/FileTime.Core/Command/ICommand.cs @@ -1,10 +1,14 @@ +using AsyncEvent; using FileTime.Core.Timeline; namespace FileTime.Core.Command { public interface ICommand { + string DisplayLabel { get; } Task CanRun(PointInTime startPoint); Task SimulateCommand(PointInTime startPoint); + int Progress { get; } + AsyncEventHandler ProgressChanged { get; } } } \ No newline at end of file diff --git a/src/Core/FileTime.Core/Command/MoveCommand.cs b/src/Core/FileTime.Core/Command/MoveCommand.cs index 0530911..373f932 100644 --- a/src/Core/FileTime.Core/Command/MoveCommand.cs +++ b/src/Core/FileTime.Core/Command/MoveCommand.cs @@ -1,3 +1,4 @@ +using AsyncEvent; using FileTime.Core.Models; using FileTime.Core.Timeline; @@ -10,6 +11,10 @@ namespace FileTime.Core.Command public IContainer? Target { get; set; } public TransportMode? TransportMode { get; set; } = Command.TransportMode.Merge; + public int Progress => 100; + public AsyncEventHandler ProgressChanged { get; } = new(); + public string DisplayLabel { get; } = "MoveCommand"; + public Task CanRun(PointInTime startPoint) { throw new NotImplementedException(); diff --git a/src/Core/FileTime.Core/Command/OperationProgress.cs b/src/Core/FileTime.Core/Command/OperationProgress.cs new file mode 100644 index 0000000..eb54462 --- /dev/null +++ b/src/Core/FileTime.Core/Command/OperationProgress.cs @@ -0,0 +1,8 @@ +namespace FileTime.Core.Command +{ + public class OperationProgress + { + public int Progress { get; set; } + public int TotalCount { get; set; } + } +} \ No newline at end of file diff --git a/src/Core/FileTime.Core/Command/RenameCommand.cs b/src/Core/FileTime.Core/Command/RenameCommand.cs index 28d8aa5..5c240c6 100644 --- a/src/Core/FileTime.Core/Command/RenameCommand.cs +++ b/src/Core/FileTime.Core/Command/RenameCommand.cs @@ -1,3 +1,5 @@ +using AsyncEvent; +using FileTime.Core.Extensions; using FileTime.Core.Models; using FileTime.Core.Timeline; @@ -8,6 +10,10 @@ namespace FileTime.Core.Command public AbsolutePath Source { get; } public string Target { get; } + public int Progress => 100; + public AsyncEventHandler ProgressChanged { get; } = new(); + public string DisplayLabel { get; } = "RenameCommand"; + public RenameCommand(AbsolutePath source, string target) { Source = source; @@ -24,14 +30,25 @@ namespace FileTime.Core.Command } } - public Task SimulateCommand(PointInTime startPoint) + public async Task SimulateCommand(PointInTime startPoint) { - throw new NotImplementedException(); + var item = await Source.Resolve(); + if (item == null) throw new FileNotFoundException(); + var newDifferences = new List() + { + new Difference(item.ToDifferenceItemType(), + DifferenceActionType.Delete, + Source), + new Difference(item.ToDifferenceItemType(), + DifferenceActionType.Delete, + Source) + }; + return startPoint.WithDifferences(newDifferences); } public Task CanRun(PointInTime startPoint) { - throw new NotImplementedException(); + return Task.FromResult(Source.Resolve() != null ? CanCommandRun.True : CanCommandRun.False); } } } \ No newline at end of file diff --git a/src/Core/FileTime.Core/Models/AbsolutePath.cs b/src/Core/FileTime.Core/Models/AbsolutePath.cs index 929eed2..4599ea6 100644 --- a/src/Core/FileTime.Core/Models/AbsolutePath.cs +++ b/src/Core/FileTime.Core/Models/AbsolutePath.cs @@ -3,7 +3,7 @@ using FileTime.Core.Timeline; namespace FileTime.Core.Models { - public sealed class AbsolutePath + public sealed class AbsolutePath : IEquatable { public IContentProvider ContentProvider { get; } public IContentProvider? VirtualContentProvider { get; } @@ -67,12 +67,6 @@ namespace FileTime.Core.Models return new AbsolutePath(contentProvider, path, virtualContentProvider); } - public bool IsEqual(AbsolutePath path) - { - //TODO: sure?? - return path.ContentProvider == ContentProvider && path.Path == Path; - } - public async Task Resolve() { var result = VirtualContentProvider != null && (await VirtualContentProvider.IsExists(Path)) @@ -87,11 +81,36 @@ namespace FileTime.Core.Models public string GetParent() { var pathParts = Path.Split(Constants.SeparatorChar); - return string.Join(Constants.SeparatorChar, pathParts); + return string.Join(Constants.SeparatorChar, pathParts[..^1]); } public AbsolutePath GetParentAsAbsolutePath() => new(ContentProvider, GetParent(), VirtualContentProvider); public string GetName() => Path.Split(Constants.SeparatorChar).Last(); + + public override bool Equals(object? obj) => this.Equals(obj as AbsolutePath); + + public bool Equals(AbsolutePath? other) => + other is not null && other.ContentProvider == ContentProvider && other.Path == Path; + + public override int GetHashCode() => (ContentProvider.Name, Path).GetHashCode(); + + public static bool operator ==(AbsolutePath? lhs, AbsolutePath? rhs) + { + if (lhs is null) + { + if (rhs is null) + { + return true; + } + + // Only the left side is null. + return false; + } + // Equals handles case of null on right side. + return lhs.Equals(rhs); + } + + public static bool operator !=(AbsolutePath? lhs, AbsolutePath? rhs) => !(lhs == rhs); } } \ No newline at end of file diff --git a/src/Core/FileTime.Core/StateManagement/ElementCreationStates.cs b/src/Core/FileTime.Core/StateManagement/ElementCreationStates.cs deleted file mode 100644 index b2b6d4b..0000000 --- a/src/Core/FileTime.Core/StateManagement/ElementCreationStates.cs +++ /dev/null @@ -1,7 +0,0 @@ -namespace FileTime.Core.StateManagement -{ - public class ElementCreationStates - { - - } -} \ No newline at end of file diff --git a/src/Core/FileTime.Core/Timeline/ReadOnlyParallelCommands.cs b/src/Core/FileTime.Core/Timeline/ReadOnlyParallelCommands.cs index 5bd22eb..1d5acdd 100644 --- a/src/Core/FileTime.Core/Timeline/ReadOnlyParallelCommands.cs +++ b/src/Core/FileTime.Core/Timeline/ReadOnlyParallelCommands.cs @@ -3,9 +3,12 @@ namespace FileTime.Core.Timeline public class ReadOnlyParallelCommands { public IReadOnlyList Commands { get; } + public ushort Id { get; } + public ReadOnlyParallelCommands(ParallelCommands parallelCommands) { Commands = parallelCommands.Commands.Select(c => new ReadOnlyCommandTimeState(c)).ToList().AsReadOnly(); + Id = parallelCommands.Id; } } } \ No newline at end of file diff --git a/src/Core/FileTime.Core/Timeline/TimeRunner.cs b/src/Core/FileTime.Core/Timeline/TimeRunner.cs index 9ebde23..99c9dc3 100644 --- a/src/Core/FileTime.Core/Timeline/TimeRunner.cs +++ b/src/Core/FileTime.Core/Timeline/TimeRunner.cs @@ -12,7 +12,7 @@ namespace FileTime.Core.Timeline private bool _resourceIsInUse; private readonly List _commandRunners = new(); - private bool _enableRunning = true; + private bool _enableRunning = false;//true public bool EnableRunning { @@ -98,6 +98,8 @@ namespace FileTime.Core.Timeline private void RunCommands() { + while (_commandsToRun.Count > 0 && _commandsToRun[0].Commands.Count == 0) _commandsToRun.RemoveAt(0); + if (_commandsToRun.Count > 0) { foreach (var command in _commandsToRun[0].Commands) @@ -139,6 +141,10 @@ namespace FileTime.Core.Timeline if (command != null) { _commandsToRun[0].Remove(command); + if (_commandsToRun[0].Commands.Count == 0) + { + _commandsToRun.RemoveAt(0); + } } _commandRunners.Remove(thread); @@ -150,12 +156,8 @@ namespace FileTime.Core.Timeline public async Task Refresh() { - await RunWithLockAsync(async () => - { - await RefreshCommands(PointInTime.CreateEmpty()); - }); + await RunWithLockAsync(async () => await RefreshCommands(PointInTime.CreateEmpty())); await UpdateReadOnlyCommands(); - } private async Task RefreshCommands(PointInTime? fullStartTime = null) @@ -171,10 +173,10 @@ namespace FileTime.Core.Timeline private async Task UpdateReadOnlyCommands() { - await RunWithLockAsync(() => - { - ParallelCommands = _commandsToRun.ConvertAll(c => new ReadOnlyParallelCommands(c)).AsReadOnly(); - }); + var wait = false; + await RunWithLockAsync(() => wait = _commandsToRun.Count == 1); + if (wait) await Task.Delay(100); + await RunWithLockAsync(() => ParallelCommands = _commandsToRun.ConvertAll(c => new ReadOnlyParallelCommands(c)).AsReadOnly()); CommandsChanged?.Invoke(this, EventArgs.Empty); } diff --git a/src/GuiApp/FileTime.Avalonia/FileTime.Avalonia.csproj b/src/GuiApp/FileTime.Avalonia/FileTime.Avalonia.csproj index 0146552..6fc54e2 100644 --- a/src/GuiApp/FileTime.Avalonia/FileTime.Avalonia.csproj +++ b/src/GuiApp/FileTime.Avalonia/FileTime.Avalonia.csproj @@ -7,7 +7,6 @@ - diff --git a/src/GuiApp/FileTime.Avalonia/ViewModels/MainPageViewModel.cs b/src/GuiApp/FileTime.Avalonia/ViewModels/MainPageViewModel.cs index b00c744..4a455d1 100644 --- a/src/GuiApp/FileTime.Avalonia/ViewModels/MainPageViewModel.cs +++ b/src/GuiApp/FileTime.Avalonia/ViewModels/MainPageViewModel.cs @@ -73,7 +73,7 @@ namespace FileTime.Avalonia.ViewModels [Property] private ObservableCollection _popupTexts = new ObservableCollection(); - public IReadOnlyList TimelineCommands => _timeRunner.ParallelCommands; + public ObservableCollection TimelineCommands { get; } = new(); async partial void OnInitialize() { @@ -85,7 +85,7 @@ namespace FileTime.Avalonia.ViewModels inputInterface.InputHandler = ReadInputs; App.ServiceProvider.GetService(); - _timeRunner.CommandsChanged += (o, e) => OnPropertyChanged(nameof(TimelineCommands)); + _timeRunner.CommandsChanged += UpdateParalellCommands; InitCommandBindings(); _keysToSkip.Add(new KeyWithModifiers[] { new KeyWithModifiers(Key.Up) }); @@ -179,6 +179,31 @@ namespace FileTime.Avalonia.ViewModels Places = places; } + private void UpdateParalellCommands(object? sender, EventArgs e) + { + foreach (var parallelCommand in _timeRunner.ParallelCommands) + { + if (!TimelineCommands.Any(c => c.Id == parallelCommand.Id)) + { + TimelineCommands.Add(new ParallelCommandsViewModel(parallelCommand)); + } + } + var itemsToRemove = new List(); + foreach (var parallelCommandVm in TimelineCommands) + { + if (!_timeRunner.ParallelCommands.Any(c => c.Id == parallelCommandVm.Id)) + { + itemsToRemove.Add(parallelCommandVm); + } + } + + for (var i = 0; i < itemsToRemove.Count; i++) + { + itemsToRemove[i].Dispose(); + TimelineCommands.Remove(itemsToRemove[i]); + } + } + private async Task GetContainerForWindowsDrive(DriveInfo drive) { return (await LocalContentProvider.GetRootContainers()).FirstOrDefault(d => d.Name == drive.Name.TrimEnd(Path.DirectorySeparatorChar)); @@ -625,14 +650,10 @@ namespace FileTime.Avalonia.ViewModels [Command] public async void ProcessInputs() { - try + if (_inputHandler != null) { - if (_inputHandler != null) - { - await _inputHandler.Invoke(); - } + await _inputHandler.Invoke(); } - catch { } Inputs = null; _inputHandler = null; @@ -781,7 +802,7 @@ namespace FileTime.Avalonia.ViewModels var selectedItemName = AppState.SelectedTab.SelectedItem?.Item.Name; var currentLocationItems = await AppState.SelectedTab.CurrentLocation.GetItems(); - if(currentLocationItems.FirstOrDefault(i => i.Item.Name.ToLower() == AppState.RapidTravelText.ToLower()) is IItemViewModel matchItem) + if (currentLocationItems.FirstOrDefault(i => i.Item.Name.ToLower() == AppState.RapidTravelText.ToLower()) is IItemViewModel matchItem) { await AppState.SelectedTab.SetCurrentSelectedItem(matchItem.Item); } diff --git a/src/GuiApp/FileTime.Avalonia/ViewModels/ParalellCommandsViewModel.cs b/src/GuiApp/FileTime.Avalonia/ViewModels/ParalellCommandsViewModel.cs new file mode 100644 index 0000000..f78b0d8 --- /dev/null +++ b/src/GuiApp/FileTime.Avalonia/ViewModels/ParalellCommandsViewModel.cs @@ -0,0 +1,43 @@ +using System.Linq; +using System.Collections.Generic; +using System; +using FileTime.Core.Timeline; + +namespace FileTime.Avalonia.ViewModels +{ + public class ParallelCommandsViewModel : IDisposable + { + private bool _disposed; + public IReadOnlyCollection ParallelCommands { get; } + public ushort Id { get; } + + public ParallelCommandsViewModel(ReadOnlyParallelCommands parallelCommands) + { + ParallelCommands = parallelCommands.Commands.Select(c => new ParallelCommandViewModel(c)).ToList().AsReadOnly(); + Id = parallelCommands.Id; + } + + ~ParallelCommandsViewModel() + { + Dispose(false); + } + + public void Dispose() + { + Dispose(true); + GC.SuppressFinalize(this); + } + + private void Dispose(bool disposing) + { + if (!_disposed && disposing) + { + foreach(var commandVm in ParallelCommands) + { + commandVm.Dispose(); + } + } + _disposed = true; + } + } +} diff --git a/src/GuiApp/FileTime.Avalonia/ViewModels/ParallelCommandViewModel.cs b/src/GuiApp/FileTime.Avalonia/ViewModels/ParallelCommandViewModel.cs new file mode 100644 index 0000000..41abb03 --- /dev/null +++ b/src/GuiApp/FileTime.Avalonia/ViewModels/ParallelCommandViewModel.cs @@ -0,0 +1,56 @@ +using System; +using System.Threading.Tasks; +using AsyncEvent; +using FileTime.Core.Command; +using FileTime.Core.Timeline; +using MvvmGen; + +namespace FileTime.Avalonia.ViewModels +{ + [ViewModel] + public partial class ParallelCommandViewModel : IDisposable + { + private bool _disposed; + private readonly ReadOnlyCommandTimeState _commandTimeState; + + [Property] + private int _progress; + + public CanCommandRun CanRun => _commandTimeState.CanRun; + public bool ForceRun => _commandTimeState.ForceRun; + + public string Name => _commandTimeState.Command.DisplayLabel; + + public ParallelCommandViewModel(ReadOnlyCommandTimeState commandTimeState) + { + _commandTimeState = commandTimeState; + _commandTimeState.Command.ProgressChanged.Add(HandleProgressChange); + } + + private Task HandleProgressChange(object? sender, AsyncEventArgs e) + { + Progress = _commandTimeState.Command.Progress; + return Task.CompletedTask; + } + + ~ParallelCommandViewModel() + { + Dispose(false); + } + + public void Dispose() + { + Dispose(true); + GC.SuppressFinalize(this); + } + + private void Dispose(bool disposing) + { + if (!_disposed && disposing) + { + _commandTimeState.Command.ProgressChanged.Remove(HandleProgressChange); + } + _disposed = true; + } + } +} \ No newline at end of file diff --git a/src/GuiApp/FileTime.Avalonia/Views/MainWindow.axaml b/src/GuiApp/FileTime.Avalonia/Views/MainWindow.axaml index bf4ae59..040e1eb 100644 --- a/src/GuiApp/FileTime.Avalonia/Views/MainWindow.axaml +++ b/src/GuiApp/FileTime.Avalonia/Views/MainWindow.axaml @@ -139,13 +139,18 @@ - - - - - - - + + + + + + + + + + + + @@ -290,14 +295,14 @@ - + - + diff --git a/src/Providers/FileTime.Providers.Local/CommandHandlers/CopyCommandHandler.cs b/src/Providers/FileTime.Providers.Local/CommandHandlers/CopyCommandHandler.cs index 08c8f7f..4b78b53 100644 --- a/src/Providers/FileTime.Providers.Local/CommandHandlers/CopyCommandHandler.cs +++ b/src/Providers/FileTime.Providers.Local/CommandHandlers/CopyCommandHandler.cs @@ -1,19 +1,11 @@ using FileTime.Core.Command; using FileTime.Core.Models; -using FileTime.Core.StateManagement; using FileTime.Core.Timeline; namespace FileTime.Providers.Local.CommandHandlers { public class CopyCommandHandler : ICommandHandler { - private readonly ElementCreationStates _elementCreationStates; - - public CopyCommandHandler(ElementCreationStates elementCreationStates) - { - _elementCreationStates = elementCreationStates; - } - public bool CanHandle(object command) { if (command is not CopyCommand copyCommand) return false;