diff --git a/src/Core/FileTime.Core/Command/CopyCommand.cs b/src/Core/FileTime.Core/Command/CopyCommand.cs index 7b0bdd3..ecd87e6 100644 --- a/src/Core/FileTime.Core/Command/CopyCommand.cs +++ b/src/Core/FileTime.Core/Command/CopyCommand.cs @@ -22,6 +22,7 @@ namespace FileTime.Core.Command public AsyncEventHandler ProgressChanged { get; } = new(); public string DisplayLabel { get; } = "Copy"; + public IReadOnlyList CanRunMessages { get; } = new List().AsReadOnly(); private async Task UpdateProgress() { @@ -73,7 +74,7 @@ namespace FileTime.Core.Command return (IContainer)(await newContainerDiff.AbsolutePath.Resolve())!; }; - await DoCopy(Sources, Target, TransportMode.Value); + await TraverseTree(Sources, Target, TransportMode.Value); return startPoint.WithDifferences(newDiffs); } @@ -107,7 +108,7 @@ namespace FileTime.Core.Command } }; - await DoCopy(Sources, Target, TransportMode.Value); + await TraverseTree(Sources, Target, TransportMode.Value); } private async Task CalculateProgress() @@ -136,11 +137,11 @@ namespace FileTime.Core.Command return Task.CompletedTask; }; - await DoCopy(Sources, Target, TransportMode.Value); + await TraverseTree(Sources, Target, TransportMode.Value); _operationStatuses = operationStatuses; } - private async Task DoCopy( + private async Task TraverseTree( IEnumerable sources, IContainer target, TransportMode transportMode) @@ -159,7 +160,7 @@ namespace FileTime.Core.Command var childDirectories = (await container.GetContainers())!.Select(d => new AbsolutePath(d)); var childFiles = (await container.GetElements())!.Select(f => new AbsolutePath(f)); - await DoCopy(childDirectories.Concat(childFiles), targetContainer, transportMode); + await TraverseTree(childDirectories.Concat(childFiles), targetContainer, transportMode); if (_containerCopyDone != null) await _containerCopyDone.Invoke(new AbsolutePath(container)); } else if (item is IElement element) diff --git a/src/Core/FileTime.Core/Command/CreateContainerCommand.cs b/src/Core/FileTime.Core/Command/CreateContainerCommand.cs index fa845c6..bfe2448 100644 --- a/src/Core/FileTime.Core/Command/CreateContainerCommand.cs +++ b/src/Core/FileTime.Core/Command/CreateContainerCommand.cs @@ -13,6 +13,7 @@ namespace FileTime.Core.Command public AsyncEventHandler ProgressChanged { get; } = new(); public string DisplayLabel { get; } = "CreateContainer"; + public IReadOnlyList CanRunMessages { get; } = new List().AsReadOnly(); public CreateContainerCommand(AbsolutePath container, string newContainerName) { diff --git a/src/Core/FileTime.Core/Command/CreateElementCommand.cs b/src/Core/FileTime.Core/Command/CreateElementCommand.cs index a5c94a3..f006120 100644 --- a/src/Core/FileTime.Core/Command/CreateElementCommand.cs +++ b/src/Core/FileTime.Core/Command/CreateElementCommand.cs @@ -12,6 +12,7 @@ namespace FileTime.Core.Command public int Progress => 100; public AsyncEventHandler ProgressChanged { get; } = new(); public string DisplayLabel { get; } = "CreateElement"; + public IReadOnlyList CanRunMessages { get; } = new List().AsReadOnly(); public CreateElementCommand(AbsolutePath container, string newElementName) { diff --git a/src/Core/FileTime.Core/Command/DeleteCommand.cs b/src/Core/FileTime.Core/Command/DeleteCommand.cs index 12c028c..b143c56 100644 --- a/src/Core/FileTime.Core/Command/DeleteCommand.cs +++ b/src/Core/FileTime.Core/Command/DeleteCommand.cs @@ -7,6 +7,9 @@ namespace FileTime.Core.Command { public class DeleteCommand : IExecutableCommand { + private Func? _deleteContainer; + private Func? _deleteElement; + public int Progress => 100; public AsyncEventHandler ProgressChanged { get; } = new(); @@ -14,46 +17,79 @@ namespace FileTime.Core.Command public IList ItemsToDelete { get; } = new List(); public string DisplayLabel { get; } = "DeleteCommand"; + public bool HardDelete { get; set; } + public IReadOnlyList CanRunMessages { get; } = new List().AsReadOnly(); + public async Task SimulateCommand(PointInTime startPoint) { var newDifferences = new List(); - foreach (var itemToDelete in ItemsToDelete) + _deleteContainer = (c) => { - var item = await itemToDelete.Resolve(); newDifferences.Add(new Difference( - item.ToDifferenceItemType(), + DifferenceItemType.Container, DifferenceActionType.Delete, - itemToDelete + new AbsolutePath(c) )); + + return Task.CompletedTask; + }; + _deleteElement = (e) => + { + newDifferences.Add(new Difference( + DifferenceItemType.Element, + DifferenceActionType.Delete, + new AbsolutePath(e) + )); + + return Task.CompletedTask; + }; + + foreach (var item in ItemsToDelete) + { + await TraverseTree((await item.Resolve())!); } + return startPoint.WithDifferences(newDifferences); } public async Task Execute(TimeRunner timeRunner) { + _deleteContainer = async (c) => + { + await c.Delete(HardDelete); + await timeRunner.RefreshContainer.InvokeAsync(this, new AbsolutePath(c)); + }; + _deleteElement = async (e) => await e.Delete(HardDelete); + foreach (var item in ItemsToDelete) { - await DoDelete((await item.Resolve())!, timeRunner); + await TraverseTree((await item.Resolve())!); } } - private async Task DoDelete(IItem item, TimeRunner timeRunner) + private async Task TraverseTree(IItem item) { if (item is IContainer container) { - foreach (var child in (await container.GetItems())!) + if (!HardDelete && container.SupportsDirectoryLevelSoftDelete) { - await DoDelete(child, timeRunner); - await child.Delete(); + if (_deleteContainer != null) await _deleteContainer.Invoke(container); + } + else + { + foreach (var child in (await container.GetItems())!) + { + await TraverseTree(child); + } + + if (_deleteContainer != null) await _deleteContainer.Invoke(container); } - await item.Delete(); - await timeRunner.RefreshContainer.InvokeAsync(this, new AbsolutePath(container)); } else if (item is IElement element) { - await element.Delete(); + if (_deleteElement != null) await _deleteElement.Invoke(element); } } @@ -63,7 +99,12 @@ namespace FileTime.Core.Command foreach (var itemPath in ItemsToDelete) { var resolvedItem = await itemPath.Resolve(); - if (!(resolvedItem?.CanDelete ?? true)) + if (resolvedItem != null + && ( + resolvedItem.CanDelete == SupportsDelete.False + || (resolvedItem.CanDelete == SupportsDelete.HardDeleteOnly && !HardDelete) + ) + ) { result = CanCommandRun.Forceable; } diff --git a/src/Core/FileTime.Core/Command/ICommand.cs b/src/Core/FileTime.Core/Command/ICommand.cs index 534da50..0bbde3d 100644 --- a/src/Core/FileTime.Core/Command/ICommand.cs +++ b/src/Core/FileTime.Core/Command/ICommand.cs @@ -6,6 +6,7 @@ namespace FileTime.Core.Command public interface ICommand { string DisplayLabel { get; } + IReadOnlyList CanRunMessages { get; } Task CanRun(PointInTime startPoint); Task SimulateCommand(PointInTime startPoint); int Progress { get; } diff --git a/src/Core/FileTime.Core/Command/MoveCommand.cs b/src/Core/FileTime.Core/Command/MoveCommand.cs index 373f932..a3f5219 100644 --- a/src/Core/FileTime.Core/Command/MoveCommand.cs +++ b/src/Core/FileTime.Core/Command/MoveCommand.cs @@ -14,6 +14,7 @@ namespace FileTime.Core.Command public int Progress => 100; public AsyncEventHandler ProgressChanged { get; } = new(); public string DisplayLabel { get; } = "MoveCommand"; + public IReadOnlyList CanRunMessages { get; } = new List().AsReadOnly(); public Task CanRun(PointInTime startPoint) { diff --git a/src/Core/FileTime.Core/Command/RenameCommand.cs b/src/Core/FileTime.Core/Command/RenameCommand.cs index 5c240c6..ccb815f 100644 --- a/src/Core/FileTime.Core/Command/RenameCommand.cs +++ b/src/Core/FileTime.Core/Command/RenameCommand.cs @@ -13,6 +13,7 @@ namespace FileTime.Core.Command public int Progress => 100; public AsyncEventHandler ProgressChanged { get; } = new(); public string DisplayLabel { get; } = "RenameCommand"; + public IReadOnlyList CanRunMessages { get; } = new List().AsReadOnly(); public RenameCommand(AbsolutePath source, string target) { diff --git a/src/Core/FileTime.Core/Models/IContainer.cs b/src/Core/FileTime.Core/Models/IContainer.cs index fcd1797..2e377e8 100644 --- a/src/Core/FileTime.Core/Models/IContainer.cs +++ b/src/Core/FileTime.Core/Models/IContainer.cs @@ -20,6 +20,7 @@ namespace FileTime.Core.Models Task CanOpen(); bool IsLoaded { get; } + bool SupportsDirectoryLevelSoftDelete { get; } AsyncEventHandler Refreshed { get; } } diff --git a/src/Core/FileTime.Core/Models/IItem.cs b/src/Core/FileTime.Core/Models/IItem.cs index bd3cadd..1c5c3e9 100644 --- a/src/Core/FileTime.Core/Models/IItem.cs +++ b/src/Core/FileTime.Core/Models/IItem.cs @@ -7,10 +7,10 @@ namespace FileTime.Core.Models string Name { get; } string? FullName { get; } bool IsHidden { get; } - bool CanDelete { get; } + SupportsDelete CanDelete { get; } bool CanRename { get; } IContentProvider Provider { get; } - Task Delete(); + Task Delete(bool hardDelete = false); Task Rename(string newName); IContainer? GetParent(); } diff --git a/src/Core/FileTime.Core/Models/SupportsDelete.cs b/src/Core/FileTime.Core/Models/SupportsDelete.cs new file mode 100644 index 0000000..4648023 --- /dev/null +++ b/src/Core/FileTime.Core/Models/SupportsDelete.cs @@ -0,0 +1,9 @@ +namespace FileTime.Core.Models +{ + public enum SupportsDelete + { + True, + HardDeleteOnly, + False + } +} \ No newline at end of file diff --git a/src/Core/FileTime.Core/Models/VirtualContainer.cs b/src/Core/FileTime.Core/Models/VirtualContainer.cs index 6db050d..d5d86dc 100644 --- a/src/Core/FileTime.Core/Models/VirtualContainer.cs +++ b/src/Core/FileTime.Core/Models/VirtualContainer.cs @@ -25,12 +25,14 @@ namespace FileTime.Core.Models public bool IsHidden => BaseContainer.IsHidden; public bool IsLoaded => BaseContainer.IsLoaded; - public bool CanDelete => BaseContainer.CanDelete; + public SupportsDelete CanDelete => BaseContainer.CanDelete; public bool CanRename => BaseContainer.CanRename; public IContentProvider Provider => BaseContainer.Provider; public IReadOnlyList Exceptions => BaseContainer.Exceptions; + public bool SupportsDirectoryLevelSoftDelete => BaseContainer.SupportsDirectoryLevelSoftDelete; + public AsyncEventHandler Refreshed { get; } private void RefreshAddBase(Func handler) @@ -151,7 +153,7 @@ namespace FileTime.Core.Models return Task.FromResult(Elements); } - public async Task Delete() => await BaseContainer.Delete(); + public async Task Delete(bool hardDelete = false) => await BaseContainer.Delete(); public async Task Clone() { return new VirtualContainer( diff --git a/src/Core/FileTime.Core/Providers/TopContainer.cs b/src/Core/FileTime.Core/Providers/TopContainer.cs index 52387f4..3acbbe9 100644 --- a/src/Core/FileTime.Core/Providers/TopContainer.cs +++ b/src/Core/FileTime.Core/Providers/TopContainer.cs @@ -24,13 +24,15 @@ namespace FileTime.Core.Providers #pragma warning disable CS8603 // Possible null reference return. public IContentProvider Provider => null; #pragma warning restore CS8603 // Possible null reference return. - public bool CanDelete => false; + public SupportsDelete CanDelete => SupportsDelete.False; public bool CanRename => false; public AsyncEventHandler Refreshed { get; } = new(); public IReadOnlyList Exceptions { get; } = new List().AsReadOnly(); + public bool SupportsDirectoryLevelSoftDelete => false; + public TopContainer(IEnumerable contentProviders) { _contentProviders = new List(contentProviders); @@ -47,7 +49,7 @@ namespace FileTime.Core.Providers public Task CreateElement(string name) => throw new NotImplementedException(); - public Task Delete() => throw new NotImplementedException(); + public Task Delete(bool hardDelete = false) => throw new NotImplementedException(); public Task GetByPath(string path, bool acceptDeepestMatch = false) => throw new NotImplementedException(); diff --git a/src/Core/FileTime.Core/Timeline/TimeContainer.cs b/src/Core/FileTime.Core/Timeline/TimeContainer.cs index 5b85160..59f7c9e 100644 --- a/src/Core/FileTime.Core/Timeline/TimeContainer.cs +++ b/src/Core/FileTime.Core/Timeline/TimeContainer.cs @@ -19,7 +19,7 @@ namespace FileTime.Core.Timeline public bool IsHidden => false; - public bool CanDelete => true; + public SupportsDelete CanDelete => SupportsDelete.True; public bool CanRename => true; @@ -27,6 +27,8 @@ namespace FileTime.Core.Timeline public IContentProvider VirtualProvider { get; } public IReadOnlyList Exceptions { get; } = new List().AsReadOnly(); + public bool SupportsDirectoryLevelSoftDelete => false; + public TimeContainer(string name, IContainer parent, IContentProvider contentProvider, IContentProvider virtualContentProvider, PointInTime pointInTime) { _parent = parent; @@ -44,7 +46,7 @@ namespace FileTime.Core.Timeline public Task CreateElement(string name) => Task.FromResult((IElement)new TimeElement(name, this, Provider, VirtualProvider)); - public Task Delete() => Task.CompletedTask; + public Task Delete(bool hardDelete = false) => Task.CompletedTask; public async Task GetByPath(string path, bool acceptDeepestMatch = false) { diff --git a/src/Core/FileTime.Core/Timeline/TimeElement.cs b/src/Core/FileTime.Core/Timeline/TimeElement.cs index 1c90bda..dd8eef4 100644 --- a/src/Core/FileTime.Core/Timeline/TimeElement.cs +++ b/src/Core/FileTime.Core/Timeline/TimeElement.cs @@ -24,14 +24,14 @@ namespace FileTime.Core.Timeline public bool IsHidden => false; - public bool CanDelete => true; + public SupportsDelete CanDelete => SupportsDelete.True; public bool CanRename => true; public IContentProvider Provider { get; } public IContentProvider VirtualProvider { get; } - public Task Delete() => Task.CompletedTask; + public Task Delete(bool hardDelete = false) => Task.CompletedTask; public IContainer? GetParent() => _parent; diff --git a/src/Core/FileTime.Core/Timeline/TimeProvider.cs b/src/Core/FileTime.Core/Timeline/TimeProvider.cs index 75d05fc..53db1f8 100644 --- a/src/Core/FileTime.Core/Timeline/TimeProvider.cs +++ b/src/Core/FileTime.Core/Timeline/TimeProvider.cs @@ -18,7 +18,7 @@ namespace FileTime.Core.Timeline public bool IsHidden => false; - public bool CanDelete => false; + public SupportsDelete CanDelete => SupportsDelete.False; public bool CanRename => false; @@ -26,6 +26,8 @@ namespace FileTime.Core.Timeline public IReadOnlyList Exceptions { get; } = new List().AsReadOnly(); + public bool SupportsDirectoryLevelSoftDelete => false; + public TimeProvider(PointInTime pointInTime) { _pointInTime = pointInTime; @@ -48,7 +50,7 @@ namespace FileTime.Core.Timeline throw new NotImplementedException(); } - public Task Delete() => throw new NotSupportedException(); + public Task Delete(bool hardDelete = false) => throw new NotSupportedException(); public Task GetByPath(string path, bool acceptDeepestMatch = false) { diff --git a/src/GuiApp/Assets/filetime.xcf b/src/GuiApp/Assets/filetime.xcf index 635c492..2047c8b 100644 Binary files a/src/GuiApp/Assets/filetime.xcf and b/src/GuiApp/Assets/filetime.xcf differ diff --git a/src/GuiApp/FileTime.Avalonia/App.axaml b/src/GuiApp/FileTime.Avalonia/App.axaml index 6711d6c..b2ddf2a 100644 --- a/src/GuiApp/FileTime.Avalonia/App.axaml +++ b/src/GuiApp/FileTime.Avalonia/App.axaml @@ -9,7 +9,7 @@ - #073642 + #E7073642 #083e4c #D0083e4c @@ -36,8 +36,8 @@ - - + + + - + - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - + IsVisible="{Binding Inputs, Converter={StaticResource IsNotNullConverter}}"> + + + + - + + + + + + + + + + + + + + + + +