CopyCommand detail: speed

This commit is contained in:
2023-07-27 13:56:22 +02:00
parent 9171a3de54
commit bfa971f352
13 changed files with 143 additions and 43 deletions

View File

@@ -5,5 +5,6 @@ public interface ICommandTimeStateViewModel
IObservable<int> TotalProgress { get; } IObservable<int> TotalProgress { get; }
IObservable<int> CurrentProgress { get; } IObservable<int> CurrentProgress { get; }
IObservable<string> DisplayLabel { get; } IObservable<string> DisplayLabel { get; }
IObservable<string> DisplayDetailLabel { get; }
IObservable<bool> IsSelected { get; } IObservable<bool> IsSelected { get; }
} }

View File

@@ -9,12 +9,14 @@ public class CommandTimeStateViewModel : ICommandTimeStateViewModel
public IObservable<int> CurrentProgress { get; } public IObservable<int> CurrentProgress { get; }
public IObservable<string> DisplayLabel { get; } public IObservable<string> DisplayLabel { get; }
public IObservable<string> DisplayDetailLabel { get; }
public IObservable<bool> IsSelected { get; } public IObservable<bool> IsSelected { get; }
public CommandTimeStateViewModel(CommandTimeState commandTimeState) public CommandTimeStateViewModel(CommandTimeState commandTimeState)
{ {
DisplayLabel = commandTimeState.Command.DisplayLabel; DisplayLabel = commandTimeState.Command.DisplayLabel;
DisplayDetailLabel = commandTimeState.Command.DisplayDetailLabel;
TotalProgress = commandTimeState.Command.TotalProgress; TotalProgress = commandTimeState.Command.TotalProgress;
CurrentProgress = commandTimeState.Command.CurrentProgress; CurrentProgress = commandTimeState.Command.CurrentProgress;
//TODO //TODO

View File

@@ -5,6 +5,7 @@ namespace FileTime.Core.Command;
public interface ICommand public interface ICommand
{ {
IObservable<string> DisplayLabel { get; } IObservable<string> DisplayLabel { get; }
IObservable<string> DisplayDetailLabel { get; }
IObservable<int> TotalProgress { get; } IObservable<int> TotalProgress { get; }
IObservable<int> CurrentProgress { get; } IObservable<int> CurrentProgress { get; }

View File

@@ -7,28 +7,33 @@ namespace FileTime.Core.Command;
public abstract class CommandBase : ICommand public abstract class CommandBase : ICommand
{ {
private readonly BehaviorSubject<string> _displayLabel; private readonly BehaviorSubject<string> _displayLabel;
private readonly BehaviorSubject<string> _displayDetailLabel;
private readonly BehaviorSubject<int> _totalProgress; private readonly BehaviorSubject<int> _totalProgress;
private readonly BehaviorSubject<int> _currentProgress; private readonly BehaviorSubject<int> _currentProgress;
public IObservable<string> DisplayLabel { get; } public IObservable<string> DisplayLabel { get; }
public IObservable<string> DisplayDetailLabel { get; }
public IObservable<int> TotalProgress { get; } public IObservable<int> TotalProgress { get; }
public IObservable<int> CurrentProgress { get; } public IObservable<int> 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); _displayLabel = new(displayLabel);
_displayDetailLabel = new(displayDetailLabel);
_totalProgress = new(totalProgress); _totalProgress = new(totalProgress);
_currentProgress = new(currentProgress); _currentProgress = new(currentProgress);
DisplayLabel = _displayLabel.AsObservable(); DisplayLabel = _displayLabel.AsObservable();
DisplayDetailLabel = _displayDetailLabel.AsObservable();
TotalProgress = _totalProgress.AsObservable(); TotalProgress = _totalProgress.AsObservable();
CurrentProgress = _currentProgress.AsObservable(); CurrentProgress = _currentProgress.AsObservable();
} }
public abstract Task<CanCommandRun> CanRun(PointInTime currentTime); public abstract Task<CanCommandRun> CanRun(PointInTime currentTime);
public abstract Task<PointInTime> SimulateCommand(PointInTime currentTime); public abstract Task<PointInTime> 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); protected void SetTotalProgress(int totalProgress) => _totalProgress.OnNext(totalProgress);
@@ -42,7 +47,7 @@ public abstract class CommandBase : ICommand
{ {
var total = data.Sum(d => d.TotalProgress); var total = data.Sum(d => d.TotalProgress);
if (total == 0) return 0; 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); .Subscribe(SetTotalProgress);
} }

View File

@@ -1,8 +1,11 @@
using System.Reactive.Linq; using System.Reactive.Linq;
using System.Reactive.Subjects; using System.Reactive.Subjects;
using ByteSizeLib;
using DeclarativeProperty;
using FileTime.Core.Enums; using FileTime.Core.Enums;
using FileTime.Core.Models; using FileTime.Core.Models;
using FileTime.Core.Timeline; using FileTime.Core.Timeline;
using Microsoft.Extensions.Logging;
namespace FileTime.Core.Command.Copy; namespace FileTime.Core.Command.Copy;
@@ -10,19 +13,25 @@ public class CopyCommand : CommandBase, ITransportationCommand
{ {
private readonly ITimelessContentProvider _timelessContentProvider; private readonly ITimelessContentProvider _timelessContentProvider;
private readonly ICommandSchedulerNotifier _commandSchedulerNotifier; private readonly ICommandSchedulerNotifier _commandSchedulerNotifier;
private readonly ILogger<CopyCommand> _logger;
private readonly List<OperationProgress> _operationProgresses = new(); private readonly List<OperationProgress> _operationProgresses = new();
private readonly BehaviorSubject<OperationProgress?> _currentOperationProgress = new(null); private readonly BehaviorSubject<OperationProgress?> _currentOperationProgress = new(null);
private long _recentTotalSum;
private readonly DeclarativeProperty<long> _recentTotalProcessed = new();
private readonly DeclarativeProperty<DateTime> _recentStartTime = new();
public IReadOnlyList<FullName> Sources { get; } public IReadOnlyList<FullName> Sources { get; }
public FullName Target { get; } public FullName Target { get; }
public TransportMode TransportMode { get; } public TransportMode TransportMode { get; }
public CopyCommand( internal CopyCommand(
ITimelessContentProvider timelessContentProvider, ITimelessContentProvider timelessContentProvider,
ICommandSchedulerNotifier commandSchedulerNotifier, ICommandSchedulerNotifier commandSchedulerNotifier,
ILogger<CopyCommand> logger,
IReadOnlyCollection<FullName>? sources, IReadOnlyCollection<FullName>? sources,
TransportMode? mode, TransportMode? mode,
FullName? targetFullName) FullName? targetFullName)
@@ -30,11 +39,12 @@ public class CopyCommand : CommandBase, ITransportationCommand
{ {
_timelessContentProvider = timelessContentProvider; _timelessContentProvider = timelessContentProvider;
_commandSchedulerNotifier = commandSchedulerNotifier; _commandSchedulerNotifier = commandSchedulerNotifier;
_logger = logger;
_currentOperationProgress _currentOperationProgress
.Select(p => .Select(p =>
{ {
if (p is null) return Observable.Never<int>(); if (p is null) return Observable.Never<int>();
return p.Progress.Select(currentProgress => (int)(currentProgress * 100 / p.TotalCount)); return p.Progress.Select(currentProgress => (int) (currentProgress * 100 / p.TotalCount));
}) })
.Switch() .Switch()
.Subscribe(SetCurrentProgress); .Subscribe(SetCurrentProgress);
@@ -46,6 +56,21 @@ public class CopyCommand : CommandBase, ITransportationCommand
Sources = new List<FullName>(sources).AsReadOnly(); Sources = new List<FullName>(sources).AsReadOnly();
TransportMode = mode.Value; TransportMode = mode.Value;
Target = targetFullName; 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<CanCommandRun> CanRun(PointInTime currentTime) public override Task<CanCommandRun> CanRun(PointInTime currentTime)
@@ -79,13 +104,16 @@ public class CopyCommand : CommandBase, ITransportationCommand
var resolvedTarget = await _timelessContentProvider.GetItemByFullNameAsync(Target, currentTime); var resolvedTarget = await _timelessContentProvider.GetItemByFullNameAsync(Target, currentTime);
_recentTotalSum = 0;
await _recentTotalProcessed.SetValue(0);
await _recentStartTime.SetValue(DateTime.Now);
await TraverseTree( await TraverseTree(
currentTime, currentTime,
Sources, Sources,
new AbsolutePath(_timelessContentProvider, resolvedTarget), new AbsolutePath(_timelessContentProvider, resolvedTarget),
TransportMode, TransportMode,
copyOperation); copyOperation);
//await TimeRunner.RefreshContainer.InvokeAsync(this, Target);
} }
private async Task CalculateProgressAsync(PointInTime currentTime) private async Task CalculateProgressAsync(PointInTime currentTime)
@@ -119,7 +147,7 @@ public class CopyCommand : CommandBase, ITransportationCommand
.Subscribe(statuses => .Subscribe(statuses =>
{ {
var done = statuses.Count(s => s) + 1; 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}"); SetDisplayLabel($"Copy - {done} / {statuses.Count}");
}); });
@@ -135,7 +163,7 @@ public class CopyCommand : CommandBase, ITransportationCommand
{ {
foreach (var source in sources) 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); var item = await _timelessContentProvider.GetItemByFullNameAsync(source, currentTime);
if (item is IContainer container) 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 //Not used, progress is reactive in this command
//Note: Maybe this should be removed altogether, and every command should use reactive progress //Note: Maybe this should be removed altogether, and every command should use reactive progress
Task.CompletedTask; return Task.CompletedTask;
}
} }

