diff --git a/src/AppCommon/FileTime.App.Core.Abstraction/Models/FileExtension.cs b/src/AppCommon/FileTime.App.Core.Abstraction/Models/FileExtension.cs deleted file mode 100644 index f081c5a..0000000 --- a/src/AppCommon/FileTime.App.Core.Abstraction/Models/FileExtension.cs +++ /dev/null @@ -1,3 +0,0 @@ -namespace FileTime.App.Core.Models; - -public record FileExtension(long? Size); \ No newline at end of file diff --git a/src/Core/FileTime.Core.Abstraction/Models/AbsolutePath.cs b/src/Core/FileTime.Core.Abstraction/Models/AbsolutePath.cs index 262d473..1448bfc 100644 --- a/src/Core/FileTime.Core.Abstraction/Models/AbsolutePath.cs +++ b/src/Core/FileTime.Core.Abstraction/Models/AbsolutePath.cs @@ -50,4 +50,7 @@ public class AbsolutePath return null; } } + + public AbsolutePath GetChild(string childName, AbsolutePathType type) + => new (TimelessProvider, PointInTime, Path.GetChild(childName), type); } \ No newline at end of file diff --git a/src/Core/FileTime.Core.Abstraction/Models/Extensions/FileExtension.cs b/src/Core/FileTime.Core.Abstraction/Models/Extensions/FileExtension.cs new file mode 100644 index 0000000..912492f --- /dev/null +++ b/src/Core/FileTime.Core.Abstraction/Models/Extensions/FileExtension.cs @@ -0,0 +1,3 @@ +namespace FileTime.Core.Models.Extensions; + +public record FileExtension(long? Size); \ No newline at end of file diff --git a/src/Core/FileTime.Core.Command/Copy/CalculateStrategy.cs b/src/Core/FileTime.Core.Command/Copy/CalculateStrategy.cs new file mode 100644 index 0000000..de507f5 --- /dev/null +++ b/src/Core/FileTime.Core.Command/Copy/CalculateStrategy.cs @@ -0,0 +1,27 @@ +using FileTime.Core.Models; +using FileTime.Core.Models.Extensions; +using FileTime.Core.Timeline; + +namespace FileTime.Core.Command.Copy; + +public class CalculateStrategy : ICopyStrategy +{ + private readonly List _operationStatuses; + public IReadOnlyList OperationStatuses { get; } + + public CalculateStrategy() + { + _operationStatuses = new(); + OperationStatuses = _operationStatuses.AsReadOnly(); + } + + public Task ContainerCopyDoneAsync(AbsolutePath path) => Task.CompletedTask; + + public async Task CopyAsync(AbsolutePath from, AbsolutePath to, CopyCommandContext context) + { + var resolvedFrom = await from.ResolveAsync(); + _operationStatuses.Add(new OperationProgress(from.Path.Path, (resolvedFrom as IElement)?.GetExtension()?.Size ?? 0L)); + } + + public Task CreateContainerAsync(IContainer target, string name, PointInTime currentTime) => Task.CompletedTask; +} \ 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 e838cd1..babb811 100644 --- a/src/Core/FileTime.Core.Command/Copy/CopyCommand.cs +++ b/src/Core/FileTime.Core.Command/Copy/CopyCommand.cs @@ -1,16 +1,140 @@ +using FileTime.Core.Enums; +using FileTime.Core.Extensions; +using FileTime.Core.Models; using FileTime.Core.Timeline; namespace FileTime.Core.Command.Copy; public class CopyCommand : ITransportationCommand { - public Task CanRun(PointInTime currentTime) + private readonly ITimelessContentProvider _timelessContentProvider; + + private readonly List _operationProgresses = new(); + + public IList Sources { get; } = new List(); + + public FullName? Target { get; set; } + + public TransportMode? TransportMode { get; set; } = Command.TransportMode.Merge; + public OperationProgress? CurrentOperationProgress { get; private set; } + + public CopyCommand(ITimelessContentProvider timelessContentProvider) { - throw new NotImplementedException(); + _timelessContentProvider = timelessContentProvider; } - public Task SimulateCommand(PointInTime currentTime) + public Task CanRun(PointInTime currentTime) { - throw new NotImplementedException(); + //TODO: + return Task.FromResult(CanCommandRun.True); + } + + public async Task SimulateCommand(PointInTime currentTime) + { + 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 simulateOperation = new SimulateStrategy(_timelessContentProvider); + var resolvedTarget = await _timelessContentProvider.GetItemByFullNameAsync(Target, currentTime); + + await TraverseTree( + currentTime, + Sources, + new AbsolutePath(_timelessContentProvider, resolvedTarget), + TransportMode.Value, + simulateOperation); + + return currentTime.WithDifferences(simulateOperation.NewDiffs); + } + + public async Task ExecuteAsync(CopyFunc copy) + { + 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 currentTime = PointInTime.Present; + + await CalculateProgressAsync(currentTime); + + var copyOperation = new CopyStrategy(copy, new CopyStrategyParam(_operationProgresses)); + + var resolvedTarget = await _timelessContentProvider.GetItemByFullNameAsync(Target, currentTime); + + await TraverseTree( + currentTime, + Sources, + new AbsolutePath(_timelessContentProvider, resolvedTarget), + TransportMode.Value, + copyOperation); + //await TimeRunner.RefreshContainer.InvokeAsync(this, Target); + } + + private async Task CalculateProgressAsync(PointInTime currentTime) + { + 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 calculateOperation = new CalculateStrategy(); + var resolvedTarget = await _timelessContentProvider.GetItemByFullNameAsync(Target, currentTime); + + await TraverseTree( + currentTime, + Sources, + new AbsolutePath(_timelessContentProvider, resolvedTarget), + TransportMode.Value, + calculateOperation); + + _operationProgresses.Clear(); + _operationProgresses.AddRange(calculateOperation.OperationStatuses); + } + + private async Task TraverseTree( + PointInTime curretnTime, + 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, curretnTime); + + if (item is IContainer container) + { + if (!((await resolvedTarget.Items.GetItemsAsync())?.Any(i => i.Path.GetName() == item.Name) ?? false)) + { + await copyOperation.CreateContainerAsync(resolvedTarget, container.Name, container.PointInTime); + } + + var children = await container.Items.GetItemsAsync(); + if (children is null) continue; + + await TraverseTree(curretnTime, 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 = currentProgress; + + await copyOperation.CopyAsync(new AbsolutePath(_timelessContentProvider, element), newElementPath, new CopyCommandContext(UpdateProgress, currentProgress)); + } + } + } + + private Task UpdateProgress() + { + //TODO + 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 new file mode 100644 index 0000000..a88ca4d --- /dev/null +++ b/src/Core/FileTime.Core.Command/Copy/CopyCommandContext.cs @@ -0,0 +1,16 @@ +namespace FileTime.Core.Command.Copy; + +public class CopyCommandContext +{ + private readonly Func _updateProgress; + + public CopyCommandContext(Func updateProgress, OperationProgress? currentProgress) + { + _updateProgress = updateProgress; + CurrentProgress = currentProgress; + } + + public OperationProgress? CurrentProgress { get; } + + public async Task UpdateProgress() => await _updateProgress.Invoke(); +} \ No newline at end of file diff --git a/src/Core/FileTime.Core.Command/Copy/CopyFunc.cs b/src/Core/FileTime.Core.Command/Copy/CopyFunc.cs new file mode 100644 index 0000000..15f0482 --- /dev/null +++ b/src/Core/FileTime.Core.Command/Copy/CopyFunc.cs @@ -0,0 +1,5 @@ +using FileTime.Core.Models; + +namespace FileTime.Core.Command.Copy; + +public delegate Task CopyFunc(AbsolutePath from, AbsolutePath to, CopyCommandContext context); \ 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 new file mode 100644 index 0000000..7f3efa9 --- /dev/null +++ b/src/Core/FileTime.Core.Command/Copy/CopyStrategy.cs @@ -0,0 +1,37 @@ +using FileTime.Core.Models; +using FileTime.Core.Timeline; + +namespace FileTime.Core.Command.Copy; + +public class CopyStrategy : ICopyStrategy +{ + private readonly CopyFunc _copy; + private readonly CopyStrategyParam _copyStrategyParam; + + public CopyStrategy(CopyFunc copy, CopyStrategyParam copyStrategyParam) + { + _copy = copy; + _copyStrategyParam = copyStrategyParam; + } + + public async Task ContainerCopyDoneAsync(AbsolutePath containerPath) + { + foreach (var item in _copyStrategyParam.OperationProgresses.FindAll(o => o.Key.StartsWith(containerPath.Path.Path))) + { + item.SetProgress(item.TotalCount); + } + + await _copyStrategyParam.RefreshContainerAsync(containerPath.Path); + } + + public async Task CopyAsync(AbsolutePath from, AbsolutePath to, CopyCommandContext context) + { + await _copy(from, to, context); + context.CurrentProgress?.SetProgress(context.CurrentProgress.TotalCount); + } + + public Task CreateContainerAsync(IContainer target, string name, PointInTime currentTime) + { + throw new NotImplementedException(); + } +} \ No newline at end of file diff --git a/src/Core/FileTime.Core.Command/Copy/CopyStrategyParam.cs b/src/Core/FileTime.Core.Command/Copy/CopyStrategyParam.cs new file mode 100644 index 0000000..cfbaf21 --- /dev/null +++ b/src/Core/FileTime.Core.Command/Copy/CopyStrategyParam.cs @@ -0,0 +1,17 @@ +using FileTime.Core.Models; + +namespace FileTime.Core.Command.Copy; + +public class CopyStrategyParam +{ + private readonly Func _refreshContainer; + public List OperationProgresses { get; } + + public CopyStrategyParam(List operationProgresses, Func refreshContainer) + { + OperationProgresses = operationProgresses; + _refreshContainer = refreshContainer; + } + + public async Task RefreshContainerAsync(FullName containerName) => await _refreshContainer(containerName); +} \ No newline at end of file diff --git a/src/Core/FileTime.Core.Command/Copy/ICopyStrategy.cs b/src/Core/FileTime.Core.Command/Copy/ICopyStrategy.cs new file mode 100644 index 0000000..55e81c7 --- /dev/null +++ b/src/Core/FileTime.Core.Command/Copy/ICopyStrategy.cs @@ -0,0 +1,11 @@ +using FileTime.Core.Models; +using FileTime.Core.Timeline; + +namespace FileTime.Core.Command.Copy; + +public interface ICopyStrategy +{ + Task CreateContainerAsync(IContainer target, string name, PointInTime currentTime); + Task ContainerCopyDoneAsync(AbsolutePath containerPath); + Task CopyAsync(AbsolutePath from, AbsolutePath to, CopyCommandContext context); +} \ No newline at end of file diff --git a/src/Core/FileTime.Core.Command/Copy/SimulateStrategy.cs b/src/Core/FileTime.Core.Command/Copy/SimulateStrategy.cs new file mode 100644 index 0000000..920e3ef --- /dev/null +++ b/src/Core/FileTime.Core.Command/Copy/SimulateStrategy.cs @@ -0,0 +1,39 @@ +using FileTime.Core.Models; +using FileTime.Core.Timeline; + +namespace FileTime.Core.Command.Copy; + +public class SimulateStrategy : ICopyStrategy +{ + private readonly List _newDiffs; + private readonly ITimelessContentProvider _timelessContentProvider; + + public IReadOnlyList NewDiffs { get; } + + public SimulateStrategy(ITimelessContentProvider timelessContentProvider) + { + _timelessContentProvider = timelessContentProvider; + _newDiffs = new(); + NewDiffs = _newDiffs.AsReadOnly(); + } + + public Task CreateContainerAsync(IContainer target, string name, PointInTime currentTime) + { + var newContainerDiff = new Difference( + DifferenceActionType.Create, + new AbsolutePath(_timelessContentProvider, currentTime, target.FullName!.GetChild(name), Enums.AbsolutePathType.Container) + ); + + _newDiffs.Add(newContainerDiff); + + return Task.FromResult((IContainer)null!); + } + + public Task ContainerCopyDoneAsync(AbsolutePath path) => Task.CompletedTask; + + public Task CopyAsync(AbsolutePath from, AbsolutePath to, CopyCommandContext context) + { + _newDiffs.Add(new Difference(DifferenceActionType.Create, to)); + return Task.CompletedTask; + } +} \ No newline at end of file diff --git a/src/Core/FileTime.Core.Command/Helper.cs b/src/Core/FileTime.Core.Command/Helper.cs new file mode 100644 index 0000000..99bdb2f --- /dev/null +++ b/src/Core/FileTime.Core.Command/Helper.cs @@ -0,0 +1,28 @@ +using FileTime.Core.Extensions; +using FileTime.Core.Models; + +namespace FileTime.Core.Command; + +public static class Helper +{ + public static async Task GetNewNameAsync(IContainer resolvedTarget, string name, TransportMode transportMode) + { + var items = await resolvedTarget.Items.GetItemsAsync() ?? throw new NullReferenceException(); + var newName = name; + var targetNameExists = resolvedTarget != null && items.Any(i => i.Path.GetName() == newName); + if (transportMode == TransportMode.Merge && resolvedTarget != null) + { + for (var i = 0; targetNameExists; i++) + { + newName = name + (i == 0 ? "_" : $"_{i}"); + targetNameExists = resolvedTarget != null && items.Any(i => i.Path.GetName() == newName); + } + } + else if (transportMode == TransportMode.Skip && targetNameExists) + { + return null; + } + + return newName; + } +} \ No newline at end of file diff --git a/src/Core/FileTime.Core.Command/OperationProgress.cs b/src/Core/FileTime.Core.Command/OperationProgress.cs new file mode 100644 index 0000000..48a73bb --- /dev/null +++ b/src/Core/FileTime.Core.Command/OperationProgress.cs @@ -0,0 +1,24 @@ +using System.Reactive.Linq; +using System.Reactive.Subjects; + +namespace FileTime.Core.Command; + +public class OperationProgress +{ + private readonly BehaviorSubject _currentProgress = new(0); + public string Key { get; } + public IObservable Progress { get; } + public long TotalCount { get; } + public IObservable IsDone { get; } + + public OperationProgress(string key, long totalCount) + { + Key = key; + TotalCount = totalCount; + + Progress = _currentProgress.AsObservable(); + IsDone = Progress.Select(p => p >= TotalCount); + } + + public void SetProgress(long progress) => _currentProgress.OnNext(progress); +} \ No newline at end of file