MoveCommand
This commit is contained in:
@@ -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;
|
||||
}
|
||||
@@ -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;
|
||||
|
||||
@@ -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);
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
}
|
||||
@@ -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),
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
}
|
||||
28
src/Providers/FileTime.Providers.Local/LocalItemMover.cs
Normal file
28
src/Providers/FileTime.Providers.Local/LocalItemMover.cs
Normal 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;
|
||||
}
|
||||
}
|
||||
@@ -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>();
|
||||
|
||||
Reference in New Issue
Block a user