View File

@@ -12,5 +12,5 @@ public class CopyCommandContext
public OperationProgress? CurrentProgress { get; } public OperationProgress? CurrentProgress { get; }
public async Task UpdateProgress() => await _updateProgress.Invoke(); public async Task UpdateProgressAsync() => await _updateProgress.Invoke();
} }

View File

@@ -1,5 +1,7 @@
using FileTime.Core.Models; using FileTime.Core.Models;
using FileTime.Core.Timeline; using FileTime.Core.Timeline;
using Microsoft.Extensions.DependencyInjection;
using Microsoft.Extensions.Logging;
namespace FileTime.Core.Command.Copy; namespace FileTime.Core.Command.Copy;
@@ -7,19 +9,29 @@ public class CopyCommandFactory : ITransportationCommandFactory<CopyCommand>
{ {
private readonly ITimelessContentProvider _timelessContentProvider; private readonly ITimelessContentProvider _timelessContentProvider;
private readonly ICommandSchedulerNotifier _commandSchedulerNotifier; private readonly ICommandSchedulerNotifier _commandSchedulerNotifier;
private readonly IServiceProvider _serviceProvider;
public CopyCommandFactory( public CopyCommandFactory(
ITimelessContentProvider timelessContentProvider, ITimelessContentProvider timelessContentProvider,
ICommandSchedulerNotifier commandSchedulerNotifier) ICommandSchedulerNotifier commandSchedulerNotifier,
IServiceProvider serviceProvider)
{ {
_timelessContentProvider = timelessContentProvider; _timelessContentProvider = timelessContentProvider;
_commandSchedulerNotifier = commandSchedulerNotifier; _commandSchedulerNotifier = commandSchedulerNotifier;
_serviceProvider = serviceProvider;
} }
public CopyCommand GenerateCommand( public CopyCommand GenerateCommand(
IReadOnlyCollection<FullName> sources, IReadOnlyCollection<FullName> sources,
TransportMode mode, TransportMode mode,
FullName targetFullName) FullName targetFullName)
=> new(_timelessContentProvider, _commandSchedulerNotifier, sources, mode, targetFullName); => new(
_timelessContentProvider,
_commandSchedulerNotifier,
_serviceProvider.GetRequiredService<ILogger<CopyCommand>>(),
sources,
mode,
targetFullName
);
} }

