From bfa971f3523573172111e0840be98c7ee316edc2 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=C3=81d=C3=A1m=20Kov=C3=A1cs?= Date: Thu, 27 Jul 2023 13:56:22 +0200 Subject: [PATCH] CopyCommand detail: speed --- .../Timeline/ICommandTimeStateViewModel.cs | 1 + .../Timeline/CommandTimeStateViewModel.cs | 2 + .../Command/ICommand.cs | 1 + src/Core/FileTime.Core.Command/CommandBase.cs | 17 ++++-- .../FileTime.Core.Command/Copy/CopyCommand.cs | 59 ++++++++++++++++--- .../Copy/CopyCommandContext.cs | 2 +- .../Copy/CopyCommandFactory.cs | 18 +++++- .../Copy/CopyStrategy.cs | 8 ++- .../Delete/DeleteCommand.cs | 6 ++ .../FileTime.Core.Command/Move/MoveCommand.cs | 2 +- .../OperationProgress.cs | 18 +++--- .../StreamCopyCommandHandler.cs | 8 ++- .../FileTime.GuiApp/Views/MainWindow.axaml | 44 ++++++++++---- 13 files changed, 143 insertions(+), 43 deletions(-) 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 92f326f..d5f0aec 100644 --- a/src/AppCommon/FileTime.App.Core.Abstraction/ViewModels/Timeline/ICommandTimeStateViewModel.cs +++ b/src/AppCommon/FileTime.App.Core.Abstraction/ViewModels/Timeline/ICommandTimeStateViewModel.cs @@ -5,5 +5,6 @@ public interface ICommandTimeStateViewModel IObservable TotalProgress { get; } IObservable CurrentProgress { get; } IObservable DisplayLabel { get; } + IObservable DisplayDetailLabel { get; } IObservable IsSelected { get; } } \ 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 ce1429f..1bef90d 100644 --- a/src/AppCommon/FileTime.App.Core/ViewModels/Timeline/CommandTimeStateViewModel.cs +++ b/src/AppCommon/FileTime.App.Core/ViewModels/Timeline/CommandTimeStateViewModel.cs @@ -9,12 +9,14 @@ public class CommandTimeStateViewModel : ICommandTimeStateViewModel public IObservable CurrentProgress { get; } public IObservable DisplayLabel { get; } + public IObservable DisplayDetailLabel { get; } public IObservable IsSelected { get; } public CommandTimeStateViewModel(CommandTimeState commandTimeState) { DisplayLabel = commandTimeState.Command.DisplayLabel; + DisplayDetailLabel = commandTimeState.Command.DisplayDetailLabel; TotalProgress = commandTimeState.Command.TotalProgress; CurrentProgress = commandTimeState.Command.CurrentProgress; //TODO diff --git a/src/Core/FileTime.Core.Abstraction/Command/ICommand.cs b/src/Core/FileTime.Core.Abstraction/Command/ICommand.cs index 338dd54..89b1c54 100644 --- a/src/Core/FileTime.Core.Abstraction/Command/ICommand.cs +++ b/src/Core/FileTime.Core.Abstraction/Command/ICommand.cs @@ -5,6 +5,7 @@ namespace FileTime.Core.Command; public interface ICommand { IObservable DisplayLabel { get; } + IObservable DisplayDetailLabel { get; } IObservable TotalProgress { get; } IObservable CurrentProgress { get; } diff --git a/src/Core/FileTime.Core.Command/CommandBase.cs b/src/Core/FileTime.Core.Command/CommandBase.cs index 0ec9622..fd84413 100644 --- a/src/Core/FileTime.Core.Command/CommandBase.cs +++ b/src/Core/FileTime.Core.Command/CommandBase.cs @@ -7,28 +7,33 @@ namespace FileTime.Core.Command; public abstract class CommandBase : ICommand { private readonly BehaviorSubject _displayLabel; + private readonly BehaviorSubject _displayDetailLabel; private readonly BehaviorSubject _totalProgress; private readonly BehaviorSubject _currentProgress; - + public IObservable DisplayLabel { get; } + public IObservable DisplayDetailLabel { get; } public IObservable TotalProgress { get; } public IObservable CurrentProgress { get; } - protected CommandBase(string displayLabel = "", int totalProgress = 0, int currentProgress = 0) + protected CommandBase(string displayLabel = "", string displayDetailLabel = "", int totalProgress = 0, int currentProgress = 0) { _displayLabel = new(displayLabel); + _displayDetailLabel = new(displayDetailLabel); _totalProgress = new(totalProgress); _currentProgress = new(currentProgress); - + DisplayLabel = _displayLabel.AsObservable(); + DisplayDetailLabel = _displayDetailLabel.AsObservable(); TotalProgress = _totalProgress.AsObservable(); CurrentProgress = _currentProgress.AsObservable(); } public abstract Task CanRun(PointInTime currentTime); public abstract Task SimulateCommand(PointInTime currentTime); - - protected void SetDisplayLabel(string displayLabel) => _displayLabel.OnNext(displayLabel); + + protected void SetDisplayLabel(string? displayLabel) => _displayLabel.OnNext(displayLabel ?? string.Empty); + protected void SetDisplayDetailLabel(string? displayLabel) => _displayDetailLabel.OnNext(displayLabel ?? string.Empty); protected void SetTotalProgress(int totalProgress) => _totalProgress.OnNext(totalProgress); @@ -42,7 +47,7 @@ public abstract class CommandBase : ICommand { var total = data.Sum(d => d.TotalProgress); if (total == 0) return 0; - return (int)(data.Sum(d => d.Progress) * 100 / total); + return (int) (data.Sum(d => d.Progress) * 100 / total); }) .Subscribe(SetTotalProgress); } \ No newline at end of file diff --git a/src/Core/FileTime.Core.Command/Copy/CopyCommand.cs b/src/Core/FileTime.Core.Command/Copy/CopyCommand.cs index ad103d2..d869fa9 100644 --- a/src/Core/FileTime.Core.Command/Copy/CopyCommand.cs +++ b/src/Core/FileTime.Core.Command/Copy/CopyCommand.cs @@ -1,8 +1,11 @@ using System.Reactive.Linq; using System.Reactive.Subjects; +using ByteSizeLib; +using DeclarativeProperty; using FileTime.Core.Enums; using FileTime.Core.Models; using FileTime.Core.Timeline; +using Microsoft.Extensions.Logging; namespace FileTime.Core.Command.Copy; @@ -10,19 +13,25 @@ public class CopyCommand : CommandBase, ITransportationCommand { private readonly ITimelessContentProvider _timelessContentProvider; private readonly ICommandSchedulerNotifier _commandSchedulerNotifier; + private readonly ILogger _logger; private readonly List _operationProgresses = new(); private readonly BehaviorSubject _currentOperationProgress = new(null); + private long _recentTotalSum; + private readonly DeclarativeProperty _recentTotalProcessed = new(); + private readonly DeclarativeProperty _recentStartTime = new(); + public IReadOnlyList Sources { get; } public FullName Target { get; } public TransportMode TransportMode { get; } - public CopyCommand( + internal CopyCommand( ITimelessContentProvider timelessContentProvider, ICommandSchedulerNotifier commandSchedulerNotifier, + ILogger logger, IReadOnlyCollection? sources, TransportMode? mode, FullName? targetFullName) @@ -30,11 +39,12 @@ public class CopyCommand : CommandBase, ITransportationCommand { _timelessContentProvider = timelessContentProvider; _commandSchedulerNotifier = commandSchedulerNotifier; + _logger = logger; _currentOperationProgress .Select(p => { if (p is null) return Observable.Never(); - return p.Progress.Select(currentProgress => (int)(currentProgress * 100 / p.TotalCount)); + return p.Progress.Select(currentProgress => (int) (currentProgress * 100 / p.TotalCount)); }) .Switch() .Subscribe(SetCurrentProgress); @@ -46,6 +56,21 @@ public class CopyCommand : CommandBase, ITransportationCommand Sources = new List(sources).AsReadOnly(); TransportMode = mode.Value; Target = targetFullName; + + var recentSpeed = DeclarativePropertyHelpers.CombineLatest( + _recentTotalProcessed, + _recentStartTime, + (total, start) => + { + var elapsed = DateTime.Now - start; + + var size = new ByteSize(total / elapsed.TotalSeconds); + return Task.FromResult(size + "/s"); + }); + + recentSpeed + .Debounce(TimeSpan.FromMilliseconds(500)) + .Subscribe(SetDisplayDetailLabel); } public override Task CanRun(PointInTime currentTime) @@ -79,13 +104,16 @@ public class CopyCommand : CommandBase, ITransportationCommand var resolvedTarget = await _timelessContentProvider.GetItemByFullNameAsync(Target, currentTime); + _recentTotalSum = 0; + await _recentTotalProcessed.SetValue(0); + await _recentStartTime.SetValue(DateTime.Now); + await TraverseTree( currentTime, Sources, new AbsolutePath(_timelessContentProvider, resolvedTarget), TransportMode, copyOperation); - //await TimeRunner.RefreshContainer.InvokeAsync(this, Target); } private async Task CalculateProgressAsync(PointInTime currentTime) @@ -119,7 +147,7 @@ public class CopyCommand : CommandBase, ITransportationCommand .Subscribe(statuses => { var done = statuses.Count(s => s) + 1; - if(done > statuses.Count) done = statuses.Count; + if (done > statuses.Count) done = statuses.Count; SetDisplayLabel($"Copy - {done} / {statuses.Count}"); }); @@ -135,7 +163,7 @@ public class CopyCommand : CommandBase, ITransportationCommand { foreach (var source in sources) { - var resolvedTarget = (IContainer)await target.ResolveAsync() ?? throw new Exception(); + var resolvedTarget = (IContainer) await target.ResolveAsync() ?? throw new Exception(); var item = await _timelessContentProvider.GetItemByFullNameAsync(source, currentTime); if (item is IContainer container) @@ -165,8 +193,25 @@ public class CopyCommand : CommandBase, ITransportationCommand } } - private Task UpdateProgress() => + private readonly object _updateProgressLock = new(); + private Task UpdateProgress() + { + lock (_updateProgressLock) + { + var now = DateTime.Now; + var delta = now - _recentStartTime.Value; + if (delta.TotalSeconds > 5) + { + _recentTotalSum += _recentTotalProcessed.Value; + _recentStartTime.SetValueSafe(now); + } + + var totalProcessedBytes = _operationProgresses.Select(o => o.Progress.Value).Sum(); + _recentTotalProcessed.SetValueSafe(totalProcessedBytes - _recentTotalSum); + } + //Not used, progress is reactive in this command //Note: Maybe this should be removed altogether, and every command should use reactive progress - Task.CompletedTask; + return Task.CompletedTask; + } } \ No newline at end of file diff --git a/src/Core/FileTime.Core.Command/Copy/CopyCommandContext.cs b/src/Core/FileTime.Core.Command/Copy/CopyCommandContext.cs index a88ca4d..986c008 100644 --- a/src/Core/FileTime.Core.Command/Copy/CopyCommandContext.cs +++ b/src/Core/FileTime.Core.Command/Copy/CopyCommandContext.cs @@ -12,5 +12,5 @@ public class CopyCommandContext public OperationProgress? CurrentProgress { get; } - public async Task UpdateProgress() => await _updateProgress.Invoke(); + public async Task UpdateProgressAsync() => await _updateProgress.Invoke(); } \ No newline at end of file diff --git a/src/Core/FileTime.Core.Command/Copy/CopyCommandFactory.cs b/src/Core/FileTime.Core.Command/Copy/CopyCommandFactory.cs index cd0bda8..1f180c5 100644 --- a/src/Core/FileTime.Core.Command/Copy/CopyCommandFactory.cs +++ b/src/Core/FileTime.Core.Command/Copy/CopyCommandFactory.cs @@ -1,5 +1,7 @@ using FileTime.Core.Models; using FileTime.Core.Timeline; +using Microsoft.Extensions.DependencyInjection; +using Microsoft.Extensions.Logging; namespace FileTime.Core.Command.Copy; @@ -7,19 +9,29 @@ public class CopyCommandFactory : ITransportationCommandFactory { private readonly ITimelessContentProvider _timelessContentProvider; private readonly ICommandSchedulerNotifier _commandSchedulerNotifier; + private readonly IServiceProvider _serviceProvider; public CopyCommandFactory( ITimelessContentProvider timelessContentProvider, - ICommandSchedulerNotifier commandSchedulerNotifier) + ICommandSchedulerNotifier commandSchedulerNotifier, + IServiceProvider serviceProvider) { _timelessContentProvider = timelessContentProvider; _commandSchedulerNotifier = commandSchedulerNotifier; + _serviceProvider = serviceProvider; } public CopyCommand GenerateCommand( IReadOnlyCollection sources, TransportMode mode, - FullName targetFullName) - => new(_timelessContentProvider, _commandSchedulerNotifier, sources, mode, targetFullName); + FullName targetFullName) + => new( + _timelessContentProvider, + _commandSchedulerNotifier, + _serviceProvider.GetRequiredService>(), + sources, + mode, + targetFullName + ); } \ No newline at end of file diff --git a/src/Core/FileTime.Core.Command/Copy/CopyStrategy.cs b/src/Core/FileTime.Core.Command/Copy/CopyStrategy.cs index 101b2a4..3c99c6f 100644 --- a/src/Core/FileTime.Core.Command/Copy/CopyStrategy.cs +++ b/src/Core/FileTime.Core.Command/Copy/CopyStrategy.cs @@ -18,7 +18,7 @@ public class CopyStrategy : ICopyStrategy { foreach (var item in _copyStrategyParam.OperationProgresses.FindAll(o => o.Key.StartsWith(containerPath.Path.Path))) { - item.SetProgress(item.TotalCount); + await item.SetProgressAsync(item.TotalCount); } await _copyStrategyParam.RefreshContainerAsync(containerPath.Path); @@ -27,7 +27,11 @@ public class CopyStrategy : ICopyStrategy public async Task CopyAsync(AbsolutePath from, AbsolutePath to, CopyCommandContext context) { await _copy(from, to, context); - context.CurrentProgress?.SetProgress(context.CurrentProgress.TotalCount); + if (context.CurrentProgress is not null) + { + await context.CurrentProgress.SetProgressAsync(context.CurrentProgress.TotalCount); + } + if (to.Path.GetParent() is { } parent) await _copyStrategyParam.RefreshContainerAsync(parent); } diff --git a/src/Core/FileTime.Core.Command/Delete/DeleteCommand.cs b/src/Core/FileTime.Core.Command/Delete/DeleteCommand.cs index 3f98915..eafb704 100644 --- a/src/Core/FileTime.Core.Command/Delete/DeleteCommand.cs +++ b/src/Core/FileTime.Core.Command/Delete/DeleteCommand.cs @@ -46,6 +46,12 @@ public class DeleteCommand : CommandBase, IExecutableCommand new Dictionary(), new DeleteStrategy() ); + + var parents = ItemsToDelete.Select(i => i.GetParent()).OfType().Distinct(); + foreach (var parent in parents) + { + await _commandSchedulerNotifier.RefreshContainer(parent); + } } private async Task TraverseTree( diff --git a/src/Core/FileTime.Core.Command/Move/MoveCommand.cs b/src/Core/FileTime.Core.Command/Move/MoveCommand.cs index f67d8a3..6763dab 100644 --- a/src/Core/FileTime.Core.Command/Move/MoveCommand.cs +++ b/src/Core/FileTime.Core.Command/Move/MoveCommand.cs @@ -75,7 +75,7 @@ public class MoveCommand : CommandBase, IExecutableCommand if (itemToMove.Source.GetParent() is { } parent) await _commandSchedulerNotifier.RefreshContainer(parent); - currentOperationProgress.SetProgress(1); + await currentOperationProgress.SetProgressAsync(1); } IItemMover GetOrAddItemMover(IContentProvider provider) diff --git a/src/Core/FileTime.Core.Command/OperationProgress.cs b/src/Core/FileTime.Core.Command/OperationProgress.cs index 48a73bb..1dbdb1c 100644 --- a/src/Core/FileTime.Core.Command/OperationProgress.cs +++ b/src/Core/FileTime.Core.Command/OperationProgress.cs @@ -1,24 +1,26 @@ -using System.Reactive.Linq; -using System.Reactive.Subjects; +using DeclarativeProperty; namespace FileTime.Core.Command; public class OperationProgress { - private readonly BehaviorSubject _currentProgress = new(0); + private readonly DeclarativeProperty _currentProgress = new(0); public string Key { get; } - public IObservable Progress { get; } + public IDeclarativeProperty Progress { get; } public long TotalCount { get; } - public IObservable IsDone { get; } + public IDeclarativeProperty IsDone { get; } public OperationProgress(string key, long totalCount) { Key = key; TotalCount = totalCount; - Progress = _currentProgress.AsObservable(); - IsDone = Progress.Select(p => p >= TotalCount); + Progress = _currentProgress; + IsDone = Progress.Map(p => p >= TotalCount); } - public void SetProgress(long progress) => _currentProgress.OnNext(progress); + public async Task SetProgressAsync(long progress) => await _currentProgress.SetValue(progress); + + public void SetProgressSafe(long progress) => + _currentProgress.SetValueSafe(progress); } \ No newline at end of file diff --git a/src/Core/FileTime.Core.CommandHandlers/StreamCopyCommandHandler.cs b/src/Core/FileTime.Core.CommandHandlers/StreamCopyCommandHandler.cs index 1630ee4..1d3bc81 100644 --- a/src/Core/FileTime.Core.CommandHandlers/StreamCopyCommandHandler.cs +++ b/src/Core/FileTime.Core.CommandHandlers/StreamCopyCommandHandler.cs @@ -76,8 +76,12 @@ public class StreamCopyCommandHandler : ICommandHandler await writer.WriteBytesAsync(dataRead); await writer.FlushAsync(); currentProgress += dataRead.LongLength; - copyCommandContext.CurrentProgress?.SetProgress(currentProgress); - await copyCommandContext.UpdateProgress(); + if (copyCommandContext.CurrentProgress is not null) + { + copyCommandContext.CurrentProgress.SetProgressSafe(currentProgress); + } + + await copyCommandContext.UpdateProgressAsync(); } } while (dataRead.Length > 0); } diff --git a/src/GuiApp/Avalonia/FileTime.GuiApp/Views/MainWindow.axaml b/src/GuiApp/Avalonia/FileTime.GuiApp/Views/MainWindow.axaml index 403be96..0b34403 100644 --- a/src/GuiApp/Avalonia/FileTime.GuiApp/Views/MainWindow.axaml +++ b/src/GuiApp/Avalonia/FileTime.GuiApp/Views/MainWindow.axaml @@ -316,25 +316,43 @@ + Padding="5" + Width="300"> - - - - - + + + + + + + + + + + + + +