using System.Reactive.Linq; using System.Reactive.Subjects; using FileTime.Core.Enums; using FileTime.Core.Models; using FileTime.Core.Timeline; namespace FileTime.Core.Command.Copy; public class CopyCommand : CommandBase, ITransportationCommand { private readonly ITimelessContentProvider _timelessContentProvider; private readonly ICommandSchedulerNotifier _commandSchedulerNotifier; private readonly List _operationProgresses = new(); private readonly BehaviorSubject _currentOperationProgress = new(null); public IReadOnlyList Sources { get; } public FullName Target { get; } public TransportMode TransportMode { get; } public IObservable CurrentOperationProgress { get; } public CopyCommand( ITimelessContentProvider timelessContentProvider, ICommandSchedulerNotifier commandSchedulerNotifier, IReadOnlyCollection? sources, TransportMode? mode, FullName? targetFullName) : base("Copy - Calculating...") { _timelessContentProvider = timelessContentProvider; _commandSchedulerNotifier = commandSchedulerNotifier; CurrentOperationProgress = _currentOperationProgress.AsObservable(); if (sources is null) throw new ArgumentException(nameof(Sources) + " can not be null"); if (targetFullName is null) throw new ArgumentException(nameof(Target) + " can not be null"); if (mode is null) throw new ArgumentException(nameof(TransportMode) + " can not be null"); Sources = new List(sources).AsReadOnly(); TransportMode = mode.Value; Target = targetFullName; } public override Task CanRun(PointInTime currentTime) { //TODO: return Task.FromResult(CanCommandRun.True); } public override async Task SimulateCommand(PointInTime currentTime) { var simulateOperation = new SimulateStrategy(_timelessContentProvider); var resolvedTarget = await _timelessContentProvider.GetItemByFullNameAsync(Target, currentTime); await TraverseTree( currentTime, Sources, new AbsolutePath(_timelessContentProvider, resolvedTarget), TransportMode, simulateOperation); return currentTime.WithDifferences(simulateOperation.NewDiffs); } public async Task ExecuteAsync(CopyFunc copy) { var currentTime = PointInTime.Present; await CalculateProgressAsync(currentTime); var copyOperation = new CopyStrategy(copy, new CopyStrategyParam(_operationProgresses, _commandSchedulerNotifier.RefreshContainer)); var resolvedTarget = await _timelessContentProvider.GetItemByFullNameAsync(Target, currentTime); await TraverseTree( currentTime, Sources, new AbsolutePath(_timelessContentProvider, resolvedTarget), TransportMode, copyOperation); //await TimeRunner.RefreshContainer.InvokeAsync(this, Target); } private async Task CalculateProgressAsync(PointInTime currentTime) { var calculateOperation = new CalculateStrategy(); var resolvedTarget = await _timelessContentProvider.GetItemByFullNameAsync(Target, currentTime); await TraverseTree( currentTime, Sources, new AbsolutePath(_timelessContentProvider, resolvedTarget), TransportMode, calculateOperation); _operationProgresses.Clear(); _operationProgresses.AddRange(calculateOperation.OperationStatuses); _operationProgresses .Select(op => op.Progress.Select(p => (Progress: p, TotalProgress: op.TotalCount))) .CombineLatest() .Select(data => { var total = data.Sum(d => d.TotalProgress); if (total == 0) return 0; return (int) (data.Sum(d => d.Progress) * 100 / total); }) .Subscribe(SetTotalProgress); if (Sources.Count == 1) { SetDisplayLabel($"Copy - {Sources.First().GetName()}"); } else { _operationProgresses .Select(o => o.IsDone) .CombineLatest() .Subscribe(statuses => { var done = statuses.Count(s => s) + 1; SetDisplayLabel($"Copy - {done} / {statuses.Count}"); }); } } private async Task TraverseTree( PointInTime currentTime, IEnumerable sources, AbsolutePath target, TransportMode transportMode, ICopyStrategy copyOperation) { var resolvedTarget = ((IContainer) await target.ResolveAsync()) ?? throw new Exception(); foreach (var source in sources) { var item = await _timelessContentProvider.GetItemByFullNameAsync(source, currentTime); if (item is IContainer container) { if (resolvedTarget.ItemsCollection.All(i => i.Path.GetName() != item.Name)) { await copyOperation.CreateContainerAsync(resolvedTarget, container.Name, container.PointInTime); } var children = container.ItemsCollection; await TraverseTree(currentTime, children.Select(c => c.Path).ToList(), target.GetChild(item.Name, AbsolutePathType.Container), transportMode, copyOperation); await copyOperation.ContainerCopyDoneAsync(new AbsolutePath(_timelessContentProvider, container)); } else if (item is IElement element) { var newElementName = await Helper.GetNewNameAsync(resolvedTarget, element.Name, transportMode); if (newElementName == null) continue; var newElementPath = target.GetChild(newElementName, AbsolutePathType.Element); 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)); } } } private Task UpdateProgress() => //Not used, progress is reactive in this command //Note: Maybe this should be removed altogether, and every command should use reactive progress Task.CompletedTask; }