View File

@@ -18,7 +18,7 @@ public class CopyStrategy : ICopyStrategy
{ {
foreach (var item in _copyStrategyParam.OperationProgresses.FindAll(o => o.Key.StartsWith(containerPath.Path.Path))) 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); await _copyStrategyParam.RefreshContainerAsync(containerPath.Path);
@@ -27,7 +27,11 @@ public class CopyStrategy : ICopyStrategy
public async Task CopyAsync(AbsolutePath from, AbsolutePath to, CopyCommandContext context) public async Task CopyAsync(AbsolutePath from, AbsolutePath to, CopyCommandContext context)
{ {
await _copy(from, to, 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) if (to.Path.GetParent() is { } parent)
await _copyStrategyParam.RefreshContainerAsync(parent); await _copyStrategyParam.RefreshContainerAsync(parent);
} }

View File

@@ -46,6 +46,12 @@ public class DeleteCommand : CommandBase, IExecutableCommand
new Dictionary<string, IItemDeleter>(), new Dictionary<string, IItemDeleter>(),
new DeleteStrategy() new DeleteStrategy()
); );
var parents = ItemsToDelete.Select(i => i.GetParent()).OfType<FullName>().Distinct();
foreach (var parent in parents)
{
await _commandSchedulerNotifier.RefreshContainer(parent);
}
} }
private async Task TraverseTree( private async Task TraverseTree(

View File

@@ -75,7 +75,7 @@ public class MoveCommand : CommandBase, IExecutableCommand
if (itemToMove.Source.GetParent() is { } parent) if (itemToMove.Source.GetParent() is { } parent)
await _commandSchedulerNotifier.RefreshContainer(parent); await _commandSchedulerNotifier.RefreshContainer(parent);
currentOperationProgress.SetProgress(1); await currentOperationProgress.SetProgressAsync(1);
} }
IItemMover GetOrAddItemMover(IContentProvider provider) IItemMover GetOrAddItemMover(IContentProvider provider)

