From f809f0a640c3549cad1f1fc234bbc9ba17eb8235 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=C3=81d=C3=A1m=20Kov=C3=A1cs?= Date: Wed, 16 Feb 2022 00:12:09 +0100 Subject: [PATCH] StreamCopyCommandHandler, Async rename --- .../DependencyInjection.cs | 19 +++++- .../Application.CommandHandlers.cs | 6 +- .../FileTime.Core/Command/CanHandleCommand.cs | 9 +++ .../FileTime.Core/Command/CommandExecutor.cs | 17 ++++- src/Core/FileTime.Core/Command/CopyCommand.cs | 18 ++--- .../Command/CreateContainerCommand.cs | 10 +-- .../Command/CreateElementCommand.cs | 10 +-- .../FileTime.Core/Command/DeleteCommand.cs | 6 +- .../FileTime.Core/Command/RenameCommand.cs | 6 +- .../StreamCopyCommandHandler.cs | 52 ++++++++++++++ src/Core/FileTime.Core/Models/AbsolutePath.cs | 51 ++++++++------ .../FileTime.Core/Models/AbsolutePathType.cs | 9 +++ src/Core/FileTime.Core/Models/IContainer.cs | 10 +-- src/Core/FileTime.Core/Models/IElement.cs | 5 ++ .../FileTime.Core/Models/VirtualContainer.cs | 12 ++-- .../Providers/IContentProvider.cs | 2 + .../FileTime.Core/Providers/IContentReader.cs | 9 +++ .../FileTime.Core/Providers/IContentWriter.cs | 10 +++ .../FileTime.Core/Providers/TopContainer.cs | 10 +-- src/Core/FileTime.Core/Timeline/Difference.cs | 2 +- .../FileTime.Core/Timeline/TimeContainer.cs | 10 +-- .../FileTime.Core/Timeline/TimeElement.cs | 4 ++ .../FileTime.Core/Timeline/TimeProvider.cs | 21 ++++-- src/Core/FileTime.Core/Timeline/TimeRunner.cs | 8 ++- .../Application/TabContainer.cs | 4 +- .../Services/CommandHandlerService.cs | 4 +- .../CommandHandlers/CopyCommandHandler.cs | 49 -------------- .../LocalContentProvider.cs | 14 ++-- .../LocalContentReader.cs | 50 ++++++++++++++ .../LocalContentWriter.cs | 54 +++++++++++++++ .../FileTime.Providers.Local/LocalFile.cs | 5 ++ .../FileTime.Providers.Local/LocalFolder.cs | 10 +-- .../FileTime.Providers.Local/Startup.cs | 6 +- .../SmbContentProvider.cs | 13 ++-- .../SmbContentReader.cs | 67 +++++++++++++++++++ .../SmbContentWriter.cs | 24 +++++++ .../FileTime.Providers.Smb/SmbFile.cs | 40 ++++++++++- .../FileTime.Providers.Smb/SmbFolder.cs | 18 ++--- .../FileTime.Providers.Smb/SmbServer.cs | 10 +-- .../FileTime.Providers.Smb/SmbShare.cs | 26 ++++--- .../Command/CompressCommand.cs | 1 + 41 files changed, 529 insertions(+), 182 deletions(-) create mode 100644 src/Core/FileTime.Core/Command/CanHandleCommand.cs create mode 100644 src/Core/FileTime.Core/CommandHandlers/StreamCopyCommandHandler.cs create mode 100644 src/Core/FileTime.Core/Models/AbsolutePathType.cs create mode 100644 src/Core/FileTime.Core/Providers/IContentReader.cs create mode 100644 src/Core/FileTime.Core/Providers/IContentWriter.cs delete mode 100644 src/Providers/FileTime.Providers.Local/CommandHandlers/CopyCommandHandler.cs create mode 100644 src/Providers/FileTime.Providers.Local/LocalContentReader.cs create mode 100644 src/Providers/FileTime.Providers.Local/LocalContentWriter.cs create mode 100644 src/Providers/FileTime.Providers.Smb/SmbContentReader.cs create mode 100644 src/Providers/FileTime.Providers.Smb/SmbContentWriter.cs diff --git a/src/AppCommon/FileTime.App.DependencyInjection/DependencyInjection.cs b/src/AppCommon/FileTime.App.DependencyInjection/DependencyInjection.cs index 20f4e94..7e092e7 100644 --- a/src/AppCommon/FileTime.App.DependencyInjection/DependencyInjection.cs +++ b/src/AppCommon/FileTime.App.DependencyInjection/DependencyInjection.cs @@ -1,5 +1,6 @@ using FileTime.App.Core.Clipboard; using FileTime.Core.Command; +using FileTime.Core.CommandHandlers; using FileTime.Core.Providers; using FileTime.Core.Timeline; using FileTime.Providers.Local; @@ -21,7 +22,23 @@ namespace FileTime.App.Core .AddSingleton(sp => sp.GetService() ?? throw new Exception($"No {nameof(LocalContentProvider)} instance found")) .AddSingleton() .AddSingleton() - .AddSingleton(); + .AddSingleton() + .RegisterCommandHandlers(); + } + + internal static IServiceCollection RegisterCommandHandlers(this IServiceCollection serviceCollection) + { + var commandHandlers = new List() + { + typeof(StreamCopyCommandHandler) + }; + + foreach (var commandHandler in commandHandlers) + { + serviceCollection.AddTransient(typeof(ICommandHandler), commandHandler); + } + + return serviceCollection; } } } \ No newline at end of file diff --git a/src/ConsoleApp/FileTime.ConsoleUI.App/Application.CommandHandlers.cs b/src/ConsoleApp/FileTime.ConsoleUI.App/Application.CommandHandlers.cs index e7793e3..fbeb0ef 100644 --- a/src/ConsoleApp/FileTime.ConsoleUI.App/Application.CommandHandlers.cs +++ b/src/ConsoleApp/FileTime.ConsoleUI.App/Application.CommandHandlers.cs @@ -148,13 +148,13 @@ namespace FileTime.ConsoleUI.App if (!string.IsNullOrWhiteSpace(newContainerName)) { - await currentLocation.CreateContainer(newContainerName); + await currentLocation.CreateContainerAsync(newContainerName); } } async Task Validator(string newPath) { - if (await currentLocation.IsExists(newPath)) + if (await currentLocation.IsExistsAsync(newPath)) { _coloredConsoleRenderer.ForegroundColor = _styles.ErrorColor; } @@ -169,7 +169,7 @@ namespace FileTime.ConsoleUI.App { IList? itemsToDelete = null; - var currentSelectedItems = (await _tabStates[_selectedTab!].GetCurrentMarkedItems()).Select(p => p.Resolve()).ToList(); + var currentSelectedItems = (await _tabStates[_selectedTab!].GetCurrentMarkedItems()).Select(p => p.ResolveAsync()).ToList(); var currentSelectedItem = await _selectedTab?.GetCurrentSelectedItem(); if (currentSelectedItems.Count > 0) { diff --git a/src/Core/FileTime.Core/Command/CanHandleCommand.cs b/src/Core/FileTime.Core/Command/CanHandleCommand.cs new file mode 100644 index 0000000..4e39394 --- /dev/null +++ b/src/Core/FileTime.Core/Command/CanHandleCommand.cs @@ -0,0 +1,9 @@ +namespace FileTime.Core.Command +{ + public enum CanHandleCommand + { + CanHandle, + Fallback, + CanNotHandle + } +} \ No newline at end of file diff --git a/src/Core/FileTime.Core/Command/CommandExecutor.cs b/src/Core/FileTime.Core/Command/CommandExecutor.cs index 2488148..103ec8e 100644 --- a/src/Core/FileTime.Core/Command/CommandExecutor.cs +++ b/src/Core/FileTime.Core/Command/CommandExecutor.cs @@ -1,14 +1,19 @@ using FileTime.Core.Timeline; +using Microsoft.Extensions.Logging; namespace FileTime.Core.Command { public class CommandExecutor { private readonly List _commandHandlers; + private readonly ILogger _logger; - public CommandExecutor(IEnumerable commandHandlers) + public CommandExecutor( + IEnumerable commandHandlers, + ILogger logger) { _commandHandlers = commandHandlers.ToList(); + _logger = logger; } public async Task ExecuteCommandAsync(ICommand command, TimeRunner timeRunner) @@ -19,7 +24,15 @@ namespace FileTime.Core.Command } else { - await _commandHandlers.Find(c => c.CanHandle(command))?.ExecuteAsync(command, timeRunner); + var commandHandler = _commandHandlers.Find(c => c.CanHandle(command)); + if (commandHandler != null) + { + await commandHandler.ExecuteAsync(command, timeRunner); + } + else + { + _logger.LogError("No command handler for command {Command}", command.GetType().Name); + } } } } diff --git a/src/Core/FileTime.Core/Command/CopyCommand.cs b/src/Core/FileTime.Core/Command/CopyCommand.cs index 4d572ec..789d83e 100644 --- a/src/Core/FileTime.Core/Command/CopyCommand.cs +++ b/src/Core/FileTime.Core/Command/CopyCommand.cs @@ -62,7 +62,7 @@ namespace FileTime.Core.Command _copyOperation = (_, to, _, _) => { - var target = to.GetParentAsAbsolutePath().Resolve(); + var target = to.GetParent().ResolveAsync(); newDiffs.Add(new Difference( target is IElement ? DifferenceItemType.Element @@ -79,12 +79,12 @@ namespace FileTime.Core.Command var newContainerDiff = new Difference( DifferenceItemType.Container, DifferenceActionType.Create, - AbsolutePath.FromParentAndChildName(target, name) + AbsolutePath.FromParentAndChildName(target, name, AbsolutePathType.Container) ); newDiffs.Add(newContainerDiff); - return (IContainer)(await newContainerDiff.AbsolutePath.Resolve())!; + return (IContainer)(await newContainerDiff.AbsolutePath.ResolveAsync())!; }; await TraverseTree(Sources, Target, TransportMode.Value); @@ -110,7 +110,7 @@ namespace FileTime.Core.Command await UpdateProgress(); }; - _createContainer = async (IContainer target, string name) => await target.CreateContainer(name); + _createContainer = async (IContainer target, string name) => await target.CreateContainerAsync(name); _containerCopyDone = async (path) => { foreach (var item in _operationStatuses[path]) @@ -137,7 +137,7 @@ namespace FileTime.Core.Command _copyOperation = async (from, to, _, _) => { - var parentPath = to.GetParentAsAbsolutePath(); + var parentPath = to.GetParent(); List operationsByFolder; if (operationStatuses.ContainsKey(parentPath)) { @@ -145,7 +145,7 @@ namespace FileTime.Core.Command } else { - var resolvedFrom = await from.Resolve(); + var resolvedFrom = await from.ResolveAsync(); operationsByFolder = new List(); operationStatuses.Add(parentPath, operationsByFolder); operationsByFolder.Add(new OperationProgress(from.Path, resolvedFrom is IElement element ? await element.GetElementSize() : 0L)); @@ -166,7 +166,7 @@ namespace FileTime.Core.Command foreach (var source in sources) { - var item = await source.Resolve(); + var item = await source.ResolveAsync(); if (item is IContainer container) { @@ -182,7 +182,7 @@ namespace FileTime.Core.Command { var targetName = element.Name; - var targetNameExists = await target.IsExists(targetName); + var targetNameExists = await target.IsExistsAsync(targetName); if (transportMode == Command.TransportMode.Merge) { for (var i = 0; targetNameExists; i++) @@ -197,7 +197,7 @@ namespace FileTime.Core.Command OperationProgress? operation = null; var targetFolderPath = new AbsolutePath(target); - var targetElementPath = AbsolutePath.FromParentAndChildName(target, targetName); + var targetElementPath = AbsolutePath.FromParentAndChildName(target, targetName, AbsolutePathType.Element); foreach(var asd in _operationStatuses.Keys) { diff --git a/src/Core/FileTime.Core/Command/CreateContainerCommand.cs b/src/Core/FileTime.Core/Command/CreateContainerCommand.cs index 83698de..b70feb0 100644 --- a/src/Core/FileTime.Core/Command/CreateContainerCommand.cs +++ b/src/Core/FileTime.Core/Command/CreateContainerCommand.cs @@ -26,10 +26,10 @@ namespace FileTime.Core.Command public async Task Execute(TimeRunner timeRunner) { - var possibleContainer = await Container.Resolve(); + var possibleContainer = await Container.ResolveAsync(); if (possibleContainer is IContainer container) { - await container.CreateContainer(NewContainerName); + await container.CreateContainerAsync(NewContainerName); await timeRunner.RefreshContainer.InvokeAsync(this, new AbsolutePath(container)); } //TODO: else @@ -39,18 +39,18 @@ namespace FileTime.Core.Command { var newDifferences = new List() { - new Difference(DifferenceItemType.Container, DifferenceActionType.Create, new AbsolutePath(Container.ContentProvider, Container.Path + Constants.SeparatorChar + NewContainerName, Container.VirtualContentProvider)) + new Difference(DifferenceItemType.Container, DifferenceActionType.Create, Container.GetChild(NewContainerName, AbsolutePathType.Container)) }; return Task.FromResult(startPoint.WithDifferences(newDifferences)); } public async Task CanRun(PointInTime startPoint) { - var resolvedContainer = await Container.Resolve(); + var resolvedContainer = await Container.ResolveAsync(); if (resolvedContainer == null) return CanCommandRun.Forceable; if (resolvedContainer is not IContainer container - || await container.IsExists(NewContainerName)) + || await container.IsExistsAsync(NewContainerName)) { return CanCommandRun.False; } diff --git a/src/Core/FileTime.Core/Command/CreateElementCommand.cs b/src/Core/FileTime.Core/Command/CreateElementCommand.cs index fc1298a..422d051 100644 --- a/src/Core/FileTime.Core/Command/CreateElementCommand.cs +++ b/src/Core/FileTime.Core/Command/CreateElementCommand.cs @@ -24,10 +24,10 @@ namespace FileTime.Core.Command public async Task Execute(TimeRunner timeRunner) { - var possibleContainer = await Container.Resolve(); + var possibleContainer = await Container.ResolveAsync(); if (possibleContainer is IContainer container) { - await container.CreateElement(NewElementName); + await container.CreateElementAsync(NewElementName); await timeRunner.RefreshContainer.InvokeAsync(this, new AbsolutePath(container)); } } @@ -36,18 +36,18 @@ namespace FileTime.Core.Command { var newDifferences = new List() { - new Difference(DifferenceItemType.Element, DifferenceActionType.Create, new AbsolutePath(Container.ContentProvider, Container.Path + Constants.SeparatorChar + NewElementName, Container.VirtualContentProvider)) + new Difference(DifferenceItemType.Element, DifferenceActionType.Create, Container.GetChild(NewElementName, AbsolutePathType.Element)) }; return Task.FromResult(startPoint.WithDifferences(newDifferences)); } public async Task CanRun(PointInTime startPoint) { - var resolvedContainer = Container.Resolve(); + var resolvedContainer = Container.ResolveAsync(); if (resolvedContainer == null) return CanCommandRun.Forceable; if (resolvedContainer is not IContainer container - || await container.IsExists(NewElementName)) + || await container.IsExistsAsync(NewElementName)) { return CanCommandRun.False; } diff --git a/src/Core/FileTime.Core/Command/DeleteCommand.cs b/src/Core/FileTime.Core/Command/DeleteCommand.cs index d65d820..301f16c 100644 --- a/src/Core/FileTime.Core/Command/DeleteCommand.cs +++ b/src/Core/FileTime.Core/Command/DeleteCommand.cs @@ -47,7 +47,7 @@ namespace FileTime.Core.Command foreach (var item in ItemsToDelete) { - await TraverseTree((await item.Resolve())!); + await TraverseTree((await item.ResolveAsync())!); } return startPoint.WithDifferences(newDifferences); @@ -64,7 +64,7 @@ namespace FileTime.Core.Command foreach (var item in ItemsToDelete) { - await TraverseTree((await item.Resolve())!); + await TraverseTree((await item.ResolveAsync())!); } } @@ -97,7 +97,7 @@ namespace FileTime.Core.Command var result = CanCommandRun.True; foreach (var itemPath in ItemsToDelete) { - var resolvedItem = await itemPath.Resolve(); + var resolvedItem = await itemPath.ResolveAsync(); if (resolvedItem != null && ( resolvedItem.CanDelete == SupportsDelete.False diff --git a/src/Core/FileTime.Core/Command/RenameCommand.cs b/src/Core/FileTime.Core/Command/RenameCommand.cs index 6354ba9..1d7a1c6 100644 --- a/src/Core/FileTime.Core/Command/RenameCommand.cs +++ b/src/Core/FileTime.Core/Command/RenameCommand.cs @@ -24,7 +24,7 @@ namespace FileTime.Core.Command public async Task Execute(TimeRunner timeRunner) { - var itemToRename = await Source.Resolve(); + var itemToRename = await Source.ResolveAsync(); if (itemToRename != null) { await itemToRename.Rename(Target); @@ -34,7 +34,7 @@ namespace FileTime.Core.Command public async Task SimulateCommand(PointInTime startPoint) { - var item = await Source.Resolve(); + var item = await Source.ResolveAsync(); if (item == null) throw new FileNotFoundException(); var newDifferences = new List() { @@ -50,7 +50,7 @@ namespace FileTime.Core.Command public Task CanRun(PointInTime startPoint) { - return Task.FromResult(Source.Resolve() != null ? CanCommandRun.True : CanCommandRun.False); + return Task.FromResult(Source.ResolveAsync() != null ? CanCommandRun.True : CanCommandRun.False); } } } \ No newline at end of file diff --git a/src/Core/FileTime.Core/CommandHandlers/StreamCopyCommandHandler.cs b/src/Core/FileTime.Core/CommandHandlers/StreamCopyCommandHandler.cs new file mode 100644 index 0000000..d09e137 --- /dev/null +++ b/src/Core/FileTime.Core/CommandHandlers/StreamCopyCommandHandler.cs @@ -0,0 +1,52 @@ +using FileTime.Core.Command; +using FileTime.Core.Models; +using FileTime.Core.Timeline; + +namespace FileTime.Core.CommandHandlers +{ + public class StreamCopyCommandHandler : ICommandHandler + { + public bool CanHandle(object command) + { + if (command is not CopyCommand copyCommand) return false; + + return (copyCommand.Target?.Provider.SupportsContentStreams ?? false) + && copyCommand.Sources.All(p => p.ContentProvider.SupportsContentStreams); + } + + public async Task ExecuteAsync(object command, TimeRunner timeRunner) + { + if (command is not CopyCommand copyCommand) throw new ArgumentException($"Can not execute command of type '{command.GetType()}'."); + + await copyCommand.Execute(CopyElement, timeRunner); + } + + public static async Task CopyElement(AbsolutePath sourcePath, AbsolutePath targetPath, OperationProgress? operationProgress, CopyCommandContext copyCommandContext) + { + var parent = (IContainer?)(await targetPath.GetParent().ResolveAsync())!; + var elementName = targetPath.GetName(); + if (!await parent.IsExistsAsync(elementName)) + { + await parent.CreateElementAsync(elementName); + } + + var source = (IElement?)(await sourcePath.ResolveAsync())!; + var target = (IElement?)(await targetPath.ResolveAsync())!; + + using var reader = await source.GetContentReaderAsync(); + using var writer = await target.GetContentWriterAsync(); + + byte[] dataRead; + + do + { + dataRead = await reader.ReadBytesAsync(writer.PreferredBufferSize); + await writer.WriteBytesAsync(dataRead); + await writer.FlushAsync(); + if (operationProgress != null) operationProgress.Progress += dataRead.LongLength; + await copyCommandContext.UpdateProgress(); + } + while (dataRead.Length > 0); + } + } +} \ No newline at end of file diff --git a/src/Core/FileTime.Core/Models/AbsolutePath.cs b/src/Core/FileTime.Core/Models/AbsolutePath.cs index 4599ea6..523ed3f 100644 --- a/src/Core/FileTime.Core/Models/AbsolutePath.cs +++ b/src/Core/FileTime.Core/Models/AbsolutePath.cs @@ -9,19 +9,22 @@ namespace FileTime.Core.Models public IContentProvider? VirtualContentProvider { get; } public string Path { get; } + public AbsolutePathType Type { get; } public AbsolutePath(AbsolutePath from) { ContentProvider = from.ContentProvider; Path = from.Path; VirtualContentProvider = from.VirtualContentProvider; + Type = from.Type; } - public AbsolutePath(IContentProvider contentProvider, string path, IContentProvider? virtualContentProvider) + public AbsolutePath(IContentProvider contentProvider, string path, AbsolutePathType type, IContentProvider? virtualContentProvider) { ContentProvider = contentProvider; Path = path; VirtualContentProvider = virtualContentProvider; + Type = type; } public AbsolutePath(IItem item) @@ -43,33 +46,39 @@ namespace FileTime.Core.Models ContentProvider = item.Provider; Path = item.FullName!; } + + Type = item switch + { + IContainer => AbsolutePathType.Container, + IElement => AbsolutePathType.Element, + _ => AbsolutePathType.Unknown + }; } - public static AbsolutePath FromParentAndChildName(IContainer parent, string childName) + public static AbsolutePath FromParentAndChildName(IContainer parent, string childName, AbsolutePathType childType) { - IContentProvider? contentProvider; - IContentProvider? virtualContentProvider; - string? path; + var contentProvider = parent.Provider; + var path = parent.FullName! + Constants.SeparatorChar + childName; - if (parent is TimeContainer timeContainer) + var virtualContentProvider = parent switch { - contentProvider = timeContainer.Provider; - virtualContentProvider = timeContainer.VirtualProvider; - path = timeContainer.FullName! + Constants.SeparatorChar + childName; - } - else - { - contentProvider = parent.Provider; - path = parent.FullName! + Constants.SeparatorChar + childName; - virtualContentProvider = null; - } + TimeContainer timeContainer => timeContainer.VirtualProvider, + _ => null + }; - return new AbsolutePath(contentProvider, path, virtualContentProvider); + return new AbsolutePath(contentProvider, path, childType, virtualContentProvider); } - public async Task Resolve() + public AbsolutePath GetChild(string childName, AbsolutePathType childType) { - var result = VirtualContentProvider != null && (await VirtualContentProvider.IsExists(Path)) + var path = Path + Constants.SeparatorChar + childName; + + return new AbsolutePath(ContentProvider, path, childType, VirtualContentProvider); + } + + public async Task ResolveAsync() + { + var result = VirtualContentProvider != null && (await VirtualContentProvider.IsExistsAsync(Path)) ? await VirtualContentProvider.GetByPath(Path) : null; @@ -78,13 +87,13 @@ namespace FileTime.Core.Models return result; } - public string GetParent() + public string GetParentPath() { var pathParts = Path.Split(Constants.SeparatorChar); return string.Join(Constants.SeparatorChar, pathParts[..^1]); } - public AbsolutePath GetParentAsAbsolutePath() => new(ContentProvider, GetParent(), VirtualContentProvider); + public AbsolutePath GetParent() => new(ContentProvider, GetParentPath(), AbsolutePathType.Container, VirtualContentProvider); public string GetName() => Path.Split(Constants.SeparatorChar).Last(); diff --git a/src/Core/FileTime.Core/Models/AbsolutePathType.cs b/src/Core/FileTime.Core/Models/AbsolutePathType.cs new file mode 100644 index 0000000..33fd53a --- /dev/null +++ b/src/Core/FileTime.Core/Models/AbsolutePathType.cs @@ -0,0 +1,9 @@ +namespace FileTime.Core.Models +{ + public enum AbsolutePathType + { + Unknown, + Container, + Element + } +} \ No newline at end of file diff --git a/src/Core/FileTime.Core/Models/IContainer.cs b/src/Core/FileTime.Core/Models/IContainer.cs index f1231ea..d835e9d 100644 --- a/src/Core/FileTime.Core/Models/IContainer.cs +++ b/src/Core/FileTime.Core/Models/IContainer.cs @@ -30,13 +30,13 @@ namespace FileTime.Core.Models return null; } - Task CreateContainer(string name); - Task CreateElement(string name); + Task CreateContainerAsync(string name); + Task CreateElementAsync(string name); - Task IsExists(string name); + Task IsExistsAsync(string name); - Task Clone(); - Task CanOpen(); + Task CloneAsync(); + Task CanOpenAsync(); void Unload(); bool IsLoaded { get; } diff --git a/src/Core/FileTime.Core/Models/IElement.cs b/src/Core/FileTime.Core/Models/IElement.cs index cc3c0b2..5b04156 100644 --- a/src/Core/FileTime.Core/Models/IElement.cs +++ b/src/Core/FileTime.Core/Models/IElement.cs @@ -1,3 +1,5 @@ +using FileTime.Core.Providers; + namespace FileTime.Core.Models { public interface IElement : IItem @@ -6,5 +8,8 @@ namespace FileTime.Core.Models string GetPrimaryAttributeText(); Task GetContent(CancellationToken token = default); Task GetElementSize(CancellationToken token = default); + + Task GetContentReaderAsync(); + Task GetContentWriterAsync(); } } \ 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 ef5dfec..082f1c7 100644 --- a/src/Core/FileTime.Core/Models/VirtualContainer.cs +++ b/src/Core/FileTime.Core/Models/VirtualContainer.cs @@ -139,9 +139,9 @@ namespace FileTime.Core.Models : baseContainer; } - public async Task CreateContainer(string name) => await BaseContainer.CreateContainer(name); - public async Task CreateElement(string name) => await BaseContainer.CreateElement(name); - public async Task IsExists(string name) => await BaseContainer.IsExists(name); + public async Task CreateContainerAsync(string name) => await BaseContainer.CreateContainerAsync(name); + public async Task CreateElementAsync(string name) => await BaseContainer.CreateElementAsync(name); + public async Task IsExistsAsync(string name) => await BaseContainer.IsExistsAsync(name); public Task?> GetItems(CancellationToken token = default) { @@ -157,10 +157,10 @@ namespace FileTime.Core.Models } public async Task Delete(bool hardDelete = false) => await BaseContainer.Delete(); - public async Task Clone() + public async Task CloneAsync() { return new VirtualContainer( - await BaseContainer.Clone(), + await BaseContainer.CloneAsync(), _containerTransformators, _elementTransformators, IsPermanent, @@ -170,7 +170,7 @@ namespace FileTime.Core.Models } public async Task Rename(string newName) => await BaseContainer.Rename(newName); - public async Task CanOpen() => await BaseContainer.CanOpen(); + public async Task CanOpenAsync() => await BaseContainer.CanOpenAsync(); public void Destroy() { diff --git a/src/Core/FileTime.Core/Providers/IContentProvider.cs b/src/Core/FileTime.Core/Providers/IContentProvider.cs index 7e8b570..9086d17 100644 --- a/src/Core/FileTime.Core/Providers/IContentProvider.cs +++ b/src/Core/FileTime.Core/Providers/IContentProvider.cs @@ -4,6 +4,8 @@ namespace FileTime.Core.Providers { public interface IContentProvider : IContainer { + bool SupportsContentStreams { get; } + Task> GetRootContainers(CancellationToken token = default); bool CanHandlePath(string path); diff --git a/src/Core/FileTime.Core/Providers/IContentReader.cs b/src/Core/FileTime.Core/Providers/IContentReader.cs new file mode 100644 index 0000000..bf06fcb --- /dev/null +++ b/src/Core/FileTime.Core/Providers/IContentReader.cs @@ -0,0 +1,9 @@ +namespace FileTime.Core.Providers +{ + public interface IContentReader : IDisposable + { + int PreferredBufferSize { get; } + + Task ReadBytesAsync(int bufferSize); + } +} \ No newline at end of file diff --git a/src/Core/FileTime.Core/Providers/IContentWriter.cs b/src/Core/FileTime.Core/Providers/IContentWriter.cs new file mode 100644 index 0000000..f9448e4 --- /dev/null +++ b/src/Core/FileTime.Core/Providers/IContentWriter.cs @@ -0,0 +1,10 @@ +namespace FileTime.Core.Providers +{ + public interface IContentWriter : IDisposable + { + int PreferredBufferSize { get; } + + Task WriteBytesAsync(byte[] data); + Task FlushAsync(); + } +} \ No newline at end of file diff --git a/src/Core/FileTime.Core/Providers/TopContainer.cs b/src/Core/FileTime.Core/Providers/TopContainer.cs index 0111907..937225e 100644 --- a/src/Core/FileTime.Core/Providers/TopContainer.cs +++ b/src/Core/FileTime.Core/Providers/TopContainer.cs @@ -48,9 +48,9 @@ namespace FileTime.Core.Providers } } - public Task CreateContainer(string name) => throw new NotImplementedException(); + public Task CreateContainerAsync(string name) => throw new NotImplementedException(); - public Task CreateElement(string name) => throw new NotImplementedException(); + public Task CreateElementAsync(string name) => throw new NotImplementedException(); public Task Delete(bool hardDelete = false) => throw new NotImplementedException(); @@ -58,7 +58,7 @@ namespace FileTime.Core.Providers public IContainer? GetParent() => null; - public Task IsExists(string name) => throw new NotImplementedException(); + public Task IsExistsAsync(string name) => throw new NotImplementedException(); public async Task RefreshAsync(CancellationToken token = default) => await Refreshed.InvokeAsync(this, AsyncEventArgs.Empty, token); @@ -66,11 +66,11 @@ namespace FileTime.Core.Providers public Task?> GetContainers(CancellationToken token = default) => Task.FromResult(_containers); public Task?> GetElements(CancellationToken token = default) => Task.FromResult(_elements); - public Task Clone() => Task.FromResult((IContainer)this); + public Task CloneAsync() => Task.FromResult((IContainer)this); public Task Rename(string newName) => throw new NotSupportedException(); - public Task CanOpen() => Task.FromResult(true); + public Task CanOpenAsync() => Task.FromResult(true); public void Destroy() { } diff --git a/src/Core/FileTime.Core/Timeline/Difference.cs b/src/Core/FileTime.Core/Timeline/Difference.cs index f96a1e4..242de65 100644 --- a/src/Core/FileTime.Core/Timeline/Difference.cs +++ b/src/Core/FileTime.Core/Timeline/Difference.cs @@ -24,7 +24,7 @@ namespace FileTime.Core.Timeline return new Difference( Type, Action, - new AbsolutePath(AbsolutePath.ContentProvider, AbsolutePath.Path, virtualContentProvider) + new AbsolutePath(AbsolutePath.ContentProvider, AbsolutePath.Path, AbsolutePath.Type, virtualContentProvider) ); } } diff --git a/src/Core/FileTime.Core/Timeline/TimeContainer.cs b/src/Core/FileTime.Core/Timeline/TimeContainer.cs index 6338af6..a73555c 100644 --- a/src/Core/FileTime.Core/Timeline/TimeContainer.cs +++ b/src/Core/FileTime.Core/Timeline/TimeContainer.cs @@ -45,11 +45,11 @@ namespace FileTime.Core.Timeline FullName = parent?.FullName == null ? Name : parent.FullName + Constants.SeparatorChar + Name; } - public async Task Clone() => new TimeContainer(Name, await _parent!.Clone(), Provider, VirtualProvider, _pointInTime); + public async Task CloneAsync() => new TimeContainer(Name, await _parent!.CloneAsync(), Provider, VirtualProvider, _pointInTime); - public Task CreateContainer(string name) => Task.FromResult((IContainer)new TimeContainer(name, this, Provider, VirtualProvider, _pointInTime)); + public Task CreateContainerAsync(string name) => Task.FromResult((IContainer)new TimeContainer(name, this, Provider, VirtualProvider, _pointInTime)); - public Task CreateElement(string name) => Task.FromResult((IElement)new TimeElement(name, this, Provider, VirtualProvider)); + public Task CreateElementAsync(string name) => Task.FromResult((IElement)new TimeElement(name, this, Provider, VirtualProvider)); public Task Delete(bool hardDelete = false) => Task.CompletedTask; @@ -106,7 +106,7 @@ namespace FileTime.Core.Timeline public IContainer? GetParent() => _parent; - public async Task IsExists(string name) => (await GetItems())?.Any(i => i.Name == name) ?? false; + public async Task IsExistsAsync(string name) => (await GetItems())?.Any(i => i.Name == name) ?? false; public async Task RefreshAsync(CancellationToken token = default) => await Refreshed.InvokeAsync(this, AsyncEventArgs.Empty, token); @@ -125,7 +125,7 @@ namespace FileTime.Core.Timeline if (elementDiff.Type != DifferenceItemType.Container) throw new ArgumentException($"{elementDiff}'s {nameof(Difference.Type)} property is not {DifferenceItemType.Element}."); return new TimeElement(elementDiff.Name, this, Provider, elementDiff.AbsolutePath.VirtualContentProvider ?? elementDiff.AbsolutePath.ContentProvider); } - public Task CanOpen() => Task.FromResult(true); + public Task CanOpenAsync() => Task.FromResult(true); public void Destroy() => IsDestroyed = true; public void Unload() { } diff --git a/src/Core/FileTime.Core/Timeline/TimeElement.cs b/src/Core/FileTime.Core/Timeline/TimeElement.cs index 6ec4d6a..2771fbe 100644 --- a/src/Core/FileTime.Core/Timeline/TimeElement.cs +++ b/src/Core/FileTime.Core/Timeline/TimeElement.cs @@ -48,5 +48,9 @@ namespace FileTime.Core.Timeline public Task GetElementSize(CancellationToken token = default) => Task.FromResult(-1L); public void Destroy() => IsDestroyed = true; + + public Task GetContentReaderAsync() => throw new NotSupportedException(); + + public Task GetContentWriterAsync() => throw new NotSupportedException(); } } \ No newline at end of file diff --git a/src/Core/FileTime.Core/Timeline/TimeProvider.cs b/src/Core/FileTime.Core/Timeline/TimeProvider.cs index 8ae9303..a6ae7c3 100644 --- a/src/Core/FileTime.Core/Timeline/TimeProvider.cs +++ b/src/Core/FileTime.Core/Timeline/TimeProvider.cs @@ -30,6 +30,7 @@ namespace FileTime.Core.Timeline public bool SupportsDirectoryLevelSoftDelete => false; public bool IsDestroyed => false; + public bool SupportsContentStreams => false; public TimeProvider(PointInTime pointInTime) { @@ -41,14 +42,14 @@ namespace FileTime.Core.Timeline throw new NotImplementedException(); } - public Task Clone() => Task.FromResult((IContainer)this); + public Task CloneAsync() => Task.FromResult((IContainer)this); - public Task CreateContainer(string name) + public Task CreateContainerAsync(string name) { throw new NotImplementedException(); } - public Task CreateElement(string name) + public Task CreateElementAsync(string name) { throw new NotImplementedException(); } @@ -82,7 +83,7 @@ namespace FileTime.Core.Timeline throw new NotImplementedException(); } - public Task IsExists(string name) + public Task IsExistsAsync(string name) { throw new NotImplementedException(); } @@ -92,10 +93,20 @@ namespace FileTime.Core.Timeline public Task Rename(string newName) => throw new NotSupportedException(); public void SetParent(IContainer container) { } - public Task CanOpen() => Task.FromResult(true); + public Task CanOpenAsync() => Task.FromResult(true); public void Destroy() { } public void Unload() { } + + public Task GetContentReaderAsync(IElement element) + { + throw new NotSupportedException(); + } + + public Task GetContentWriterAsync(IElement element) + { + throw new NotSupportedException(); + } } } \ No newline at end of file diff --git a/src/Core/FileTime.Core/Timeline/TimeRunner.cs b/src/Core/FileTime.Core/Timeline/TimeRunner.cs index 717647b..9b8528d 100644 --- a/src/Core/FileTime.Core/Timeline/TimeRunner.cs +++ b/src/Core/FileTime.Core/Timeline/TimeRunner.cs @@ -139,6 +139,10 @@ namespace FileTime.Core.Timeline _commandExecutor.ExecuteCommandAsync(commandToRun.Command, this).Wait(); } } + catch(Exception e) + { + _logger.LogError(e, "Error while running command: {CommandType} ({Command}).", commandToRun?.Command.GetType().Name, commandToRun?.Command.DisplayLabel); + } finally { DisposeCommandThread(Thread.CurrentThread, commandToRun).Wait(); @@ -151,11 +155,11 @@ namespace FileTime.Core.Timeline { if (command != null) { - _logger.LogDebug("Command finished running: {0}", command.Command.DisplayLabel); + _logger.LogDebug("Command finished running: {Command}", command.Command.DisplayLabel); _commandsToRun[0].Remove(command); if (_commandsToRun[0].Commands.Count == 0) { - _logger.LogDebug("Removing empty command array. {0} batch left.", _commandsToRun.Count - 1); + _logger.LogDebug("Removing empty command array. {RemainingBatchNumber} batch left.", _commandsToRun.Count - 1); _commandsToRun.RemoveAt(0); } } diff --git a/src/GuiApp/FileTime.Avalonia/Application/TabContainer.cs b/src/GuiApp/FileTime.Avalonia/Application/TabContainer.cs index 4007ac7..0b0d01f 100644 --- a/src/GuiApp/FileTime.Avalonia/Application/TabContainer.cs +++ b/src/GuiApp/FileTime.Avalonia/Application/TabContainer.cs @@ -364,7 +364,7 @@ namespace FileTime.Avalonia.Application var currentLocation = await Tab.GetCurrentLocation(); if (currentLocation != null) { - await currentLocation.CreateContainer(name); + await currentLocation.CreateContainerAsync(name); } }); } @@ -376,7 +376,7 @@ namespace FileTime.Avalonia.Application var currentLocation = await Tab.GetCurrentLocation(); if (currentLocation != null) { - await currentLocation.CreateElement(name); + await currentLocation.CreateElementAsync(name); } }); } diff --git a/src/GuiApp/FileTime.Avalonia/Services/CommandHandlerService.cs b/src/GuiApp/FileTime.Avalonia/Services/CommandHandlerService.cs index 437ff8e..730171a 100644 --- a/src/GuiApp/FileTime.Avalonia/Services/CommandHandlerService.cs +++ b/src/GuiApp/FileTime.Avalonia/Services/CommandHandlerService.cs @@ -219,7 +219,7 @@ namespace FileTime.Avalonia.Services } else if (tabContainer == null) { - var newContainer = await _appState.SelectedTab.CurrentLocation.Container.Clone(); + var newContainer = await _appState.SelectedTab.CurrentLocation.Container.CloneAsync(); var newTab = new Tab(); await newTab.Init(newContainer); @@ -351,7 +351,7 @@ namespace FileTime.Avalonia.Services //FIXME: check 'is Container' if (currentSelectedItems.Count == 1) { - if ((await currentSelectedItems[0].Resolve()) is IContainer container + if ((await currentSelectedItems[0].ResolveAsync()) is IContainer container && (await container.GetItems())?.Count > 0) { askForDelete = true; diff --git a/src/Providers/FileTime.Providers.Local/CommandHandlers/CopyCommandHandler.cs b/src/Providers/FileTime.Providers.Local/CommandHandlers/CopyCommandHandler.cs deleted file mode 100644 index 42fabfd..0000000 --- a/src/Providers/FileTime.Providers.Local/CommandHandlers/CopyCommandHandler.cs +++ /dev/null @@ -1,49 +0,0 @@ -using FileTime.Core.Command; -using FileTime.Core.Models; -using FileTime.Core.Timeline; - -namespace FileTime.Providers.Local.CommandHandlers -{ - public class CopyCommandHandler : ICommandHandler - { - public bool CanHandle(object command) - { - if (command is not CopyCommand copyCommand) return false; - - if (copyCommand.Target != null && copyCommand.Target is not LocalFolder) return false; - - if (copyCommand.Sources.Any(s => s.ContentProvider is not LocalContentProvider)) return false; - - return true; - } - - public async Task ExecuteAsync(object command, TimeRunner timeRunner) - { - if (command is not CopyCommand copyCommand) throw new ArgumentException($"Can not execute command of type '{command.GetType()}'."); - - await copyCommand.Execute(CopyElement, timeRunner); - } - - public static async Task CopyElement(AbsolutePath sourcePath, AbsolutePath targetPath, OperationProgress? operationProgress, CopyCommandContext copyCommandContext) - { - using var sourceStream = File.OpenRead(sourcePath.Path); - using var sourceReader = new BinaryReader(sourceStream); - - using var targetStream = File.OpenWrite(targetPath.Path); - using var targetWriter = new BinaryWriter(targetStream); - - const int bufferSize = 1024 * 1024; - byte[] dataRead; - - do - { - dataRead = sourceReader.ReadBytes(bufferSize); - targetWriter.Write(dataRead); - targetWriter.Flush(); - if (operationProgress != null) operationProgress.Progress += dataRead.LongLength; - await copyCommandContext.UpdateProgress(); - } - while (dataRead.Length > 0); - } - } -} \ No newline at end of file diff --git a/src/Providers/FileTime.Providers.Local/LocalContentProvider.cs b/src/Providers/FileTime.Providers.Local/LocalContentProvider.cs index 450fcd6..0d2371a 100644 --- a/src/Providers/FileTime.Providers.Local/LocalContentProvider.cs +++ b/src/Providers/FileTime.Providers.Local/LocalContentProvider.cs @@ -1,3 +1,4 @@ +using System.Threading.Tasks; using System; using System.Runtime.InteropServices; using AsyncEvent; @@ -35,6 +36,7 @@ namespace FileTime.Providers.Local public bool SupportsDirectoryLevelSoftDelete => false; public bool IsDestroyed => false; + public bool SupportsContentStreams => true; public LocalContentProvider(ILogger logger) { @@ -64,7 +66,7 @@ namespace FileTime.Providers.Local if (rootContainer == null) { - _logger.LogWarning("No root container found with name '{0}'", path[0]); + _logger.LogWarning("No root container found with name '{RootContainerName}'", path[0]); return null; } @@ -74,12 +76,12 @@ namespace FileTime.Providers.Local public async Task RefreshAsync(CancellationToken token = default) => await Refreshed.InvokeAsync(this, AsyncEventArgs.Empty, token); - public Task Clone() => Task.FromResult((IContainer)this); + public Task CloneAsync() => Task.FromResult((IContainer)this); public IContainer? GetParent() => _parent; - public Task CreateContainer(string name) => throw new NotSupportedException(); - public Task CreateElement(string name) => throw new NotSupportedException(); - public Task IsExists(string name) => Task.FromResult(_rootContainers.Any(i => i.Name == name)); + public Task CreateContainerAsync(string name) => throw new NotSupportedException(); + public Task CreateElementAsync(string name) => throw new NotSupportedException(); + public Task IsExistsAsync(string name) => Task.FromResult(_rootContainers.Any(i => i.Name == name)); public Task Delete(bool hardDelete = false) => throw new NotSupportedException(); @@ -102,7 +104,7 @@ namespace FileTime.Providers.Local public Task?> GetElements(CancellationToken token = default) => Task.FromResult(_elements); public Task Rename(string newName) => throw new NotSupportedException(); - public Task CanOpen() => Task.FromResult(true); + public Task CanOpenAsync() => Task.FromResult(true); public void Destroy() { diff --git a/src/Providers/FileTime.Providers.Local/LocalContentReader.cs b/src/Providers/FileTime.Providers.Local/LocalContentReader.cs new file mode 100644 index 0000000..698392a --- /dev/null +++ b/src/Providers/FileTime.Providers.Local/LocalContentReader.cs @@ -0,0 +1,50 @@ +using FileTime.Core.Providers; + +namespace FileTime.Providers.Local +{ + public class LocalContentReader : IContentReader + { + private readonly FileStream _readerStream; + private readonly BinaryReader _binaryReader; + private bool disposed; + + public int PreferredBufferSize => 1024 * 1024; + + public LocalContentReader(FileStream readerStream) + { + _readerStream = readerStream; + _binaryReader = new BinaryReader(_readerStream); + } + + public Task ReadBytesAsync(int bufferSize) + { + var max = bufferSize > 0 && bufferSize < PreferredBufferSize ? bufferSize : PreferredBufferSize; + + return Task.FromResult(_binaryReader.ReadBytes(max)); + } + + ~LocalContentReader() + { + Dispose(false); + } + + public void Dispose() + { + Dispose(true); + GC.SuppressFinalize(this); + } + + private void Dispose(bool disposing) + { + if (!disposed) + { + if (disposing) + { + _readerStream.Dispose(); + _binaryReader.Dispose(); + } + } + disposed = true; + } + } +} \ No newline at end of file diff --git a/src/Providers/FileTime.Providers.Local/LocalContentWriter.cs b/src/Providers/FileTime.Providers.Local/LocalContentWriter.cs new file mode 100644 index 0000000..0bd8762 --- /dev/null +++ b/src/Providers/FileTime.Providers.Local/LocalContentWriter.cs @@ -0,0 +1,54 @@ +using FileTime.Core.Providers; + +namespace FileTime.Providers.Local +{ + public class LocalContentWriter : IContentWriter + { + private readonly FileStream _writerStream; + private readonly BinaryWriter _binaryWriter; + private bool disposed; + public int PreferredBufferSize => 1024 * 1024; + + public LocalContentWriter(FileStream writerStream) + { + _writerStream = writerStream; + _binaryWriter = new BinaryWriter(_writerStream); + } + + public Task WriteBytesAsync(byte[] data) + { + _binaryWriter.Write(data); + return Task.CompletedTask; + } + + public Task FlushAsync() + { + _binaryWriter.Flush(); + return Task.CompletedTask; + } + + ~LocalContentWriter() + { + Dispose(false); + } + + public void Dispose() + { + Dispose(true); + GC.SuppressFinalize(this); + } + + private void Dispose(bool disposing) + { + if (!disposed) + { + if (disposing) + { + _writerStream.Dispose(); + _binaryWriter.Dispose(); + } + } + disposed = true; + } + } +} \ No newline at end of file diff --git a/src/Providers/FileTime.Providers.Local/LocalFile.cs b/src/Providers/FileTime.Providers.Local/LocalFile.cs index 02404f6..b7165f9 100644 --- a/src/Providers/FileTime.Providers.Local/LocalFile.cs +++ b/src/Providers/FileTime.Providers.Local/LocalFile.cs @@ -88,5 +88,10 @@ namespace FileTime.Providers.Local public Task GetElementSize(CancellationToken token = default) => Task.FromResult(File.Length); public void Destroy() => IsDestroyed = true; + + public Task GetContentReaderAsync() => + Task.FromResult((IContentReader)new LocalContentReader(System.IO.File.OpenRead(File.FullName))); + public Task GetContentWriterAsync() => + Task.FromResult((IContentWriter)new LocalContentWriter(System.IO.File.OpenWrite(File.FullName))); } } \ No newline at end of file diff --git a/src/Providers/FileTime.Providers.Local/LocalFolder.cs b/src/Providers/FileTime.Providers.Local/LocalFolder.cs index 29d65bb..e6b3a3e 100644 --- a/src/Providers/FileTime.Providers.Local/LocalFolder.cs +++ b/src/Providers/FileTime.Providers.Local/LocalFolder.cs @@ -60,7 +60,7 @@ namespace FileTime.Providers.Local public IContainer? GetParent() => _parent; - public Task Clone() => Task.FromResult((IContainer)new LocalFolder(Directory, Provider, _parent)); + public Task CloneAsync() => Task.FromResult((IContainer)new LocalFolder(Directory, Provider, _parent)); public async Task RefreshAsync(CancellationToken token = default) { @@ -131,7 +131,7 @@ namespace FileTime.Providers.Local return null; } - public async Task CreateContainer(string name) + public async Task CreateContainerAsync(string name) { Directory.CreateSubdirectory(name); await RefreshAsync(); @@ -139,7 +139,7 @@ namespace FileTime.Providers.Local return _containers!.FirstOrDefault(c => Provider.NormalizePath(c.Name) == Provider.NormalizePath(name))!; } - public async Task CreateElement(string name) + public async Task CreateElementAsync(string name) { using (File.Create(Path.Combine(Directory.FullName, name))) { } await RefreshAsync(); @@ -147,7 +147,7 @@ namespace FileTime.Providers.Local return _elements!.FirstOrDefault(e => Provider.NormalizePath(e.Name) == Provider.NormalizePath(name))!; } - public async Task IsExists(string name) => (await GetItems())?.Any(i => Provider.NormalizePath(i.Name) == Provider.NormalizePath(name)) ?? false; + public async Task IsExistsAsync(string name) => (await GetItems())?.Any(i => Provider.NormalizePath(i.Name) == Provider.NormalizePath(name)) ?? false; public Task Delete(bool hardDelete = false) { @@ -185,7 +185,7 @@ namespace FileTime.Providers.Local + ((Directory.Attributes & FileAttributes.System) == FileAttributes.System ? "s" : "-"); } } - public Task CanOpen() => Task.FromResult(true); + public Task CanOpenAsync() => Task.FromResult(true); public void Destroy() { diff --git a/src/Providers/FileTime.Providers.Local/Startup.cs b/src/Providers/FileTime.Providers.Local/Startup.cs index c20c2b4..3e2e652 100644 --- a/src/Providers/FileTime.Providers.Local/Startup.cs +++ b/src/Providers/FileTime.Providers.Local/Startup.cs @@ -1,14 +1,10 @@ -using FileTime.Providers.Local.CommandHandlers; - namespace FileTime.Providers.Local { public static class Startup { public static Type[] GetCommandHandlers() { - return new Type[]{ - typeof(CopyCommandHandler) - }; + return Array.Empty(); } } } \ No newline at end of file diff --git a/src/Providers/FileTime.Providers.Smb/SmbContentProvider.cs b/src/Providers/FileTime.Providers.Smb/SmbContentProvider.cs index 565e3d5..a695fe6 100644 --- a/src/Providers/FileTime.Providers.Smb/SmbContentProvider.cs +++ b/src/Providers/FileTime.Providers.Smb/SmbContentProvider.cs @@ -9,7 +9,7 @@ namespace FileTime.Providers.Smb { public class SmbContentProvider : IContentProvider { - private readonly object _initializationGuard = new object(); + private readonly object _initializationGuard = new(); private bool _initialized; private bool _initializing; private IContainer? _parent; @@ -39,6 +39,7 @@ namespace FileTime.Providers.Smb public bool SupportsDirectoryLevelSoftDelete => false; public bool IsDestroyed => false; + public bool SupportsContentStreams => true; public SmbContentProvider(IInputInterface inputInterface, Persistence.PersistenceService persistenceService, ILogger logger) { @@ -50,7 +51,7 @@ namespace FileTime.Providers.Smb _logger = logger; } - public async Task CreateContainer(string name) + public async Task CreateContainerAsync(string name) { var fullName = "\\\\" + name; var container = _rootContainers.Find(c => c.Name == name); @@ -69,7 +70,7 @@ namespace FileTime.Providers.Smb return container; } - public Task CreateElement(string name) + public Task CreateElementAsync(string name) { throw new NotSupportedException(); } @@ -98,9 +99,9 @@ namespace FileTime.Providers.Smb public IContainer? GetParent() => _parent; - public Task Clone() => Task.FromResult((IContainer)this); + public Task CloneAsync() => Task.FromResult((IContainer)this); - public async Task IsExists(string name) => (await GetItems())?.Any(i => i.Name == name) ?? false; + public async Task IsExistsAsync(string name) => (await GetItems())?.Any(i => i.Name == name) ?? false; public async Task RefreshAsync(CancellationToken token = default) => await Refreshed.InvokeAsync(this, AsyncEventArgs.Empty, token); @@ -124,7 +125,7 @@ namespace FileTime.Providers.Smb public Task?> GetElements(CancellationToken token = default) => Task.FromResult((IReadOnlyList?)_elements); public Task Rename(string newName) => throw new NotSupportedException(); - public Task CanOpen() => Task.FromResult(true); + public Task CanOpenAsync() => Task.FromResult(true); public void Destroy() { } diff --git a/src/Providers/FileTime.Providers.Smb/SmbContentReader.cs b/src/Providers/FileTime.Providers.Smb/SmbContentReader.cs new file mode 100644 index 0000000..f1f39e5 --- /dev/null +++ b/src/Providers/FileTime.Providers.Smb/SmbContentReader.cs @@ -0,0 +1,67 @@ +using FileTime.Core.Providers; +using SMBLibrary; +using SMBLibrary.Client; + +namespace FileTime.Providers.Smb +{ + public class SmbContentReader : IContentReader + { + private readonly ISMBFileStore _smbFileStore; + private readonly object _fileHandle; + private readonly ISMBClient _client; + private bool disposed; + private long _bytesRead; + + public int PreferredBufferSize => (int)_client.MaxReadSize; + + public SmbContentReader(ISMBFileStore smbFileStore, object fileHandle, ISMBClient client) + { + _smbFileStore = smbFileStore; + _fileHandle = fileHandle; + _client = client; + } + + public Task ReadBytesAsync(int bufferSize) + { + var max = bufferSize > 0 && bufferSize < (int)_client.MaxReadSize ? bufferSize : (int)_client.MaxReadSize; + + var status = _smbFileStore.ReadFile(out byte[] data, _fileHandle, _bytesRead, max); + if (status != NTStatus.STATUS_SUCCESS && status != NTStatus.STATUS_END_OF_FILE) + { + throw new Exception("Failed to read from file"); + } + + if (status == NTStatus.STATUS_END_OF_FILE || data.Length == 0) + { + return Task.FromResult(Array.Empty()); + } + _bytesRead += data.Length; + + return Task.FromResult(data); + } + + ~SmbContentReader() + { + Dispose(false); + } + + public void Dispose() + { + Dispose(true); + GC.SuppressFinalize(this); + } + + private void Dispose(bool disposing) + { + if (!disposed) + { + if (disposing) + { + _smbFileStore.CloseFile(_fileHandle); + _smbFileStore.Disconnect(); + } + } + disposed = true; + } + } +} \ No newline at end of file diff --git a/src/Providers/FileTime.Providers.Smb/SmbContentWriter.cs b/src/Providers/FileTime.Providers.Smb/SmbContentWriter.cs new file mode 100644 index 0000000..490fd21 --- /dev/null +++ b/src/Providers/FileTime.Providers.Smb/SmbContentWriter.cs @@ -0,0 +1,24 @@ +using FileTime.Core.Providers; + +namespace FileTime.Providers.Smb +{ + public class SmbContentWriter : IContentWriter + { + public int PreferredBufferSize => throw new NotImplementedException(); + + public void Dispose() + { + throw new NotImplementedException(); + } + + public Task FlushAsync() + { + throw new NotImplementedException(); + } + + public Task WriteBytesAsync(byte[] data) + { + throw new NotImplementedException(); + } + } +} \ No newline at end of file diff --git a/src/Providers/FileTime.Providers.Smb/SmbFile.cs b/src/Providers/FileTime.Providers.Smb/SmbFile.cs index 3ddf774..e8944f7 100644 --- a/src/Providers/FileTime.Providers.Smb/SmbFile.cs +++ b/src/Providers/FileTime.Providers.Smb/SmbFile.cs @@ -1,10 +1,15 @@ using FileTime.Core.Models; using FileTime.Core.Providers; +using SMBLibrary; namespace FileTime.Providers.Smb { public class SmbFile : IElement { + private readonly IContainer _parent; + private readonly SmbClientContext _smbClientContext; + private readonly SmbShare _smbShare; + public bool IsSpecial => false; public string Name { get; } @@ -17,11 +22,10 @@ namespace FileTime.Providers.Smb public bool CanRename => true; public IContentProvider Provider { get; } - private IContainer _parent; public bool IsDestroyed { get; private set; } - public SmbFile(string name, SmbContentProvider provider, IContainer parent) + public SmbFile(string name, SmbContentProvider provider, SmbShare smbShare, IContainer parent, SmbClientContext smbClientContext) { Name = name; FullName = parent.FullName + Constants.SeparatorChar + Name; @@ -29,6 +33,8 @@ namespace FileTime.Providers.Smb Provider = provider; _parent = parent; + _smbClientContext = smbClientContext; + _smbShare = smbShare; } public Task Delete(bool hardDelete = false) @@ -50,5 +56,35 @@ namespace FileTime.Providers.Smb public Task GetElementSize(CancellationToken token = default) => Task.FromResult(-1L); public void Destroy() => IsDestroyed = true; + + public async Task GetContentReaderAsync() + { + return await _smbClientContext.RunWithSmbClientAsync(client => + { + NTStatus status = NTStatus.STATUS_DATA_ERROR; + var fileStore = _smbShare.TreeConnect(client, out status); + + if (status != NTStatus.STATUS_SUCCESS) + { + throw new Exception($"Could not open file {NativePath} for read."); + } + + var path = NativePath!; + path = path[(_parent.NativePath!.Length + 1)..]; + status = fileStore.CreateFile(out object fileHandle, out FileStatus fileStatus, path, AccessMask.GENERIC_READ | AccessMask.SYNCHRONIZE, SMBLibrary.FileAttributes.Normal, ShareAccess.Read, CreateDisposition.FILE_OPEN, CreateOptions.FILE_NON_DIRECTORY_FILE | CreateOptions.FILE_SYNCHRONOUS_IO_ALERT, null); + + if (status != NTStatus.STATUS_SUCCESS) + { + throw new Exception($"Could not open file {NativePath} for read."); + } + + return new SmbContentReader(fileStore, fileHandle, client); + }); + } + + public Task GetContentWriterAsync() + { + throw new NotImplementedException(); + } } } \ No newline at end of file diff --git a/src/Providers/FileTime.Providers.Smb/SmbFolder.cs b/src/Providers/FileTime.Providers.Smb/SmbFolder.cs index 0d5e272..603f9c8 100644 --- a/src/Providers/FileTime.Providers.Smb/SmbFolder.cs +++ b/src/Providers/FileTime.Providers.Smb/SmbFolder.cs @@ -9,7 +9,6 @@ namespace FileTime.Providers.Smb private IReadOnlyList? _items; private IReadOnlyList? _containers; private IReadOnlyList? _elements; - private readonly SmbShare _smbShare; private readonly IContainer? _parent; public string Name { get; } @@ -22,6 +21,7 @@ namespace FileTime.Providers.Smb public SmbContentProvider Provider { get; } IContentProvider IItem.Provider => Provider; + public SmbShare SmbShare { get; } public SupportsDelete CanDelete => SupportsDelete.True; public bool CanRename => true; @@ -35,7 +35,7 @@ namespace FileTime.Providers.Smb public SmbFolder(string name, SmbContentProvider contentProvider, SmbShare smbShare, IContainer parent) { _parent = parent; - _smbShare = smbShare; + SmbShare = smbShare; Name = name; FullName = parent?.FullName == null ? Name : parent.FullName + Constants.SeparatorChar + Name; @@ -43,21 +43,21 @@ namespace FileTime.Providers.Smb Provider = contentProvider; } - public Task CreateContainer(string name) + public Task CreateContainerAsync(string name) { throw new NotImplementedException(); } - public Task CreateElement(string name) + public Task CreateElementAsync(string name) { throw new NotImplementedException(); } - public Task Clone() => Task.FromResult((IContainer)this); + public Task CloneAsync() => Task.FromResult((IContainer)this); public IContainer? GetParent() => _parent; - public Task IsExists(string name) + public Task IsExistsAsync(string name) { throw new NotImplementedException(); } @@ -78,8 +78,8 @@ namespace FileTime.Providers.Smb try { - var path = FullName![(_smbShare.FullName!.Length + 1)..]; - (containers, elements) = await _smbShare.ListFolder(this, _smbShare.Name, path, token); + var path = FullName![(SmbShare.FullName!.Length + 1)..]; + (containers, elements) = await SmbShare.ListFolder(this, path, token); } catch { } @@ -113,7 +113,7 @@ namespace FileTime.Providers.Smb if (_elements == null) await RefreshAsync(token); return _elements; } - public Task CanOpen() => Task.FromResult(true); + public Task CanOpenAsync() => Task.FromResult(true); public void Destroy() => IsDestroyed = true; diff --git a/src/Providers/FileTime.Providers.Smb/SmbServer.cs b/src/Providers/FileTime.Providers.Smb/SmbServer.cs index bd816ab..eb69f9e 100644 --- a/src/Providers/FileTime.Providers.Smb/SmbServer.cs +++ b/src/Providers/FileTime.Providers.Smb/SmbServer.cs @@ -69,12 +69,12 @@ namespace FileTime.Providers.Smb return Task.FromResult(_elements); } - public Task CreateContainer(string name) + public Task CreateContainerAsync(string name) { throw new NotSupportedException(); } - public Task CreateElement(string name) + public Task CreateElementAsync(string name) { throw new NotSupportedException(); } @@ -106,7 +106,7 @@ namespace FileTime.Providers.Smb public IContainer? GetParent() => Provider; - public Task IsExists(string name) + public Task IsExistsAsync(string name) { throw new NotImplementedException(); } @@ -120,7 +120,7 @@ namespace FileTime.Providers.Smb await Refreshed.InvokeAsync(this, AsyncEventArgs.Empty, token); } - public Task Clone() => Task.FromResult((IContainer)this); + public Task CloneAsync() => Task.FromResult((IContainer)this); private void DisposeSmbClient() { @@ -211,7 +211,7 @@ namespace FileTime.Providers.Smb } public Task Rename(string newName) => throw new NotSupportedException(); - public Task CanOpen() => Task.FromResult(true); + public Task CanOpenAsync() => Task.FromResult(true); public void Destroy() { } diff --git a/src/Providers/FileTime.Providers.Smb/SmbShare.cs b/src/Providers/FileTime.Providers.Smb/SmbShare.cs index b63d25a..6875f0d 100644 --- a/src/Providers/FileTime.Providers.Smb/SmbShare.cs +++ b/src/Providers/FileTime.Providers.Smb/SmbShare.cs @@ -61,12 +61,12 @@ namespace FileTime.Providers.Smb return _elements; } - public Task CreateContainer(string name) + public Task CreateContainerAsync(string name) { throw new NotImplementedException(); } - public Task CreateElement(string name) + public Task CreateElementAsync(string name) { throw new NotImplementedException(); } @@ -78,9 +78,9 @@ namespace FileTime.Providers.Smb public IContainer? GetParent() => _parent; - public Task Clone() => Task.FromResult((IContainer)this); + public Task CloneAsync() => Task.FromResult((IContainer)this); - public Task IsExists(string name) + public Task IsExistsAsync(string name) { throw new NotImplementedException(); } @@ -92,7 +92,7 @@ namespace FileTime.Providers.Smb try { - (containers, elements) = await ListFolder(this, Name, string.Empty, token); + (containers, elements) = await ListFolder(this, string.Empty, token); } catch { } @@ -111,14 +111,15 @@ namespace FileTime.Providers.Smb await Refreshed.InvokeAsync(this, AsyncEventArgs.Empty, token); } - public async Task<(List containers, List elements)> ListFolder(IContainer parent, string shareName, string folderName, CancellationToken token = default) + public async Task<(List containers, List elements)> ListFolder(IContainer parent, string folderName, CancellationToken token = default) { return await _smbClientContext.RunWithSmbClientAsync(client => { var containers = new List(); var elements = new List(); - NTStatus status = NTStatus.STATUS_DATA_ERROR; - ISMBFileStore fileStore = client.TreeConnect(shareName, out status); + var status = NTStatus.STATUS_DATA_ERROR; + var fileStore = TreeConnect(client, out status); + if (status == NTStatus.STATUS_SUCCESS) { status = fileStore.CreateFile(out object directoryHandle, out FileStatus fileStatus, folderName, AccessMask.GENERIC_READ, SMBLibrary.FileAttributes.Directory, ShareAccess.Read | ShareAccess.Write, CreateDisposition.FILE_OPEN, CreateOptions.FILE_DIRECTORY_FILE, null); @@ -137,7 +138,7 @@ namespace FileTime.Providers.Smb } else { - elements.Add(new SmbFile(fileDirectoryInformation.FileName, Provider, parent)); + elements.Add(new SmbFile(fileDirectoryInformation.FileName, Provider, this, parent, _smbClientContext)); } } } @@ -151,8 +152,13 @@ namespace FileTime.Providers.Smb }); } + internal ISMBFileStore TreeConnect(ISMBClient client, out NTStatus status) + { + return client.TreeConnect(Name, out status); + } + public Task Rename(string newName) => throw new NotSupportedException(); - public Task CanOpen() => Task.FromResult(true); + public Task CanOpenAsync() => Task.FromResult(true); public void Destroy() { } diff --git a/src/Tools/FileTime.Tools.Compression/Command/CompressCommand.cs b/src/Tools/FileTime.Tools.Compression/Command/CompressCommand.cs index 520c6cc..b0a2e2f 100644 --- a/src/Tools/FileTime.Tools.Compression/Command/CompressCommand.cs +++ b/src/Tools/FileTime.Tools.Compression/Command/CompressCommand.cs @@ -13,6 +13,7 @@ namespace FileTime.Tools.Compression.Command public IReadOnlyList CanRunMessages { get; } = new List().AsReadOnly(); public int Progress { get; } + public int CurrentProgress { get; } public AsyncEventHandler ProgressChanged { get; } = new AsyncEventHandler();