MoveCommand
This commit is contained in:
@@ -0,0 +1,5 @@
|
||||
using FileTime.Core.Models;
|
||||
|
||||
namespace FileTime.Core.Command.Move;
|
||||
|
||||
public record ItemToMove(FullName Source, FullName Target);
|
||||
@@ -10,4 +10,6 @@ public interface IContentAccessorFactory
|
||||
IContentWriterFactory GetContentWriterFactory(IContentProvider provider);
|
||||
IItemDeleter GetItemDeleter(IContentProvider provider);
|
||||
IItemDeleter<TContentProvider> GetItemDeleter<TContentProvider>() where TContentProvider : IContentProvider;
|
||||
IItemMover GetItemMover(IContentProvider provider);
|
||||
IItemMover<TContentProvider> GetItemMover<TContentProvider>() where TContentProvider : IContentProvider;
|
||||
}
|
||||
@@ -10,4 +10,17 @@ public interface IItemDeleter
|
||||
public interface IItemDeleter<in TContentProvider> : IItemDeleter where TContentProvider : IContentProvider
|
||||
{
|
||||
Task DeleteAsync(TContentProvider contentProvider, FullName fullName);
|
||||
|
||||
async Task IItemDeleter.DeleteAsync(IContentProvider contentProvider, FullName fullName)
|
||||
{
|
||||
if (contentProvider is not TContentProvider provider)
|
||||
{
|
||||
throw new ArgumentException(
|
||||
$"Content provider ({contentProvider.GetType()}) is not the required type ({typeof(TContentProvider)}) ",
|
||||
nameof(contentProvider)
|
||||
);
|
||||
}
|
||||
|
||||
await DeleteAsync(provider, fullName);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,24 @@
|
||||
using FileTime.Core.Models;
|
||||
|
||||
namespace FileTime.Core.ContentAccess;
|
||||
|
||||
public interface IItemMover
|
||||
{
|
||||
Task RenameAsync(IContentProvider contentProvider, FullName fullName, FullName newPath);
|
||||
}
|
||||
|
||||
public interface IItemMover<in TContentProvider> : IItemMover where TContentProvider : IContentProvider
|
||||
{
|
||||
Task RenameAsync(TContentProvider contentProvider, FullName fullName, FullName newPath);
|
||||
|
||||
async Task IItemMover.RenameAsync(IContentProvider contentProvider, FullName fullName, FullName newPath)
|
||||
{
|
||||
if(contentProvider is not TContentProvider provider)
|
||||
throw new ArgumentException(
|
||||
$"Content provider ({contentProvider.GetType()}) is not the required type ({typeof(TContentProvider)}) ",
|
||||
nameof(contentProvider)
|
||||
);
|
||||
|
||||
await RenameAsync(provider, fullName, newPath);
|
||||
}
|
||||
}
|
||||
@@ -33,4 +33,16 @@ public abstract class CommandBase : ICommand
|
||||
protected void SetTotalProgress(int totalProgress) => _totalProgress.OnNext(totalProgress);
|
||||
|
||||
protected void SetCurrentProgress(int currentProgress) => _currentProgress.OnNext(currentProgress);
|
||||
|
||||
protected IDisposable TrackProgress(IEnumerable<OperationProgress> operationProgresses) =>
|
||||
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);
|
||||
}
|
||||
@@ -103,16 +103,8 @@ public class CopyCommand : CommandBase, ITransportationCommand
|
||||
_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);
|
||||
//TODO: Handle IDisposable
|
||||
TrackProgress(_operationProgresses);
|
||||
|
||||
|
||||
if (Sources.Count == 1)
|
||||
@@ -127,6 +119,7 @@ public class CopyCommand : CommandBase, ITransportationCommand
|
||||
.Subscribe(statuses =>
|
||||
{
|
||||
var done = statuses.Count(s => s) + 1;
|
||||
if(done > statuses.Count) done = statuses.Count;
|
||||
|
||||
SetDisplayLabel($"Copy - {done} / {statuses.Count}");
|
||||
});
|
||||
|
||||
90
src/Core/FileTime.Core.Command/Move/MoveCommand.cs
Normal file
90
src/Core/FileTime.Core.Command/Move/MoveCommand.cs
Normal file
@@ -0,0 +1,90 @@
|
||||
using System.Reactive.Subjects;
|
||||
using FileTime.Core.ContentAccess;
|
||||
using FileTime.Core.Timeline;
|
||||
|
||||
namespace FileTime.Core.Command.Move;
|
||||
|
||||
public class MoveCommand : CommandBase, IExecutableCommand
|
||||
{
|
||||
private readonly IContentAccessorFactory _contentAccessorFactory;
|
||||
private readonly ITimelessContentProvider _timelessContentProvider;
|
||||
private readonly ICommandSchedulerNotifier _commandSchedulerNotifier;
|
||||
|
||||
private readonly List<OperationProgress> _operationProgresses = new();
|
||||
private readonly BehaviorSubject<OperationProgress?> _currentOperationProgress = new(null);
|
||||
public IReadOnlyList<ItemToMove> ItemsToMove { get; }
|
||||
|
||||
internal MoveCommand(
|
||||
IEnumerable<ItemToMove> itemsToMove,
|
||||
IContentAccessorFactory contentAccessorFactory,
|
||||
ITimelessContentProvider timelessContentProvider,
|
||||
ICommandSchedulerNotifier commandSchedulerNotifier)
|
||||
: base("Move")
|
||||
{
|
||||
_contentAccessorFactory = contentAccessorFactory;
|
||||
_timelessContentProvider = timelessContentProvider;
|
||||
_commandSchedulerNotifier = commandSchedulerNotifier;
|
||||
ItemsToMove = itemsToMove.ToList().AsReadOnly();
|
||||
}
|
||||
|
||||
public override Task<CanCommandRun> CanRun(PointInTime currentTime)
|
||||
{
|
||||
//TODO
|
||||
|
||||
return Task.FromResult(CanCommandRun.True);
|
||||
}
|
||||
|
||||
public override Task<PointInTime> SimulateCommand(PointInTime currentTime)
|
||||
{
|
||||
//TODO
|
||||
|
||||
return Task.FromResult(currentTime);
|
||||
}
|
||||
|
||||
public async Task Execute()
|
||||
{
|
||||
Calculate();
|
||||
await Move();
|
||||
}
|
||||
|
||||
private void Calculate()
|
||||
{
|
||||
_operationProgresses.Clear();
|
||||
_operationProgresses.AddRange(ItemsToMove.Select(i => new OperationProgress(i.Source.Path, 1)));
|
||||
|
||||
//TODO: Handle IDisposable
|
||||
TrackProgress(_operationProgresses);
|
||||
}
|
||||
|
||||
private async Task Move()
|
||||
{
|
||||
Dictionary<string, IItemMover> itemMovers = new();
|
||||
foreach (var itemToMove in ItemsToMove)
|
||||
{
|
||||
var currentOperationProgress =_operationProgresses.Find(p => p.Key == itemToMove.Source.Path)!;
|
||||
_currentOperationProgress.OnNext(currentOperationProgress);
|
||||
|
||||
var sourceItem = await _timelessContentProvider.GetItemByFullNameAsync(itemToMove.Source, PointInTime.Present);
|
||||
|
||||
var itemMover = GetOrAddItemMover(sourceItem.Provider);
|
||||
await itemMover.RenameAsync(sourceItem.Provider, itemToMove.Source, itemToMove.Target);
|
||||
|
||||
if (itemToMove.Source.GetParent() is { } parent)
|
||||
await _commandSchedulerNotifier.RefreshContainer(parent);
|
||||
|
||||
currentOperationProgress.SetProgress(1);
|
||||
}
|
||||
|
||||
IItemMover GetOrAddItemMover(IContentProvider provider)
|
||||
{
|
||||
if (itemMovers.TryGetValue(provider.Name, out var mover))
|
||||
{
|
||||
return mover;
|
||||
}
|
||||
|
||||
var itemMover = _contentAccessorFactory.GetItemMover(provider);
|
||||
itemMovers.Add(provider.Name, itemMover);
|
||||
return itemMover;
|
||||
}
|
||||
}
|
||||
}
|
||||
24
src/Core/FileTime.Core.Command/Move/MoveCommandFactory.cs
Normal file
24
src/Core/FileTime.Core.Command/Move/MoveCommandFactory.cs
Normal file
@@ -0,0 +1,24 @@
|
||||
using FileTime.Core.ContentAccess;
|
||||
using FileTime.Core.Timeline;
|
||||
|
||||
namespace FileTime.Core.Command.Move;
|
||||
|
||||
public class MoveCommandFactory
|
||||
{
|
||||
private readonly IContentAccessorFactory _contentAccessorFactory;
|
||||
private readonly ITimelessContentProvider _timelessContentProvider;
|
||||
private readonly ICommandSchedulerNotifier _commandSchedulerNotifier;
|
||||
|
||||
public MoveCommandFactory(
|
||||
IContentAccessorFactory contentAccessorFactory,
|
||||
ITimelessContentProvider timelessContentProvider,
|
||||
ICommandSchedulerNotifier commandSchedulerNotifier)
|
||||
{
|
||||
_contentAccessorFactory = contentAccessorFactory;
|
||||
_timelessContentProvider = timelessContentProvider;
|
||||
_commandSchedulerNotifier = commandSchedulerNotifier;
|
||||
}
|
||||
|
||||
public MoveCommand GenerateCommand(IEnumerable<ItemToMove> itemsToMove)
|
||||
=> new(itemsToMove, _contentAccessorFactory, _timelessContentProvider, _commandSchedulerNotifier);
|
||||
}
|
||||
@@ -1,10 +1,13 @@
|
||||
using FileTime.Core.Command.Copy;
|
||||
using FileTime.Core.Command.Move;
|
||||
using Microsoft.Extensions.DependencyInjection;
|
||||
|
||||
namespace FileTime.Core.Command;
|
||||
|
||||
public static class Startup
|
||||
{
|
||||
public static IServiceCollection AddCommands(this IServiceCollection serviceCollection)
|
||||
=> serviceCollection.AddSingleton<CopyCommandFactory>();
|
||||
public static IServiceCollection AddCommands(this IServiceCollection serviceCollection) =>
|
||||
serviceCollection
|
||||
.AddSingleton<CopyCommandFactory>()
|
||||
.AddSingleton<MoveCommandFactory>();
|
||||
}
|
||||
@@ -66,4 +66,18 @@ public class ContentAccessorFactory : IContentAccessorFactory
|
||||
|
||||
return (IItemDeleter)_serviceProvider.GetRequiredService(genericType);
|
||||
}
|
||||
|
||||
public IItemMover<TContentProvider> GetItemMover<TContentProvider>() where TContentProvider : IContentProvider
|
||||
{
|
||||
var genericType = typeof(IItemMover<>).MakeGenericType(typeof(TContentProvider));
|
||||
|
||||
return (IItemMover<TContentProvider>)_serviceProvider.GetRequiredService(genericType);
|
||||
}
|
||||
|
||||
public IItemMover GetItemMover(IContentProvider provider)
|
||||
{
|
||||
var genericType = typeof(IItemMover<>).MakeGenericType(provider.GetType());
|
||||
|
||||
return (IItemMover)_serviceProvider.GetRequiredService(genericType);
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user