MoveCommand

This commit is contained in:
2023-07-04 22:07:11 +02:00
parent 718fd53026
commit 453834646b
17 changed files with 263 additions and 22 deletions

View File

@@ -0,0 +1,13 @@
namespace FileTime.App.Core.UserCommand;
public class RenameCommand : IIdentifiableUserCommand
{
public const string CommandName = "rename";
public static RenameCommand Instance { get; } = new();
private RenameCommand()
{
}
public string UserCommandID => CommandName;
}

View File

@@ -5,6 +5,7 @@ using FileTime.App.Core.ViewModels;
using FileTime.Core.Command;
using FileTime.Core.Command.CreateContainer;
using FileTime.Core.Command.CreateElement;
using FileTime.Core.Command.Move;
using FileTime.Core.Extensions;
using FileTime.Core.Interactions;
using FileTime.Core.Models;
@@ -60,6 +61,7 @@ public class ItemManipulationUserCommandHandlerService : UserCommandHandlerServi
{
new TypeUserCommandHandler<CopyCommand>(Copy),
new TypeUserCommandHandler<DeleteCommand>(Delete),
new TypeUserCommandHandler<RenameCommand>(Rename),
new TypeUserCommandHandler<MarkCommand>(MarkItem),
new TypeUserCommandHandler<PasteCommand>(Paste),
new TypeUserCommandHandler<CreateContainer>(CreateContainer),
@@ -171,6 +173,29 @@ public class ItemManipulationUserCommandHandlerService : UserCommandHandlerServi
await AddCommand(command);
}
private async Task Rename(RenameCommand command)
{
//TODO: group rename
List<ItemToMove> itemsToMove = new();
if (_currentSelectedItem?.BaseItem?.FullName is null) return;
var item = await _timelessContentProvider.GetItemByFullNameAsync(_currentSelectedItem.BaseItem.FullName, PointInTime.Present);
if (item is null) return;
var renameInput = new TextInputElement("New name", item.Name);
await _userCommunicationService.ReadInputs(renameInput);
//TODO: should check these...
var newPath = item.FullName!.GetParent()!.GetChild(renameInput.Value!);
itemsToMove.Add(new ItemToMove(item.FullName, newPath));
var moveCommandFactory = _serviceProvider.GetRequiredService<MoveCommandFactory>();
var moveCommand = moveCommandFactory.GenerateCommand(itemsToMove);
await AddCommand(moveCommand);
}
private async Task Delete(DeleteCommand command)
{
IList<FullName>? itemsToDelete = null;

View File

@@ -43,6 +43,7 @@ public class DefaultIdentifiableCommandHandlerRegister : IStartupHandler
AddUserCommand(PasteFilesFromClipboardCommand.Merge);
AddUserCommand(PauseCommandSchedulerCommand.Instance);
AddUserCommand(RefreshCommand.Instance);
AddUserCommand(RenameCommand.Instance);
AddUserCommand(StartCommandSchedulerCommand.Instance);
AddUserCommand(IdentifiableSearchCommand.SearchByNameContains);
AddUserCommand(SwitchToTabCommand.SwitchToLastTab);

View File

@@ -0,0 +1,5 @@
using FileTime.Core.Models;
namespace FileTime.Core.Command.Move;
public record ItemToMove(FullName Source, FullName Target);

View File

@@ -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;
}

View File

@@ -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);
}
}

View File

@@ -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);
}
}

View File

@@ -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);
}

View File

@@ -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}");
});

View 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;
}
}
}

View 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);
}

View File

@@ -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>();
}

View File

@@ -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);
}
}

View File

@@ -82,8 +82,8 @@ public static class MainConfiguration
//new CommandBindingConfiguration(ConfigCommand.PreviousTimelineBlock, Key.H ),
//new CommandBindingConfiguration(ConfigCommand.PreviousTimelineCommand, Key.K ),
new(RefreshCommand.CommandName, Key.R),
//new CommandBindingConfiguration(ConfigCommand.Rename, Key.F2),
//new CommandBindingConfiguration(ConfigCommand.Rename, new[] { Key.C, Key.W }),
new(RenameCommand.CommandName, Key.F2),
new(RenameCommand.CommandName, new[] { Key.C, Key.W }),
//new CommandBindingConfiguration(ConfigCommand.RunCommand, new KeyConfig(Key.D4, shift: true)),
//new CommandBindingConfiguration(ConfigCommand.ScanContainerSize, new[] { Key.C, Key.S }),
//new CommandBindingConfiguration(ConfigCommand.ShowAllShortcut, Key.F1),

View File

@@ -24,12 +24,4 @@ public class LocalItemDeleter : IItemDeleter<ILocalContentProvider>
return Task.CompletedTask;
}
public async Task DeleteAsync(IContentProvider contentProvider, FullName fullName)
{
var localContentProvider = contentProvider as ILocalContentProvider;
if (localContentProvider is null) throw new ArgumentException("Content provider is not a local content provider", nameof(contentProvider));
await DeleteAsync(localContentProvider, fullName);
}
}

View File

@@ -0,0 +1,28 @@
using FileTime.Core.ContentAccess;
using FileTime.Core.Models;
namespace FileTime.Providers.Local;
public class LocalItemMover : IItemMover<ILocalContentProvider>
{
public Task RenameAsync(ILocalContentProvider contentProvider, FullName fullName, FullName newPath)
{
var source = contentProvider.GetNativePath(fullName);
var destination = contentProvider.GetNativePath(newPath);
if (File.Exists(source.Path))
{
File.Move(source.Path, destination.Path);
}
else if (Directory.Exists(source.Path))
{
Directory.Move(source.Path, destination.Path);
}
else
{
throw new FileNotFoundException(source.Path);
}
return Task.CompletedTask;
}
}

View File

@@ -14,6 +14,8 @@ public static class Startup
serviceCollection.TryAddSingleton<IItemCreator<LocalContentProvider>>(sp => sp.GetRequiredService<IItemCreator<ILocalContentProvider>>());
serviceCollection.TryAddSingleton<IItemDeleter<ILocalContentProvider>, LocalItemDeleter>();
serviceCollection.TryAddSingleton<IItemDeleter<LocalContentProvider>>(sp => sp.GetRequiredService<IItemDeleter<ILocalContentProvider>>());
serviceCollection.TryAddSingleton<IItemMover<ILocalContentProvider>, LocalItemMover>();
serviceCollection.TryAddSingleton<IItemMover<LocalContentProvider>>(sp => sp.GetRequiredService<IItemMover<ILocalContentProvider>>());
serviceCollection.TryAddSingleton<IContentReaderFactory<ILocalContentProvider>, LocalContentReaderFactory>();
serviceCollection.TryAddSingleton<IContentReaderFactory<LocalContentProvider>>(sp => sp.GetRequiredService<IContentReaderFactory<ILocalContentProvider>>());
serviceCollection.TryAddSingleton<IContentWriterFactory<ILocalContentProvider>, LocalContentWriterFactory>();