View File

@@ -1,24 +1,26 @@
using System.Reactive.Linq; using DeclarativeProperty;
using System.Reactive.Subjects;
namespace FileTime.Core.Command; namespace FileTime.Core.Command;
public class OperationProgress public class OperationProgress
{ {
private readonly BehaviorSubject<long> _currentProgress = new(0); private readonly DeclarativeProperty<long> _currentProgress = new(0);
public string Key { get; } public string Key { get; }
public IObservable<long> Progress { get; } public IDeclarativeProperty<long> Progress { get; }
public long TotalCount { get; } public long TotalCount { get; }
public IObservable<bool> IsDone { get; } public IDeclarativeProperty<bool> IsDone { get; }
public OperationProgress(string key, long totalCount) public OperationProgress(string key, long totalCount)
{ {
Key = key; Key = key;
TotalCount = totalCount; TotalCount = totalCount;
Progress = _currentProgress.AsObservable(); Progress = _currentProgress;
IsDone = Progress.Select(p => p >= TotalCount); 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);
} }

View File

@@ -76,8 +76,12 @@ public class StreamCopyCommandHandler : ICommandHandler
await writer.WriteBytesAsync(dataRead); await writer.WriteBytesAsync(dataRead);
await writer.FlushAsync(); await writer.FlushAsync();
currentProgress += dataRead.LongLength; currentProgress += dataRead.LongLength;
copyCommandContext.CurrentProgress?.SetProgress(currentProgress); if (copyCommandContext.CurrentProgress is not null)
await copyCommandContext.UpdateProgress(); {
copyCommandContext.CurrentProgress.SetProgressSafe(currentProgress);
}
await copyCommandContext.UpdateProgressAsync();
} }
} while (dataRead.Length > 0); } while (dataRead.Length > 0);
} }

View File

@@ -316,25 +316,43 @@
<Border <Border
Background="{DynamicResource ContainerBackgroundColor}" Background="{DynamicResource ContainerBackgroundColor}"
CornerRadius="10" CornerRadius="10"
Height="50" Height="70"
Margin="0,0,10,0" Margin="0,0,10,0"
Padding="5"> Padding="5"
Width="300">
<ScrollViewer> <ScrollViewer>
<ItemsControl ItemsSource="{Binding Commands.Collection}"> <ItemsControl ItemsSource="{Binding Commands.Collection}">
<ItemsControl.ItemTemplate> <ItemsControl.ItemTemplate>
<DataTemplate> <DataTemplate>
<Border BorderThickness="1" Classes.SelectedTimelineCommand="{Binding IsSelected}"> <Border BorderThickness="1" Classes.SelectedTimelineCommand="{Binding IsSelected}">
<StackPanel> <Grid ColumnDefinitions="*, *" RowDefinitions="Auto, Auto">
<TextBlock Text="{Binding DisplayLabel^}" /> <TextBlock Grid.ColumnSpan="2" Text="{Binding DisplayLabel^}" />
<ProgressBar
Margin="0,5,0,0" <StackPanel
Maximum="100" Grid.Column="0"
Value="{Binding CurrentProgress^}" /> Grid.Row="1"
<ProgressBar Margin="0,5,5,5">
Margin="0,5,0,0" <TextBlock Text="{Binding DisplayDetailLabel^}" TextAlignment="Right" />
Maximum="100"
Value="{Binding TotalProgress^}" /> <ProgressBar
</StackPanel> Margin="0,5,0,0"
Maximum="100"
MinWidth="50"
Value="{Binding CurrentProgress^}" />
</StackPanel>
<StackPanel
Grid.Column="1"
Grid.Row="1"
Margin="5,5,0,5">
<TextBlock Text="{Binding TotalProgress^, StringFormat={}{0}%}" TextAlignment="Right" />
<ProgressBar
Margin="0,5,0,0"
Maximum="100"
MinWidth="50"
Value="{Binding TotalProgress^}" />
</StackPanel>
</Grid>
</Border> </Border>
</DataTemplate> </DataTemplate>
</ItemsControl.ItemTemplate> </ItemsControl.ItemTemplate>