CopyCommand detail: speed
This commit is contained in:
@@ -5,5 +5,6 @@ public interface ICommandTimeStateViewModel
|
||||
IObservable<int> TotalProgress { get; }
|
||||
IObservable<int> CurrentProgress { get; }
|
||||
IObservable<string> DisplayLabel { get; }
|
||||
IObservable<string> DisplayDetailLabel { get; }
|
||||
IObservable<bool> IsSelected { get; }
|
||||
}
|
||||
@@ -9,12 +9,14 @@ public class CommandTimeStateViewModel : ICommandTimeStateViewModel
|
||||
public IObservable<int> CurrentProgress { get; }
|
||||
|
||||
public IObservable<string> DisplayLabel { get; }
|
||||
public IObservable<string> DisplayDetailLabel { get; }
|
||||
|
||||
public IObservable<bool> IsSelected { get; }
|
||||
|
||||
public CommandTimeStateViewModel(CommandTimeState commandTimeState)
|
||||
{
|
||||
DisplayLabel = commandTimeState.Command.DisplayLabel;
|
||||
DisplayDetailLabel = commandTimeState.Command.DisplayDetailLabel;
|
||||
TotalProgress = commandTimeState.Command.TotalProgress;
|
||||
CurrentProgress = commandTimeState.Command.CurrentProgress;
|
||||
//TODO
|
||||
|
||||
@@ -5,6 +5,7 @@ namespace FileTime.Core.Command;
|
||||
public interface ICommand
|
||||
{
|
||||
IObservable<string> DisplayLabel { get; }
|
||||
IObservable<string> DisplayDetailLabel { get; }
|
||||
IObservable<int> TotalProgress { get; }
|
||||
IObservable<int> CurrentProgress { get; }
|
||||
|
||||
|
||||
@@ -7,20 +7,24 @@ namespace FileTime.Core.Command;
|
||||
public abstract class CommandBase : ICommand
|
||||
{
|
||||
private readonly BehaviorSubject<string> _displayLabel;
|
||||
private readonly BehaviorSubject<string> _displayDetailLabel;
|
||||
private readonly BehaviorSubject<int> _totalProgress;
|
||||
private readonly BehaviorSubject<int> _currentProgress;
|
||||
|
||||
public IObservable<string> DisplayLabel { get; }
|
||||
public IObservable<string> DisplayDetailLabel { get; }
|
||||
public IObservable<int> TotalProgress { 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);
|
||||
_displayDetailLabel = new(displayDetailLabel);
|
||||
_totalProgress = new(totalProgress);
|
||||
_currentProgress = new(currentProgress);
|
||||
|
||||
DisplayLabel = _displayLabel.AsObservable();
|
||||
DisplayDetailLabel = _displayDetailLabel.AsObservable();
|
||||
TotalProgress = _totalProgress.AsObservable();
|
||||
CurrentProgress = _currentProgress.AsObservable();
|
||||
}
|
||||
@@ -28,7 +32,8 @@ public abstract class CommandBase : ICommand
|
||||
public abstract Task<CanCommandRun> CanRun(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);
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
@@ -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<CopyCommand> _logger;
|
||||
|
||||
private readonly List<OperationProgress> _operationProgresses = new();
|
||||
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 FullName Target { get; }
|
||||
|
||||
public TransportMode TransportMode { get; }
|
||||
|
||||
public CopyCommand(
|
||||
internal CopyCommand(
|
||||
ITimelessContentProvider timelessContentProvider,
|
||||
ICommandSchedulerNotifier commandSchedulerNotifier,
|
||||
ILogger<CopyCommand> logger,
|
||||
IReadOnlyCollection<FullName>? 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<int>();
|
||||
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<FullName>(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<CanCommandRun> 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;
|
||||
}
|
||||
}
|
||||
@@ -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();
|
||||
}
|
||||
@@ -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<CopyCommand>
|
||||
{
|
||||
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<FullName> sources,
|
||||
TransportMode mode,
|
||||
FullName targetFullName)
|
||||
=> new(_timelessContentProvider, _commandSchedulerNotifier, sources, mode, targetFullName);
|
||||
=> new(
|
||||
_timelessContentProvider,
|
||||
_commandSchedulerNotifier,
|
||||
_serviceProvider.GetRequiredService<ILogger<CopyCommand>>(),
|
||||
sources,
|
||||
mode,
|
||||
targetFullName
|
||||
);
|
||||
}
|
||||
@@ -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);
|
||||
}
|
||||
|
||||
@@ -46,6 +46,12 @@ public class DeleteCommand : CommandBase, IExecutableCommand
|
||||
new Dictionary<string, IItemDeleter>(),
|
||||
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(
|
||||
|
||||
@@ -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)
|
||||
|
||||
@@ -1,24 +1,26 @@
|
||||
using System.Reactive.Linq;
|
||||
using System.Reactive.Subjects;
|
||||
using DeclarativeProperty;
|
||||
|
||||
namespace FileTime.Core.Command;
|
||||
|
||||
public class OperationProgress
|
||||
{
|
||||
private readonly BehaviorSubject<long> _currentProgress = new(0);
|
||||
private readonly DeclarativeProperty<long> _currentProgress = new(0);
|
||||
public string Key { get; }
|
||||
public IObservable<long> Progress { get; }
|
||||
public IDeclarativeProperty<long> Progress { get; }
|
||||
public long TotalCount { get; }
|
||||
public IObservable<bool> IsDone { get; }
|
||||
public IDeclarativeProperty<bool> 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);
|
||||
}
|
||||
@@ -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);
|
||||
}
|
||||
|
||||
@@ -316,25 +316,43 @@
|
||||
<Border
|
||||
Background="{DynamicResource ContainerBackgroundColor}"
|
||||
CornerRadius="10"
|
||||
Height="50"
|
||||
Height="70"
|
||||
Margin="0,0,10,0"
|
||||
Padding="5">
|
||||
Padding="5"
|
||||
Width="300">
|
||||
<ScrollViewer>
|
||||
<ItemsControl ItemsSource="{Binding Commands.Collection}">
|
||||
<ItemsControl.ItemTemplate>
|
||||
<DataTemplate>
|
||||
<Border BorderThickness="1" Classes.SelectedTimelineCommand="{Binding IsSelected}">
|
||||
<StackPanel>
|
||||
<TextBlock Text="{Binding DisplayLabel^}" />
|
||||
<Grid ColumnDefinitions="*, *" RowDefinitions="Auto, Auto">
|
||||
<TextBlock Grid.ColumnSpan="2" Text="{Binding DisplayLabel^}" />
|
||||
|
||||
<StackPanel
|
||||
Grid.Column="0"
|
||||
Grid.Row="1"
|
||||
Margin="0,5,5,5">
|
||||
<TextBlock Text="{Binding DisplayDetailLabel^}" TextAlignment="Right" />
|
||||
|
||||
<ProgressBar
|
||||
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>
|
||||
</DataTemplate>
|
||||
</ItemsControl.ItemTemplate>
|
||||
|
||||
Reference in New Issue
Block a user