diff --git a/src/AppCommon/FileTime.App.Core/Command/Commands.cs b/src/AppCommon/FileTime.App.Core/Command/Commands.cs index dcc0dd5..1a74b40 100644 --- a/src/AppCommon/FileTime.App.Core/Command/Commands.cs +++ b/src/AppCommon/FileTime.App.Core/Command/Commands.cs @@ -7,6 +7,7 @@ namespace FileTime.App.Core.Command AutoRefresh, ChangeTimelineMode, CloseTab, + Compress, Copy, CopyHash, CopyPath, diff --git a/src/AppCommon/FileTime.App.DependencyInjection/FileTime.App.DependencyInjection.csproj b/src/AppCommon/FileTime.App.DependencyInjection/FileTime.App.DependencyInjection.csproj index 3d30e02..0cacd89 100644 --- a/src/AppCommon/FileTime.App.DependencyInjection/FileTime.App.DependencyInjection.csproj +++ b/src/AppCommon/FileTime.App.DependencyInjection/FileTime.App.DependencyInjection.csproj @@ -9,6 +9,7 @@ + diff --git a/src/Core/FileTime.Core/Command/CopyCommand.cs b/src/Core/FileTime.Core/Command/CopyCommand.cs index 1bfe3a4..85bb7f7 100644 --- a/src/Core/FileTime.Core/Command/CopyCommand.cs +++ b/src/Core/FileTime.Core/Command/CopyCommand.cs @@ -1,4 +1,5 @@ using AsyncEvent; +using FileTime.Core.Interactions; using FileTime.Core.Models; using FileTime.Core.Timeline; @@ -14,7 +15,7 @@ namespace FileTime.Core.Command public IList Sources { get; } = new List(); - public IContainer? Target { get; set; } + public AbsolutePath? Target { get; set; } public TransportMode? TransportMode { get; set; } = Command.TransportMode.Merge; @@ -25,6 +26,9 @@ namespace FileTime.Core.Command public string DisplayLabel { get; } = "Copy"; public IReadOnlyList CanRunMessages { get; } = new List().AsReadOnly(); + public bool TargetIsContainer => true; + public List Inputs { get; } = new(); + public List? InputResults { get; set; } private async Task UpdateProgress() { @@ -85,7 +89,8 @@ namespace FileTime.Core.Command return (IContainer)(await newContainerDiff.AbsolutePath.ResolveAsync())!; }; - await TraverseTree(Sources, Target, TransportMode.Value); + var resolvedTarget = (IContainer)(await Target.ResolveAsync())!; + await TraverseTree(Sources, resolvedTarget, TransportMode.Value); return startPoint.WithDifferences(newDiffs); } @@ -122,7 +127,8 @@ namespace FileTime.Core.Command } }; - await TraverseTree(Sources, Target, TransportMode.Value); + var resolvedTarget = (IContainer)(await Target.ResolveAsync())!; + await TraverseTree(Sources, resolvedTarget, TransportMode.Value); } private async Task CalculateProgress() @@ -150,7 +156,8 @@ namespace FileTime.Core.Command } }; - await TraverseTree(Sources, Target, TransportMode.Value); + var resolvedTarget = (IContainer)(await Target.ResolveAsync())!; + await TraverseTree(Sources, resolvedTarget, TransportMode.Value); _operationStatuses = operationStatuses; } @@ -198,7 +205,7 @@ namespace FileTime.Core.Command var targetFolderPath = new AbsolutePath(target); var targetElementPath = AbsolutePath.FromParentAndChildName(target, targetName, AbsolutePathType.Element); - foreach(var asd in _operationStatuses.Keys) + foreach (var asd in _operationStatuses.Keys) { var hash1 = asd.GetHashCode(); var hash2 = targetFolderPath.GetHashCode(); diff --git a/src/Core/FileTime.Core/Command/ITransportationCommand.cs b/src/Core/FileTime.Core/Command/ITransportationCommand.cs index cbf5721..32696a8 100644 --- a/src/Core/FileTime.Core/Command/ITransportationCommand.cs +++ b/src/Core/FileTime.Core/Command/ITransportationCommand.cs @@ -1,3 +1,4 @@ +using FileTime.Core.Interactions; using FileTime.Core.Models; namespace FileTime.Core.Command @@ -5,7 +6,10 @@ namespace FileTime.Core.Command public interface ITransportationCommand : ICommand { IList Sources { get; } - IContainer? Target { get; set;} + AbsolutePath? Target { get; set; } TransportMode? TransportMode { get; set; } + bool TargetIsContainer { get; } + List Inputs { get; } + List? InputResults { get; set; } } } \ No newline at end of file diff --git a/src/Core/FileTime.Core/Command/MoveCommand.cs b/src/Core/FileTime.Core/Command/MoveCommand.cs index 389ffd6..fe9a537 100644 --- a/src/Core/FileTime.Core/Command/MoveCommand.cs +++ b/src/Core/FileTime.Core/Command/MoveCommand.cs @@ -1,4 +1,5 @@ using AsyncEvent; +using FileTime.Core.Interactions; using FileTime.Core.Models; using FileTime.Core.Timeline; @@ -8,7 +9,7 @@ namespace FileTime.Core.Command { public IList Sources { get; } = new List(); - public IContainer? Target { get; set; } + public AbsolutePath? Target { get; set; } public TransportMode? TransportMode { get; set; } = Command.TransportMode.Merge; public int Progress => 100; @@ -16,6 +17,10 @@ namespace FileTime.Core.Command public AsyncEventHandler ProgressChanged { get; } = new(); public string DisplayLabel { get; } = "MoveCommand"; public IReadOnlyList CanRunMessages { get; } = new List().AsReadOnly(); + public bool TargetIsContainer => true; + + public List Inputs { get; } = new(); + public List? InputResults { get; set; } public Task CanRun(PointInTime startPoint) { diff --git a/src/Core/FileTime.Core/Command/RenameCommand.cs b/src/Core/FileTime.Core/Command/RenameCommand.cs index b7a161c..18f27f1 100644 --- a/src/Core/FileTime.Core/Command/RenameCommand.cs +++ b/src/Core/FileTime.Core/Command/RenameCommand.cs @@ -1,5 +1,6 @@ using AsyncEvent; using FileTime.Core.Extensions; +using FileTime.Core.Interactions; using FileTime.Core.Models; using FileTime.Core.Timeline; @@ -28,7 +29,7 @@ namespace FileTime.Core.Command if (itemToRename != null) { await itemToRename.Rename(Target); - if(timeRunner.RefreshContainer != null) await timeRunner.RefreshContainer.InvokeAsync(this, new AbsolutePath(itemToRename.GetParent()!)); + if (timeRunner.RefreshContainer != null) await timeRunner.RefreshContainer.InvokeAsync(this, new AbsolutePath(itemToRename.GetParent()!)); } } diff --git a/src/Core/FileTime.Core/CommandHandlers/StreamCopyCommandHandler.cs b/src/Core/FileTime.Core/CommandHandlers/StreamCopyCommandHandler.cs index 7263c03..3af426c 100644 --- a/src/Core/FileTime.Core/CommandHandlers/StreamCopyCommandHandler.cs +++ b/src/Core/FileTime.Core/CommandHandlers/StreamCopyCommandHandler.cs @@ -10,7 +10,7 @@ namespace FileTime.Core.CommandHandlers { if (command is not CopyCommand copyCommand) return false; - return (copyCommand.Target?.Provider.SupportsContentStreams ?? false) + return (copyCommand.Target?.ContentProvider.SupportsContentStreams ?? false) && copyCommand.Sources.All(p => p.ContentProvider.SupportsContentStreams); } diff --git a/src/Core/FileTime.Core/Interactions/InputElement.cs b/src/Core/FileTime.Core/Interactions/InputElement.cs index bcdd7ab..66857be 100644 --- a/src/Core/FileTime.Core/Interactions/InputElement.cs +++ b/src/Core/FileTime.Core/Interactions/InputElement.cs @@ -31,9 +31,9 @@ namespace FileTime.Core.Interactions return new InputElement(label, InputType.Password, defaultValue); } - public static InputElement ForOptions(string label, List defaultValue) + public static InputElement ForOptions(string label, IEnumerable defaultValue) { - return new InputElement(label, InputType.Options, defaultValue); + return new InputElement(label, InputType.Options, new List(defaultValue)); } } } \ No newline at end of file diff --git a/src/Core/FileTime.Core/Providers/ContentProviderStream.cs b/src/Core/FileTime.Core/Providers/ContentProviderStream.cs index 25127ff..67b597f 100644 --- a/src/Core/FileTime.Core/Providers/ContentProviderStream.cs +++ b/src/Core/FileTime.Core/Providers/ContentProviderStream.cs @@ -1,16 +1,14 @@ -using System.Threading.Tasks; namespace FileTime.Core.Providers { public class ContentProviderStream : Stream { - private readonly IContentReader? _contentReader; private readonly IContentWriter? _contentWriter; - public override bool CanRead => _contentReader == null; + public override bool CanRead => _contentReader != null; - public override bool CanSeek => false; + public override bool CanSeek => _contentReader != null; - public override bool CanWrite => _contentWriter == null; + public override bool CanWrite => _contentWriter != null; public override long Length => throw new NotImplementedException(); @@ -28,7 +26,8 @@ namespace FileTime.Core.Providers public override void Flush() { - throw new NotImplementedException(); + if (_contentWriter == null) throw new NotSupportedException(); + Task.Run(async () => await _contentWriter.FlushAsync()).Wait(); } public override int Read(byte[] buffer, int offset, int count) @@ -45,7 +44,16 @@ namespace FileTime.Core.Providers public override long Seek(long offset, SeekOrigin origin) { - throw new NotImplementedException(); + if (_contentReader == null) throw new NotSupportedException(); + + var newPosition = origin switch + { + SeekOrigin.Begin => offset, + SeekOrigin.Current => _contentReader.Position ?? 0 + offset, + _ => throw new NotSupportedException() + }; + _contentReader.SetPosition(newPosition); + return newPosition; } public override void SetLength(long value) @@ -55,7 +63,14 @@ namespace FileTime.Core.Providers public override void Write(byte[] buffer, int offset, int count) { - throw new NotImplementedException(); + if (_contentWriter == null) throw new NotSupportedException(); + var data = buffer; + if (buffer.Length != count) + { + data = new byte[count]; + Array.Copy(buffer, data, count); + } + Task.Run(async () => await _contentWriter.WriteBytesAsync(data, offset)).Wait(); } } } \ No newline at end of file diff --git a/src/Core/FileTime.Core/Providers/IContentReader.cs b/src/Core/FileTime.Core/Providers/IContentReader.cs index c77c31e..4f2632d 100644 --- a/src/Core/FileTime.Core/Providers/IContentReader.cs +++ b/src/Core/FileTime.Core/Providers/IContentReader.cs @@ -3,7 +3,9 @@ namespace FileTime.Core.Providers public interface IContentReader : IDisposable { int PreferredBufferSize { get; } + long? Position { get; } Task ReadBytesAsync(int bufferSize, int? offset = null); + void SetPosition(long position); } } \ No newline at end of file diff --git a/src/Core/FileTime.Core/Providers/IContentWriter.cs b/src/Core/FileTime.Core/Providers/IContentWriter.cs index f9448e4..808377a 100644 --- a/src/Core/FileTime.Core/Providers/IContentWriter.cs +++ b/src/Core/FileTime.Core/Providers/IContentWriter.cs @@ -4,7 +4,7 @@ namespace FileTime.Core.Providers { int PreferredBufferSize { get; } - Task WriteBytesAsync(byte[] data); + Task WriteBytesAsync(byte[] data, int? index = null); Task FlushAsync(); } } \ 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 c83702f..ca65eb7 100644 --- a/src/Core/FileTime.Core/Timeline/TimeRunner.cs +++ b/src/Core/FileTime.Core/Timeline/TimeRunner.cs @@ -151,11 +151,11 @@ namespace FileTime.Core.Timeline } finally { - Task.Run(async () => await DisposeCommandThread(Thread.CurrentThread, commandToRun)).Wait(); + Task.Run(async () => await DisposeCommandThread(commandToRun)).Wait(); } } - private async Task DisposeCommandThread(Thread thread, CommandTimeState? command) + private async Task DisposeCommandThread(CommandTimeState? command) { await RunWithLockAsync(async () => { @@ -170,7 +170,7 @@ namespace FileTime.Core.Timeline } } - var currentCommandRunner = _commandRunners.Find(r => r.Thread == thread); + var currentCommandRunner = _commandRunners.Find(r => r.Command == command); if (currentCommandRunner != null) _commandRunners.Remove(currentCommandRunner); await UpdateReadOnlyCommands(); StartCommandRunner(); diff --git a/src/GuiApp/FileTime.Avalonia/Configuration/MainConfiguration.cs b/src/GuiApp/FileTime.Avalonia/Configuration/MainConfiguration.cs index 49aace9..a76eab3 100644 --- a/src/GuiApp/FileTime.Avalonia/Configuration/MainConfiguration.cs +++ b/src/GuiApp/FileTime.Avalonia/Configuration/MainConfiguration.cs @@ -45,6 +45,7 @@ namespace FileTime.Avalonia.Configuration new CommandBindingConfiguration(Commands.AutoRefresh, new KeyConfig(Key.R, shift: true)), new CommandBindingConfiguration(Commands.ChangeTimelineMode, new[] { Key.T, Key.M }), new CommandBindingConfiguration(Commands.CloseTab, Key.Q), + new CommandBindingConfiguration(Commands.Compress, new[] { Key.Y, Key.C }), new CommandBindingConfiguration(Commands.Copy, new[] { Key.Y, Key.Y }), new CommandBindingConfiguration(Commands.CopyHash, new[] { Key.C, Key.H }), new CommandBindingConfiguration(Commands.CopyPath, new[] { Key.C, Key.P }), diff --git a/src/GuiApp/FileTime.Avalonia/Services/CommandHandlerService.cs b/src/GuiApp/FileTime.Avalonia/Services/CommandHandlerService.cs index 8a1400a..52b16e3 100644 --- a/src/GuiApp/FileTime.Avalonia/Services/CommandHandlerService.cs +++ b/src/GuiApp/FileTime.Avalonia/Services/CommandHandlerService.cs @@ -18,6 +18,7 @@ using FileTime.Core.Models; using FileTime.Core.Providers; using FileTime.Core.Timeline; using FileTime.Providers.Local; +using FileTime.Tools.Compression.Command; using Microsoft.Extensions.Logging; namespace FileTime.Avalonia.Services @@ -66,14 +67,15 @@ namespace FileTime.Avalonia.Services {Commands.AutoRefresh, ToggleAutoRefresh}, {Commands.ChangeTimelineMode, ChangeTimelineMode}, {Commands.CloseTab, CloseTab}, + {Commands.Compress, Compress}, {Commands.Copy, Copy}, {Commands.CopyHash, CopyHash}, {Commands.CopyPath, CopyPath}, {Commands.CreateContainer, CreateContainer}, {Commands.CreateElement, CreateElement}, {Commands.Cut, Cut}, - {Commands.EnterRapidTravel, EnterRapidTravelMode}, {Commands.Edit, Edit}, + {Commands.EnterRapidTravel, EnterRapidTravelMode}, {Commands.GoToHome, GotToHome}, {Commands.GoToPath, GoToContainer}, {Commands.GoToProvider, GotToProvider}, @@ -468,14 +470,37 @@ namespace FileTime.Avalonia.Services command.Sources.Add(item); } + _clipboard.Clear(); + var currentLocation = _appState.SelectedTab.CurrentLocation.Container; - command.Target = currentLocation is VirtualContainer virtualContainer + + currentLocation = + currentLocation is VirtualContainer virtualContainer ? virtualContainer.BaseContainer : currentLocation; - await AddCommand(command); + if (!command.TargetIsContainer) + { + var handler = async (List inputs) => + { + command.Target = AbsolutePath.FromParentAndChildName(currentLocation, inputs[0].Value, AbsolutePathType.Element); + command.InputResults = inputs.Skip(1).Select(i => i.Option ?? i.Value).ToList(); + await AddCommand(command); + }; - _clipboard.Clear(); + var inputs = new List() + { + InputElement.ForText("Compressed element name") + }; + inputs.AddRange(command.Inputs); + + _dialogService.ReadInputs(inputs, handler); + } + else + { + command.Target = new AbsolutePath(currentLocation); + await AddCommand(command); + } } } @@ -659,7 +684,6 @@ namespace FileTime.Avalonia.Services foreach (var timelineBlock in _appState.TimelineCommands) { - foreach (var command in timelineBlock.ParallelCommands) { if (command.IsSelected) @@ -881,10 +905,34 @@ namespace FileTime.Avalonia.Services _dialogService.ReadInputs(new List() { - InputElement.ForOptions("Hash function", Enum.GetValues().Cast().ToList()) + InputElement.ForOptions("Hash function", Enum.GetValues().Cast()) }, handler); return Task.CompletedTask; } + + private async Task Compress() + { + _clipboard.Clear(); + _clipboard.SetCommand(); + + var currentSelectedItems = await _appState.SelectedTab.TabState.GetCurrentMarkedItems(); + if (currentSelectedItems.Count > 0) + { + foreach (var selectedItem in currentSelectedItems) + { + _clipboard.AddContent(selectedItem); + } + await _appState.SelectedTab.TabState.ClearCurrentMarkedItems(); + } + else + { + var currentSelectedItem = _appState.SelectedTab.SelectedItem?.Item; + if (currentSelectedItem != null) + { + _clipboard.AddContent(new AbsolutePath(currentSelectedItem)); + } + } + } } } \ No newline at end of file diff --git a/src/Providers/FileTime.Providers.Local/LocalContentReader.cs b/src/Providers/FileTime.Providers.Local/LocalContentReader.cs index a79487d..f2af080 100644 --- a/src/Providers/FileTime.Providers.Local/LocalContentReader.cs +++ b/src/Providers/FileTime.Providers.Local/LocalContentReader.cs @@ -1,4 +1,3 @@ -using System.Threading.Tasks; using FileTime.Core.Providers; namespace FileTime.Providers.Local @@ -7,10 +6,10 @@ namespace FileTime.Providers.Local { private readonly FileStream _readerStream; private readonly BinaryReader _binaryReader; - private bool disposed; + private bool _disposed; public int PreferredBufferSize => 1024 * 1024; - private long? _bytesRead; + public long? Position { get; private set; } public LocalContentReader(FileStream readerStream) { @@ -24,10 +23,10 @@ namespace FileTime.Providers.Local if (offset != null) { - if (_bytesRead == null) _bytesRead = 0; + if (Position == null) Position = 0; var buffer = new byte[max]; var bytesRead = _binaryReader.Read(buffer, offset.Value, max); - _bytesRead += bytesRead; + Position += bytesRead; if (buffer.Length != bytesRead) { @@ -41,6 +40,11 @@ namespace FileTime.Providers.Local } } + public void SetPosition(long position) + { + Position = position; + } + ~LocalContentReader() { Dispose(false); @@ -54,7 +58,7 @@ namespace FileTime.Providers.Local private void Dispose(bool disposing) { - if (!disposed) + if (!_disposed) { if (disposing) { @@ -62,7 +66,7 @@ namespace FileTime.Providers.Local _binaryReader.Dispose(); } } - disposed = true; + _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 index 0bd8762..a75a120 100644 --- a/src/Providers/FileTime.Providers.Local/LocalContentWriter.cs +++ b/src/Providers/FileTime.Providers.Local/LocalContentWriter.cs @@ -15,9 +15,16 @@ namespace FileTime.Providers.Local _binaryWriter = new BinaryWriter(_writerStream); } - public Task WriteBytesAsync(byte[] data) + public Task WriteBytesAsync(byte[] data, int? index = null) { - _binaryWriter.Write(data); + if (index != null) + { + _binaryWriter.Write(data, index.Value, data.Length); + } + else + { + _binaryWriter.Write(data); + } return Task.CompletedTask; } diff --git a/src/Providers/FileTime.Providers.Smb/SmbContentReader.cs b/src/Providers/FileTime.Providers.Smb/SmbContentReader.cs index 898ae6a..bf286f3 100644 --- a/src/Providers/FileTime.Providers.Smb/SmbContentReader.cs +++ b/src/Providers/FileTime.Providers.Smb/SmbContentReader.cs @@ -10,7 +10,8 @@ namespace FileTime.Providers.Smb private readonly object _fileHandle; private readonly ISMBClient _client; private bool _disposed; - private long _bytesRead; + private long _position; + public long? Position => _position; public int PreferredBufferSize => (int)_client.MaxReadSize; @@ -25,7 +26,7 @@ namespace FileTime.Providers.Smb { var max = bufferSize > 0 && bufferSize < (int)_client.MaxReadSize ? bufferSize : (int)_client.MaxReadSize; - var status = _smbFileStore.ReadFile(out byte[] data, _fileHandle, offset ?? _bytesRead, max); + var status = _smbFileStore.ReadFile(out byte[] data, _fileHandle, offset ?? _position, max); if (status != NTStatus.STATUS_SUCCESS && status != NTStatus.STATUS_END_OF_FILE) { throw new Exception("Failed to read from file"); @@ -35,11 +36,16 @@ namespace FileTime.Providers.Smb { return Task.FromResult(Array.Empty()); } - _bytesRead += data.Length; + _position += data.Length; return Task.FromResult(data); } + public void SetPosition(long position) + { + _position = position; + } + ~SmbContentReader() { Dispose(false); diff --git a/src/Providers/FileTime.Providers.Smb/SmbContentWriter.cs b/src/Providers/FileTime.Providers.Smb/SmbContentWriter.cs index 063f5f0..be90275 100644 --- a/src/Providers/FileTime.Providers.Smb/SmbContentWriter.cs +++ b/src/Providers/FileTime.Providers.Smb/SmbContentWriter.cs @@ -26,14 +26,16 @@ namespace FileTime.Providers.Smb return Task.CompletedTask; } - public Task WriteBytesAsync(byte[] data) + public Task WriteBytesAsync(byte[] data, int? index = null) { - var status = _smbFileStore.WriteFile(out int numberOfBytesWritten, _fileHandle, _writeOffset, data); + var status = _smbFileStore.WriteFile(out int numberOfBytesWritten, _fileHandle, index ?? _writeOffset, data); if (status != NTStatus.STATUS_SUCCESS) { throw new Exception("Failed to write to file"); } - _writeOffset += numberOfBytesWritten; + _writeOffset = index == null + ? _writeOffset + numberOfBytesWritten + : index.Value + numberOfBytesWritten; return Task.CompletedTask; } diff --git a/src/Tools/FileTime.Tools.Compression/Command/CompressCommand.cs b/src/Tools/FileTime.Tools.Compression/Command/CompressCommand.cs index b0a2e2f..4a1652e 100644 --- a/src/Tools/FileTime.Tools.Compression/Command/CompressCommand.cs +++ b/src/Tools/FileTime.Tools.Compression/Command/CompressCommand.cs @@ -1,13 +1,20 @@ using AsyncEvent; using FileTime.Core.Command; +using FileTime.Core.Extensions; +using FileTime.Core.Interactions; using FileTime.Core.Models; +using FileTime.Core.Providers; using FileTime.Core.Timeline; +using FileTime.Tools.Compression.Command.OperationHandlers; +using SharpCompress.Archives; +using SharpCompress.Common; namespace FileTime.Tools.Compression.Command { - public class CompressCommand : IExecutableCommand + public class CompressCommand : IExecutableCommand, ITransportationCommand { public IList Sources { get; } = new List(); + public AbsolutePath? Target { get; set; } public string DisplayLabel { get; } = "Compress"; public IReadOnlyList CanRunMessages { get; } = new List().AsReadOnly(); @@ -16,6 +23,18 @@ namespace FileTime.Tools.Compression.Command public int CurrentProgress { get; } public AsyncEventHandler ProgressChanged { get; } = new AsyncEventHandler(); + public TransportMode? TransportMode { get; set; } + public bool TargetIsContainer => false; + public List Inputs { get; } + public List? InputResults { get; set; } + + public CompressCommand() + { + Inputs = new List() + { + InputElement.ForOptions("Compression method", Enum.GetValues().Cast()) + }; + } public Task CanRun(PointInTime startPoint) { @@ -25,12 +44,89 @@ namespace FileTime.Tools.Compression.Command public Task SimulateCommand(PointInTime startPoint) { + //TODO: implement return Task.FromResult(startPoint.WithDifferences(new List())); } - public Task Execute(TimeRunner timeRunner) + public async Task Execute(TimeRunner timeRunner) { - throw new NotImplementedException(); + if (Target == null) throw new ArgumentException(nameof(Target) + " can not be null"); + + var disposables = Enumerable.Empty(); + + ICompressOperation? compressOperation = null; + try + { + var compressionType = + InputResults?.Count > 0 && InputResults[0] is Models.CompressionType compType + ? compType + : Models.CompressionType.Zip; + + compressOperation = compressionType switch + { + Models.CompressionType.Gzip => SharpCompress.Archives.GZip.GZipArchive.Create().Map(a => GetCompressOperation(a, (s) => a.SaveTo(s, new SharpCompress.Writers.WriterOptions(CompressionType.GZip)))), + Models.CompressionType.Zip => SharpCompress.Archives.Zip.ZipArchive.Create().Map(a => GetCompressOperation(a, a.SaveTo)), + Models.CompressionType.Tar => SharpCompress.Archives.Tar.TarArchive.Create().Map(a => GetCompressOperation(a, (s) => a.SaveTo(s, new SharpCompress.Writers.WriterOptions(CompressionType.None)))), + Models.CompressionType.TarBz2 => SharpCompress.Archives.Tar.TarArchive.Create().Map(a => GetCompressOperation(a, (s) => a.SaveTo(s, new SharpCompress.Writers.WriterOptions(CompressionType.BZip2)))), + Models.CompressionType.TarLz => SharpCompress.Archives.Tar.TarArchive.Create().Map(a => GetCompressOperation(a, (s) => a.SaveTo(s, new SharpCompress.Writers.WriterOptions(CompressionType.LZip)))), + _ => throw new NotImplementedException() + }; + + disposables = await TraverseTree(Sources, "", compressOperation); + + var resolvedParent = (IContainer)(await Target.GetParent().ResolveAsync())!; + var targetElement = await resolvedParent.CreateElementAsync(Target.GetName()); + + using var contentWriter = await targetElement.GetContentWriterAsync(); + using var contentWriterStream = new ContentProviderStream(contentWriter); + compressOperation.SaveTo(contentWriterStream); + + await contentWriterStream.FlushAsync(); + } + finally + { + compressOperation?.Dispose(); + + foreach (var disposable in disposables) + { + disposable.Dispose(); + } + } + } + + private static async Task> TraverseTree( + IEnumerable sources, + string basePath, + ICompressOperation operations) + { + var disposables = Enumerable.Empty(); + foreach (var source in sources) + { + var item = await source.ResolveAsync(); + + if (item is IContainer container) + { + var items = await container.GetItems(); + if (items == null) continue; + + var childItems = items.Select(i => new AbsolutePath(i)).ToList()!; + var path = string.IsNullOrEmpty(basePath) ? container.Name : basePath + "\\" + container.Name; + disposables = disposables.Concat(await TraverseTree(childItems, path, operations)); + } + else if (item is IElement element) + { + var path = string.IsNullOrEmpty(basePath) ? element.Name : basePath + "\\" + element.Name; + disposables = disposables.Concat(await operations.CompressElement(element, path)); + } + } + return disposables; + } + + public static CompressOperation GetCompressOperation(AbstractWritableArchive archive, Action saveTo) + where TEntry : IArchiveEntry + where TVolume : IVolume + { + return new CompressOperation(archive, saveTo); } } } \ No newline at end of file diff --git a/src/Tools/FileTime.Tools.Compression/Command/OperationHandlers/CompressOperation.cs b/src/Tools/FileTime.Tools.Compression/Command/OperationHandlers/CompressOperation.cs new file mode 100644 index 0000000..45c7d23 --- /dev/null +++ b/src/Tools/FileTime.Tools.Compression/Command/OperationHandlers/CompressOperation.cs @@ -0,0 +1,64 @@ +using FileTime.Core.Models; +using FileTime.Core.Providers; +using SharpCompress.Archives; +using SharpCompress.Common; + +namespace FileTime.Tools.Compression.Command.OperationHandlers +{ + public class CompressOperation : ICompressOperation + where TEntry : IArchiveEntry + where TVolume : IVolume + { + private readonly AbstractWritableArchive _archive; + private readonly Action _saveTo; + private bool _disposed; + + public CompressOperation(AbstractWritableArchive archive, Action saveTo) + { + _archive = archive; + _saveTo = saveTo; + } + public async Task> CompressElement(IElement element, string key) + { + if (element.Provider.SupportsContentStreams) + { + var contentReader = await element.GetContentReaderAsync(); + var contentReaderStream = new ContentProviderStream(contentReader); + + _archive.AddEntry(key, contentReaderStream); + + return new IDisposable[] { contentReader, contentReaderStream }; + } + + return Enumerable.Empty(); + } + + public void SaveTo(Stream stream) + { + _saveTo(stream); + } + + ~CompressOperation() + { + Dispose(false); + } + + public void Dispose() + { + Dispose(true); + GC.SuppressFinalize(this); + } + + private void Dispose(bool disposing) + { + if (!_disposed) + { + if (disposing) + { + _archive.Dispose(); + } + } + _disposed = true; + } + } +} \ No newline at end of file diff --git a/src/Tools/FileTime.Tools.Compression/Command/OperationHandlers/ICompressOperation.cs b/src/Tools/FileTime.Tools.Compression/Command/OperationHandlers/ICompressOperation.cs new file mode 100644 index 0000000..252cfcd --- /dev/null +++ b/src/Tools/FileTime.Tools.Compression/Command/OperationHandlers/ICompressOperation.cs @@ -0,0 +1,10 @@ +using FileTime.Core.Models; + +namespace FileTime.Tools.Compression.Command.OperationHandlers +{ + public interface ICompressOperation : IDisposable + { + Task> CompressElement(IElement element, string key); + void SaveTo(Stream stream); + } +} \ No newline at end of file diff --git a/src/Tools/FileTime.Tools.Compression/Models/CompressionType.cs b/src/Tools/FileTime.Tools.Compression/Models/CompressionType.cs new file mode 100644 index 0000000..c1c7ef6 --- /dev/null +++ b/src/Tools/FileTime.Tools.Compression/Models/CompressionType.cs @@ -0,0 +1,11 @@ +namespace FileTime.Tools.Compression.Models +{ + public enum CompressionType + { + Gzip, + Zip, + Tar, + TarBz2, + TarLz + } +} \ No newline at end of file