From 453834646bc11a916d98dfaf8ff26c01ba35f2a1 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=C3=81d=C3=A1m=20Kov=C3=A1cs?= Date: Tue, 4 Jul 2023 22:07:11 +0200 Subject: [PATCH] MoveCommand --- .../UserCommand/RenameCommand.cs | 13 +++ ...emManipulationUserCommandHandlerService.cs | 25 ++++++ ...faultIdentifiableCommandHandlerRegister.cs | 1 + .../Command/Move/ItemToMove.cs | 5 ++ .../ContentAccess/IContentAccessorFactory.cs | 2 + .../ContentAccess/IItemDeleter.cs | 13 +++ .../ContentAccess/IItemMover.cs | 24 +++++ src/Core/FileTime.Core.Command/CommandBase.cs | 12 +++ .../FileTime.Core.Command/Copy/CopyCommand.cs | 13 +-- .../FileTime.Core.Command/Move/MoveCommand.cs | 90 +++++++++++++++++++ .../Move/MoveCommandFactory.cs | 24 +++++ src/Core/FileTime.Core.Command/Startup.cs | 7 +- .../ContentAccessorFactory.cs | 14 +++ .../Configuration/MainConfiguration.cs | 4 +- .../LocalItemDeleter.cs | 8 -- .../LocalItemMover.cs | 28 ++++++ .../FileTime.Providers.Local/Startup.cs | 2 + 17 files changed, 263 insertions(+), 22 deletions(-) create mode 100644 src/AppCommon/FileTime.App.Core.Abstraction/UserCommand/RenameCommand.cs create mode 100644 src/Core/FileTime.Core.Abstraction/Command/Move/ItemToMove.cs create mode 100644 src/Core/FileTime.Core.Abstraction/ContentAccess/IItemMover.cs create mode 100644 src/Core/FileTime.Core.Command/Move/MoveCommand.cs create mode 100644 src/Core/FileTime.Core.Command/Move/MoveCommandFactory.cs create mode 100644 src/Providers/FileTime.Providers.Local/LocalItemMover.cs diff --git a/src/AppCommon/FileTime.App.Core.Abstraction/UserCommand/RenameCommand.cs b/src/AppCommon/FileTime.App.Core.Abstraction/UserCommand/RenameCommand.cs new file mode 100644 index 0000000..a2cced2 --- /dev/null +++ b/src/AppCommon/FileTime.App.Core.Abstraction/UserCommand/RenameCommand.cs @@ -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; +} \ No newline at end of file diff --git a/src/AppCommon/FileTime.App.Core/Services/UserCommandHandler/ItemManipulationUserCommandHandlerService.cs b/src/AppCommon/FileTime.App.Core/Services/UserCommandHandler/ItemManipulationUserCommandHandlerService.cs index 951727d..89dbacc 100644 --- a/src/AppCommon/FileTime.App.Core/Services/UserCommandHandler/ItemManipulationUserCommandHandlerService.cs +++ b/src/AppCommon/FileTime.App.Core/Services/UserCommandHandler/ItemManipulationUserCommandHandlerService.cs @@ -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(Copy), new TypeUserCommandHandler(Delete), + new TypeUserCommandHandler(Rename), new TypeUserCommandHandler(MarkItem), new TypeUserCommandHandler(Paste), new TypeUserCommandHandler(CreateContainer), @@ -171,6 +173,29 @@ public class ItemManipulationUserCommandHandlerService : UserCommandHandlerServi await AddCommand(command); } + private async Task Rename(RenameCommand command) + { + //TODO: group rename + List 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(); + var moveCommand = moveCommandFactory.GenerateCommand(itemsToMove); + await AddCommand(moveCommand); + } + private async Task Delete(DeleteCommand command) { IList? itemsToDelete = null; diff --git a/src/AppCommon/FileTime.App.Core/StartupServices/DefaultIdentifiableCommandHandlerRegister.cs b/src/AppCommon/FileTime.App.Core/StartupServices/DefaultIdentifiableCommandHandlerRegister.cs index 7476a9f..4f21604 100644 --- a/src/AppCommon/FileTime.App.Core/StartupServices/DefaultIdentifiableCommandHandlerRegister.cs +++ b/src/AppCommon/FileTime.App.Core/StartupServices/DefaultIdentifiableCommandHandlerRegister.cs @@ -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); diff --git a/src/Core/FileTime.Core.Abstraction/Command/Move/ItemToMove.cs b/src/Core/FileTime.Core.Abstraction/Command/Move/ItemToMove.cs new file mode 100644 index 0000000..fb39e5d --- /dev/null +++ b/src/Core/FileTime.Core.Abstraction/Command/Move/ItemToMove.cs @@ -0,0 +1,5 @@ +using FileTime.Core.Models; + +namespace FileTime.Core.Command.Move; + +public record ItemToMove(FullName Source, FullName Target); \ No newline at end of file diff --git a/src/Core/FileTime.Core.Abstraction/ContentAccess/IContentAccessorFactory.cs b/src/Core/FileTime.Core.Abstraction/ContentAccess/IContentAccessorFactory.cs index 8653c35..a6fc55a 100644 --- a/src/Core/FileTime.Core.Abstraction/ContentAccess/IContentAccessorFactory.cs +++ b/src/Core/FileTime.Core.Abstraction/ContentAccess/IContentAccessorFactory.cs @@ -10,4 +10,6 @@ public interface IContentAccessorFactory IContentWriterFactory GetContentWriterFactory(IContentProvider provider); IItemDeleter GetItemDeleter(IContentProvider provider); IItemDeleter GetItemDeleter() where TContentProvider : IContentProvider; + IItemMover GetItemMover(IContentProvider provider); + IItemMover GetItemMover() where TContentProvider : IContentProvider; } \ No newline at end of file diff --git a/src/Core/FileTime.Core.Abstraction/ContentAccess/IItemDeleter.cs b/src/Core/FileTime.Core.Abstraction/ContentAccess/IItemDeleter.cs index c244838..7c6d440 100644 --- a/src/Core/FileTime.Core.Abstraction/ContentAccess/IItemDeleter.cs +++ b/src/Core/FileTime.Core.Abstraction/ContentAccess/IItemDeleter.cs @@ -10,4 +10,17 @@ public interface IItemDeleter public interface IItemDeleter : 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); + } } \ No newline at end of file diff --git a/src/Core/FileTime.Core.Abstraction/ContentAccess/IItemMover.cs b/src/Core/FileTime.Core.Abstraction/ContentAccess/IItemMover.cs new file mode 100644 index 0000000..e9b442d --- /dev/null +++ b/src/Core/FileTime.Core.Abstraction/ContentAccess/IItemMover.cs @@ -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 : 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); + } +} \ No newline at end of file diff --git a/src/Core/FileTime.Core.Command/CommandBase.cs b/src/Core/FileTime.Core.Command/CommandBase.cs index f2aab1c..0ec9622 100644 --- a/src/Core/FileTime.Core.Command/CommandBase.cs +++ b/src/Core/FileTime.Core.Command/CommandBase.cs @@ -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 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); } \ 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 dc825d4..5fbb549 100644 --- a/src/Core/FileTime.Core.Command/Copy/CopyCommand.cs +++ b/src/Core/FileTime.Core.Command/Copy/CopyCommand.cs @@ -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}"); }); diff --git a/src/Core/FileTime.Core.Command/Move/MoveCommand.cs b/src/Core/FileTime.Core.Command/Move/MoveCommand.cs new file mode 100644 index 0000000..4082920 --- /dev/null +++ b/src/Core/FileTime.Core.Command/Move/MoveCommand.cs @@ -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 _operationProgresses = new(); + private readonly BehaviorSubject _currentOperationProgress = new(null); + public IReadOnlyList ItemsToMove { get; } + + internal MoveCommand( + IEnumerable itemsToMove, + IContentAccessorFactory contentAccessorFactory, + ITimelessContentProvider timelessContentProvider, + ICommandSchedulerNotifier commandSchedulerNotifier) + : base("Move") + { + _contentAccessorFactory = contentAccessorFactory; + _timelessContentProvider = timelessContentProvider; + _commandSchedulerNotifier = commandSchedulerNotifier; + ItemsToMove = itemsToMove.ToList().AsReadOnly(); + } + + public override Task CanRun(PointInTime currentTime) + { + //TODO + + return Task.FromResult(CanCommandRun.True); + } + + public override Task 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 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; + } + } +} \ No newline at end of file diff --git a/src/Core/FileTime.Core.Command/Move/MoveCommandFactory.cs b/src/Core/FileTime.Core.Command/Move/MoveCommandFactory.cs new file mode 100644 index 0000000..8bdfcdc --- /dev/null +++ b/src/Core/FileTime.Core.Command/Move/MoveCommandFactory.cs @@ -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 itemsToMove) + => new(itemsToMove, _contentAccessorFactory, _timelessContentProvider, _commandSchedulerNotifier); +} \ No newline at end of file diff --git a/src/Core/FileTime.Core.Command/Startup.cs b/src/Core/FileTime.Core.Command/Startup.cs index 96df758..591d0e0 100644 --- a/src/Core/FileTime.Core.Command/Startup.cs +++ b/src/Core/FileTime.Core.Command/Startup.cs @@ -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(); + public static IServiceCollection AddCommands(this IServiceCollection serviceCollection) => + serviceCollection + .AddSingleton() + .AddSingleton(); } \ No newline at end of file diff --git a/src/Core/FileTime.Core.ContentAccess/ContentAccessorFactory.cs b/src/Core/FileTime.Core.ContentAccess/ContentAccessorFactory.cs index 87145bc..94934ab 100644 --- a/src/Core/FileTime.Core.ContentAccess/ContentAccessorFactory.cs +++ b/src/Core/FileTime.Core.ContentAccess/ContentAccessorFactory.cs @@ -66,4 +66,18 @@ public class ContentAccessorFactory : IContentAccessorFactory return (IItemDeleter)_serviceProvider.GetRequiredService(genericType); } + + public IItemMover GetItemMover() where TContentProvider : IContentProvider + { + var genericType = typeof(IItemMover<>).MakeGenericType(typeof(TContentProvider)); + + return (IItemMover)_serviceProvider.GetRequiredService(genericType); + } + + public IItemMover GetItemMover(IContentProvider provider) + { + var genericType = typeof(IItemMover<>).MakeGenericType(provider.GetType()); + + return (IItemMover)_serviceProvider.GetRequiredService(genericType); + } } \ No newline at end of file diff --git a/src/GuiApp/Avalonia/FileTime.GuiApp.Abstractions/Configuration/MainConfiguration.cs b/src/GuiApp/Avalonia/FileTime.GuiApp.Abstractions/Configuration/MainConfiguration.cs index a055b05..1a6f59a 100644 --- a/src/GuiApp/Avalonia/FileTime.GuiApp.Abstractions/Configuration/MainConfiguration.cs +++ b/src/GuiApp/Avalonia/FileTime.GuiApp.Abstractions/Configuration/MainConfiguration.cs @@ -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), diff --git a/src/Providers/FileTime.Providers.Local/LocalItemDeleter.cs b/src/Providers/FileTime.Providers.Local/LocalItemDeleter.cs index ad9d893..d2e18a7 100644 --- a/src/Providers/FileTime.Providers.Local/LocalItemDeleter.cs +++ b/src/Providers/FileTime.Providers.Local/LocalItemDeleter.cs @@ -24,12 +24,4 @@ public class LocalItemDeleter : IItemDeleter 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); - } } \ No newline at end of file diff --git a/src/Providers/FileTime.Providers.Local/LocalItemMover.cs b/src/Providers/FileTime.Providers.Local/LocalItemMover.cs new file mode 100644 index 0000000..af969a4 --- /dev/null +++ b/src/Providers/FileTime.Providers.Local/LocalItemMover.cs @@ -0,0 +1,28 @@ +using FileTime.Core.ContentAccess; +using FileTime.Core.Models; + +namespace FileTime.Providers.Local; + +public class LocalItemMover : IItemMover +{ + 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; + } +} \ No newline at end of file diff --git a/src/Providers/FileTime.Providers.Local/Startup.cs b/src/Providers/FileTime.Providers.Local/Startup.cs index a973066..1994c8b 100644 --- a/src/Providers/FileTime.Providers.Local/Startup.cs +++ b/src/Providers/FileTime.Providers.Local/Startup.cs @@ -14,6 +14,8 @@ public static class Startup serviceCollection.TryAddSingleton>(sp => sp.GetRequiredService>()); serviceCollection.TryAddSingleton, LocalItemDeleter>(); serviceCollection.TryAddSingleton>(sp => sp.GetRequiredService>()); + serviceCollection.TryAddSingleton, LocalItemMover>(); + serviceCollection.TryAddSingleton>(sp => sp.GetRequiredService>()); serviceCollection.TryAddSingleton, LocalContentReaderFactory>(); serviceCollection.TryAddSingleton>(sp => sp.GetRequiredService>()); serviceCollection.TryAddSingleton, LocalContentWriterFactory>();