From 5905f62d66b3c52570725432795d3a1d75c98409 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=C3=81d=C3=A1m=20Kov=C3=A1cs?= Date: Tue, 22 Aug 2023 13:24:52 +0200 Subject: [PATCH] Decompress command --- .../LocalItemCreator.cs | 3 + .../CompressionUserCommandHandler.cs | 26 +++- .../DecompressCommand.cs | 131 ++++++++++++++++++ .../DecompressCommandFactory.cs | 32 +++++ .../DecompressUserCommand.cs | 15 ++ .../FileTime.Tools.Compression/Startup.cs | 2 + 6 files changed, 207 insertions(+), 2 deletions(-) create mode 100644 src/Tools/FileTime.Tools.Compression/DecompressCommand.cs create mode 100644 src/Tools/FileTime.Tools.Compression/DecompressCommandFactory.cs create mode 100644 src/Tools/FileTime.Tools.Compression/DecompressUserCommand.cs diff --git a/src/Providers/FileTime.Providers.Local/LocalItemCreator.cs b/src/Providers/FileTime.Providers.Local/LocalItemCreator.cs index 78dd70f..49a8f63 100644 --- a/src/Providers/FileTime.Providers.Local/LocalItemCreator.cs +++ b/src/Providers/FileTime.Providers.Local/LocalItemCreator.cs @@ -63,6 +63,9 @@ public class LocalItemCreator : ItemCreatorBase try { _logger.LogTrace("Trying to create element with path {Path}", path); + var directory = Path.GetDirectoryName(path); + if (directory is { } && !Directory.Exists(directory)) Directory.CreateDirectory(directory); + await using (File.Create(path)) { } diff --git a/src/Tools/FileTime.Tools.Compression/CompressionUserCommandHandler.cs b/src/Tools/FileTime.Tools.Compression/CompressionUserCommandHandler.cs index 3835ab3..a92de66 100644 --- a/src/Tools/FileTime.Tools.Compression/CompressionUserCommandHandler.cs +++ b/src/Tools/FileTime.Tools.Compression/CompressionUserCommandHandler.cs @@ -21,12 +21,34 @@ public class CompressionUserCommandHandler : AggregatedUserCommandHandler _markedItems = appState.SelectedTab.Map(t => t?.MarkedItems).Switch(); _selectedItem = appState.SelectedTab.Map(t => t?.CurrentSelectedItem).Switch(); - AddCommandHandler(new[] + AddCommandHandler(new IUserCommandHandler[] { - new TypeUserCommandHandler(Compress) + new TypeUserCommandHandler(Compress), + new TypeUserCommandHandler(Decompress) }); } + private Task Decompress() + { + _clipboardService.Clear(); + _clipboardService.SetCommand(); + + if (_markedItems.Value is {Count: > 0} markedItems) + { + foreach (var markedItem in markedItems) + { + _clipboardService.AddContent(markedItem); + } + } + else if (_selectedItem.Value?.BaseItem?.FullName is { } fullname) + { + //TODO: check if file is decompressable + _clipboardService.AddContent(fullname); + } + + return Task.CompletedTask; + } + private Task Compress() { _clipboardService.Clear(); diff --git a/src/Tools/FileTime.Tools.Compression/DecompressCommand.cs b/src/Tools/FileTime.Tools.Compression/DecompressCommand.cs new file mode 100644 index 0000000..d414b5a --- /dev/null +++ b/src/Tools/FileTime.Tools.Compression/DecompressCommand.cs @@ -0,0 +1,131 @@ +using FileTime.Core.Command; +using FileTime.Core.ContentAccess; +using FileTime.Core.Models; +using FileTime.Core.Timeline; +using SharpCompress.Archives; + +namespace FileTime.Tools.Compression; + +public class DecompressCommand : CommandBase, IExecutableCommand, ITransportationCommand, IDisposable +{ + private record ArchiveContext(IArchive Archive, string TargetContainerName); + + private readonly CancellationTokenSource _cancellationTokenSource = new(); + private readonly ITimelessContentProvider _timelessContentProvider; + private readonly IContentAccessorFactory _contentAccessorFactory; + private readonly ICommandSchedulerNotifier _commandSchedulerNotifier; + private readonly List _archives = new(); + private readonly List _disposables = new(); + + public IReadOnlyList Sources { get; } + public FullName Target { get; } + public TransportMode TransportMode { get; } + + internal DecompressCommand( + ITimelessContentProvider timelessContentProvider, + IContentAccessorFactory contentAccessorFactory, + ICommandSchedulerNotifier commandSchedulerNotifier, + IReadOnlyCollection sources, + TransportMode mode, + FullName targetFullName) + { + _timelessContentProvider = timelessContentProvider; + _contentAccessorFactory = contentAccessorFactory; + _commandSchedulerNotifier = commandSchedulerNotifier; + ArgumentNullException.ThrowIfNull(sources); + ArgumentNullException.ThrowIfNull(mode); + ArgumentNullException.ThrowIfNull(targetFullName); + + Sources = new List(sources).AsReadOnly(); + TransportMode = mode; + Target = targetFullName; + } + + + public override Task CanRun(PointInTime currentTime) + { + //TODO: + return Task.FromResult(CanCommandRun.True); + } + + public override Task SimulateCommand(PointInTime currentTime) + { + //TODO: + return Task.FromResult(currentTime); + } + + + public override void Cancel() + => _cancellationTokenSource.Cancel(); + + public async Task Execute() + { + if (_archives.Count == 0) await OpenArchivesAsync(); + + var resolvedTarget = (IContainer) await _timelessContentProvider.GetItemByFullNameAsync(Target, PointInTime.Present); + var itemCreator = _contentAccessorFactory.GetItemCreator(resolvedTarget.Provider); + var contentWriterFactory = _contentAccessorFactory.GetContentWriterFactory(resolvedTarget.Provider); + + foreach (var archiveContext in _archives) + { + await DecompressFile(archiveContext, resolvedTarget, contentWriterFactory, itemCreator); + } + } + + private async Task DecompressFile( + ArchiveContext archiveContext, + IContainer resolvedTarget, + IContentWriterFactory contentWriterFactory, + IItemCreator itemCreator) + { + var archive = archiveContext.Archive; + foreach (var archiveEntry in archive.Entries) + { + var subPath = string.Join(Constants.SeparatorChar, archiveEntry.Key.Split('\\')); + var entryPath = Target.GetChild(subPath); + + if (archiveEntry.IsDirectory) + { + await itemCreator.CreateContainerAsync(resolvedTarget.Provider, entryPath); + } + else + { + await itemCreator.CreateElementAsync(resolvedTarget.Provider, entryPath); + var newItem = (IElement) await _timelessContentProvider.GetItemByFullNameAsync(entryPath, PointInTime.Present); + using var writer = await contentWriterFactory.CreateContentWriterAsync(newItem); + + archiveEntry.WriteTo(writer.AsStream()); + } + } + } + + private async Task OpenArchivesAsync() + { + if (_archives.Count > 0) throw new InvalidOperationException("Archives are already open"); + foreach (var source in Sources) + { + var targetElement = (IElement) await _timelessContentProvider.GetItemByFullNameAsync(source, PointInTime.Present); + var contentReader = await _contentAccessorFactory.GetContentReaderFactory(targetElement.Provider).CreateContentReaderAsync(targetElement); + var contentReaderStream = contentReader.AsStream(); + _disposables.Add(contentReader); + using var archive = ArchiveFactory.Open(contentReaderStream); + + _archives.Add(new ArchiveContext(archive, GetFileName(source.GetName()))); + } + } + + public string GetFileName(string fullName) + { + var parts = fullName.Split('.'); + var fileName = string.Join('.', parts[..^1]); + return string.IsNullOrEmpty(fileName) ? fullName : fileName; + } + + public void Dispose() + { + foreach (var archive in _archives) + { + archive.Archive.Dispose(); + } + } +} \ No newline at end of file diff --git a/src/Tools/FileTime.Tools.Compression/DecompressCommandFactory.cs b/src/Tools/FileTime.Tools.Compression/DecompressCommandFactory.cs new file mode 100644 index 0000000..7ef92d8 --- /dev/null +++ b/src/Tools/FileTime.Tools.Compression/DecompressCommandFactory.cs @@ -0,0 +1,32 @@ +using FileTime.Core.Command; +using FileTime.Core.ContentAccess; +using FileTime.Core.Models; +using FileTime.Core.Timeline; + +namespace FileTime.Tools.Compression; + +public class DecompressCommandFactory : ITransportationCommandFactory +{ + private readonly ITimelessContentProvider _timelessContentProvider; + private readonly IContentAccessorFactory _contentAccessorFactory; + private readonly ICommandSchedulerNotifier _commandSchedulerNotifier; + + public DecompressCommandFactory( + ITimelessContentProvider timelessContentProvider, + IContentAccessorFactory contentAccessorFactory, + ICommandSchedulerNotifier commandSchedulerNotifier) + { + _timelessContentProvider = timelessContentProvider; + _contentAccessorFactory = contentAccessorFactory; + _commandSchedulerNotifier = commandSchedulerNotifier; + } + + public DecompressCommand GenerateCommand(IReadOnlyCollection sources, TransportMode mode, FullName targetFullName) + => new( + _timelessContentProvider, + _contentAccessorFactory, + _commandSchedulerNotifier, + sources, + mode, + targetFullName); +} \ No newline at end of file diff --git a/src/Tools/FileTime.Tools.Compression/DecompressUserCommand.cs b/src/Tools/FileTime.Tools.Compression/DecompressUserCommand.cs new file mode 100644 index 0000000..6c36700 --- /dev/null +++ b/src/Tools/FileTime.Tools.Compression/DecompressUserCommand.cs @@ -0,0 +1,15 @@ +using FileTime.App.Core.UserCommand; + +namespace FileTime.Tools.Compression; + +public class DecompressUserCommand : IIdentifiableUserCommand +{ + public const string CommandName = "decompress"; + public static readonly DecompressUserCommand Instance = new(); + private DecompressUserCommand() + { + } + + public string UserCommandID => CommandName; + public string Title => "Select for decompression"; +} \ No newline at end of file diff --git a/src/Tools/FileTime.Tools.Compression/Startup.cs b/src/Tools/FileTime.Tools.Compression/Startup.cs index 7f5cfb4..5abb312 100644 --- a/src/Tools/FileTime.Tools.Compression/Startup.cs +++ b/src/Tools/FileTime.Tools.Compression/Startup.cs @@ -8,6 +8,7 @@ public class StartupHandler : IStartupHandler public StartupHandler(IIdentifiableUserCommandService identifiableUserCommandService) { identifiableUserCommandService.AddIdentifiableUserCommand(CompressUserCommand.Instance); + identifiableUserCommandService.AddIdentifiableUserCommand(DecompressUserCommand.Instance); } public Task InitAsync() => Task.CompletedTask; } @@ -18,6 +19,7 @@ public static class Startup { services.AddSingleton(); services.AddSingleton(); + services.AddSingleton(); services.AddSingleton(); return services; }