Compression, fixes

This commit is contained in:
2022-02-17 14:40:38 +01:00
parent f110a8442b
commit ef5c3a9caf
22 changed files with 332 additions and 47 deletions

View File

@@ -7,6 +7,7 @@ namespace FileTime.App.Core.Command
AutoRefresh, AutoRefresh,
ChangeTimelineMode, ChangeTimelineMode,
CloseTab, CloseTab,
Compress,
Copy, Copy,
CopyHash, CopyHash,
CopyPath, CopyPath,

View File

@@ -9,6 +9,7 @@
<PackageReference Include="Microsoft.Extensions.DependencyInjection" Version="6.0.0" /> <PackageReference Include="Microsoft.Extensions.DependencyInjection" Version="6.0.0" />
</ItemGroup> </ItemGroup>
<ItemGroup> <ItemGroup>
<ProjectReference Include="..\..\Tools\FileTime.Tools.Compression\FileTime.Tools.Compression.csproj" />
<ProjectReference Include="..\FileTime.App.Core\FileTime.App.Core.csproj" /> <ProjectReference Include="..\FileTime.App.Core\FileTime.App.Core.csproj" />
<ProjectReference Include="..\..\Providers\FileTime.Providers.Local\FileTime.Providers.Local.csproj" /> <ProjectReference Include="..\..\Providers\FileTime.Providers.Local\FileTime.Providers.Local.csproj" />
<ProjectReference Include="..\..\Providers\FileTime.Providers.Smb\FileTime.Providers.Smb.csproj" /> <ProjectReference Include="..\..\Providers\FileTime.Providers.Smb\FileTime.Providers.Smb.csproj" />

View File

@@ -1,4 +1,5 @@
using AsyncEvent; using AsyncEvent;
using FileTime.Core.Interactions;
using FileTime.Core.Models; using FileTime.Core.Models;
using FileTime.Core.Timeline; using FileTime.Core.Timeline;
@@ -14,7 +15,7 @@ namespace FileTime.Core.Command
public IList<AbsolutePath> Sources { get; } = new List<AbsolutePath>(); public IList<AbsolutePath> Sources { get; } = new List<AbsolutePath>();
public IContainer? Target { get; set; } public AbsolutePath? Target { get; set; }
public TransportMode? TransportMode { get; set; } = Command.TransportMode.Merge; public TransportMode? TransportMode { get; set; } = Command.TransportMode.Merge;
@@ -25,6 +26,9 @@ namespace FileTime.Core.Command
public string DisplayLabel { get; } = "Copy"; public string DisplayLabel { get; } = "Copy";
public IReadOnlyList<string> CanRunMessages { get; } = new List<string>().AsReadOnly(); public IReadOnlyList<string> CanRunMessages { get; } = new List<string>().AsReadOnly();
public bool TargetIsContainer => true;
public List<InputElement> Inputs { get; } = new();
public List<object>? InputResults { get; set; }
private async Task UpdateProgress() private async Task UpdateProgress()
{ {
@@ -85,7 +89,8 @@ namespace FileTime.Core.Command
return (IContainer)(await newContainerDiff.AbsolutePath.ResolveAsync())!; 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); 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() 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; _operationStatuses = operationStatuses;
} }

View File

@@ -1,3 +1,4 @@
using FileTime.Core.Interactions;
using FileTime.Core.Models; using FileTime.Core.Models;
namespace FileTime.Core.Command namespace FileTime.Core.Command
@@ -5,7 +6,10 @@ namespace FileTime.Core.Command
public interface ITransportationCommand : ICommand public interface ITransportationCommand : ICommand
{ {
IList<AbsolutePath> Sources { get; } IList<AbsolutePath> Sources { get; }
IContainer? Target { get; set;} AbsolutePath? Target { get; set; }
TransportMode? TransportMode { get; set; } TransportMode? TransportMode { get; set; }
bool TargetIsContainer { get; }
List<InputElement> Inputs { get; }
List<object>? InputResults { get; set; }
} }
} }

View File

@@ -1,4 +1,5 @@
using AsyncEvent; using AsyncEvent;
using FileTime.Core.Interactions;
using FileTime.Core.Models; using FileTime.Core.Models;
using FileTime.Core.Timeline; using FileTime.Core.Timeline;
@@ -8,7 +9,7 @@ namespace FileTime.Core.Command
{ {
public IList<AbsolutePath> Sources { get; } = new List<AbsolutePath>(); public IList<AbsolutePath> Sources { get; } = new List<AbsolutePath>();
public IContainer? Target { get; set; } public AbsolutePath? Target { get; set; }
public TransportMode? TransportMode { get; set; } = Command.TransportMode.Merge; public TransportMode? TransportMode { get; set; } = Command.TransportMode.Merge;
public int Progress => 100; public int Progress => 100;
@@ -16,6 +17,10 @@ namespace FileTime.Core.Command
public AsyncEventHandler ProgressChanged { get; } = new(); public AsyncEventHandler ProgressChanged { get; } = new();
public string DisplayLabel { get; } = "MoveCommand"; public string DisplayLabel { get; } = "MoveCommand";
public IReadOnlyList<string> CanRunMessages { get; } = new List<string>().AsReadOnly(); public IReadOnlyList<string> CanRunMessages { get; } = new List<string>().AsReadOnly();
public bool TargetIsContainer => true;
public List<InputElement> Inputs { get; } = new();
public List<object>? InputResults { get; set; }
public Task<CanCommandRun> CanRun(PointInTime startPoint) public Task<CanCommandRun> CanRun(PointInTime startPoint)
{ {

View File

@@ -1,5 +1,6 @@
using AsyncEvent; using AsyncEvent;
using FileTime.Core.Extensions; using FileTime.Core.Extensions;
using FileTime.Core.Interactions;
using FileTime.Core.Models; using FileTime.Core.Models;
using FileTime.Core.Timeline; using FileTime.Core.Timeline;

View File

@@ -10,7 +10,7 @@ namespace FileTime.Core.CommandHandlers
{ {
if (command is not CopyCommand copyCommand) return false; 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); && copyCommand.Sources.All(p => p.ContentProvider.SupportsContentStreams);
} }

View File

@@ -31,9 +31,9 @@ namespace FileTime.Core.Interactions
return new InputElement(label, InputType.Password, defaultValue); return new InputElement(label, InputType.Password, defaultValue);
} }
public static InputElement ForOptions(string label, List<object> defaultValue) public static InputElement ForOptions(string label, IEnumerable<object> defaultValue)
{ {
return new InputElement(label, InputType.Options, defaultValue); return new InputElement(label, InputType.Options, new List<object>(defaultValue));
} }
} }
} }

View File

@@ -1,16 +1,14 @@
using System.Threading.Tasks;
namespace FileTime.Core.Providers namespace FileTime.Core.Providers
{ {
public class ContentProviderStream : Stream public class ContentProviderStream : Stream
{ {
private readonly IContentReader? _contentReader; private readonly IContentReader? _contentReader;
private readonly IContentWriter? _contentWriter; 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(); public override long Length => throw new NotImplementedException();
@@ -28,7 +26,8 @@ namespace FileTime.Core.Providers
public override void Flush() 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) 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) 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) public override void SetLength(long value)
@@ -55,7 +63,14 @@ namespace FileTime.Core.Providers
public override void Write(byte[] buffer, int offset, int count) 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();
} }
} }
} }

View File

@@ -3,7 +3,9 @@ namespace FileTime.Core.Providers
public interface IContentReader : IDisposable public interface IContentReader : IDisposable
{ {
int PreferredBufferSize { get; } int PreferredBufferSize { get; }
long? Position { get; }
Task<byte[]> ReadBytesAsync(int bufferSize, int? offset = null); Task<byte[]> ReadBytesAsync(int bufferSize, int? offset = null);
void SetPosition(long position);
} }
} }

View File

@@ -4,7 +4,7 @@ namespace FileTime.Core.Providers
{ {
int PreferredBufferSize { get; } int PreferredBufferSize { get; }
Task WriteBytesAsync(byte[] data); Task WriteBytesAsync(byte[] data, int? index = null);
Task FlushAsync(); Task FlushAsync();
} }
} }

View File

@@ -151,11 +151,11 @@ namespace FileTime.Core.Timeline
} }
finally 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 () => 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); if (currentCommandRunner != null) _commandRunners.Remove(currentCommandRunner);
await UpdateReadOnlyCommands(); await UpdateReadOnlyCommands();
StartCommandRunner(); StartCommandRunner();

View File

@@ -45,6 +45,7 @@ namespace FileTime.Avalonia.Configuration
new CommandBindingConfiguration(Commands.AutoRefresh, new KeyConfig(Key.R, shift: true)), new CommandBindingConfiguration(Commands.AutoRefresh, new KeyConfig(Key.R, shift: true)),
new CommandBindingConfiguration(Commands.ChangeTimelineMode, new[] { Key.T, Key.M }), new CommandBindingConfiguration(Commands.ChangeTimelineMode, new[] { Key.T, Key.M }),
new CommandBindingConfiguration(Commands.CloseTab, Key.Q), 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.Copy, new[] { Key.Y, Key.Y }),
new CommandBindingConfiguration(Commands.CopyHash, new[] { Key.C, Key.H }), new CommandBindingConfiguration(Commands.CopyHash, new[] { Key.C, Key.H }),
new CommandBindingConfiguration(Commands.CopyPath, new[] { Key.C, Key.P }), new CommandBindingConfiguration(Commands.CopyPath, new[] { Key.C, Key.P }),

View File

@@ -18,6 +18,7 @@ using FileTime.Core.Models;
using FileTime.Core.Providers; using FileTime.Core.Providers;
using FileTime.Core.Timeline; using FileTime.Core.Timeline;
using FileTime.Providers.Local; using FileTime.Providers.Local;
using FileTime.Tools.Compression.Command;
using Microsoft.Extensions.Logging; using Microsoft.Extensions.Logging;
namespace FileTime.Avalonia.Services namespace FileTime.Avalonia.Services
@@ -66,14 +67,15 @@ namespace FileTime.Avalonia.Services
{Commands.AutoRefresh, ToggleAutoRefresh}, {Commands.AutoRefresh, ToggleAutoRefresh},
{Commands.ChangeTimelineMode, ChangeTimelineMode}, {Commands.ChangeTimelineMode, ChangeTimelineMode},
{Commands.CloseTab, CloseTab}, {Commands.CloseTab, CloseTab},
{Commands.Compress, Compress},
{Commands.Copy, Copy}, {Commands.Copy, Copy},
{Commands.CopyHash, CopyHash}, {Commands.CopyHash, CopyHash},
{Commands.CopyPath, CopyPath}, {Commands.CopyPath, CopyPath},
{Commands.CreateContainer, CreateContainer}, {Commands.CreateContainer, CreateContainer},
{Commands.CreateElement, CreateElement}, {Commands.CreateElement, CreateElement},
{Commands.Cut, Cut}, {Commands.Cut, Cut},
{Commands.EnterRapidTravel, EnterRapidTravelMode},
{Commands.Edit, Edit}, {Commands.Edit, Edit},
{Commands.EnterRapidTravel, EnterRapidTravelMode},
{Commands.GoToHome, GotToHome}, {Commands.GoToHome, GotToHome},
{Commands.GoToPath, GoToContainer}, {Commands.GoToPath, GoToContainer},
{Commands.GoToProvider, GotToProvider}, {Commands.GoToProvider, GotToProvider},
@@ -468,14 +470,37 @@ namespace FileTime.Avalonia.Services
command.Sources.Add(item); command.Sources.Add(item);
} }
_clipboard.Clear();
var currentLocation = _appState.SelectedTab.CurrentLocation.Container; var currentLocation = _appState.SelectedTab.CurrentLocation.Container;
command.Target = currentLocation is VirtualContainer virtualContainer
currentLocation =
currentLocation is VirtualContainer virtualContainer
? virtualContainer.BaseContainer ? virtualContainer.BaseContainer
: currentLocation; : currentLocation;
if (!command.TargetIsContainer)
{
var handler = async (List<InputElementWrapper> 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); await AddCommand(command);
};
_clipboard.Clear(); var inputs = new List<InputElement>()
{
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 timelineBlock in _appState.TimelineCommands)
{ {
foreach (var command in timelineBlock.ParallelCommands) foreach (var command in timelineBlock.ParallelCommands)
{ {
if (command.IsSelected) if (command.IsSelected)
@@ -881,10 +905,34 @@ namespace FileTime.Avalonia.Services
_dialogService.ReadInputs(new List<InputElement>() _dialogService.ReadInputs(new List<InputElement>()
{ {
InputElement.ForOptions("Hash function", Enum.GetValues<HashFunction>().Cast<object>().ToList()) InputElement.ForOptions("Hash function", Enum.GetValues<HashFunction>().Cast<object>())
}, handler); }, handler);
return Task.CompletedTask; return Task.CompletedTask;
} }
private async Task Compress()
{
_clipboard.Clear();
_clipboard.SetCommand<CompressCommand>();
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));
}
}
}
} }
} }

View File

@@ -1,4 +1,3 @@
using System.Threading.Tasks;
using FileTime.Core.Providers; using FileTime.Core.Providers;
namespace FileTime.Providers.Local namespace FileTime.Providers.Local
@@ -7,10 +6,10 @@ namespace FileTime.Providers.Local
{ {
private readonly FileStream _readerStream; private readonly FileStream _readerStream;
private readonly BinaryReader _binaryReader; private readonly BinaryReader _binaryReader;
private bool disposed; private bool _disposed;
public int PreferredBufferSize => 1024 * 1024; public int PreferredBufferSize => 1024 * 1024;
private long? _bytesRead; public long? Position { get; private set; }
public LocalContentReader(FileStream readerStream) public LocalContentReader(FileStream readerStream)
{ {
@@ -24,10 +23,10 @@ namespace FileTime.Providers.Local
if (offset != null) if (offset != null)
{ {
if (_bytesRead == null) _bytesRead = 0; if (Position == null) Position = 0;
var buffer = new byte[max]; var buffer = new byte[max];
var bytesRead = _binaryReader.Read(buffer, offset.Value, max); var bytesRead = _binaryReader.Read(buffer, offset.Value, max);
_bytesRead += bytesRead; Position += bytesRead;
if (buffer.Length != bytesRead) if (buffer.Length != bytesRead)
{ {
@@ -41,6 +40,11 @@ namespace FileTime.Providers.Local
} }
} }
public void SetPosition(long position)
{
Position = position;
}
~LocalContentReader() ~LocalContentReader()
{ {
Dispose(false); Dispose(false);
@@ -54,7 +58,7 @@ namespace FileTime.Providers.Local
private void Dispose(bool disposing) private void Dispose(bool disposing)
{ {
if (!disposed) if (!_disposed)
{ {
if (disposing) if (disposing)
{ {
@@ -62,7 +66,7 @@ namespace FileTime.Providers.Local
_binaryReader.Dispose(); _binaryReader.Dispose();
} }
} }
disposed = true; _disposed = true;
} }
} }
} }

View File

@@ -15,9 +15,16 @@ namespace FileTime.Providers.Local
_binaryWriter = new BinaryWriter(_writerStream); _binaryWriter = new BinaryWriter(_writerStream);
} }
public Task WriteBytesAsync(byte[] data) public Task WriteBytesAsync(byte[] data, int? index = null)
{
if (index != null)
{
_binaryWriter.Write(data, index.Value, data.Length);
}
else
{ {
_binaryWriter.Write(data); _binaryWriter.Write(data);
}
return Task.CompletedTask; return Task.CompletedTask;
} }

View File

@@ -10,7 +10,8 @@ namespace FileTime.Providers.Smb
private readonly object _fileHandle; private readonly object _fileHandle;
private readonly ISMBClient _client; private readonly ISMBClient _client;
private bool _disposed; private bool _disposed;
private long _bytesRead; private long _position;
public long? Position => _position;
public int PreferredBufferSize => (int)_client.MaxReadSize; 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 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) if (status != NTStatus.STATUS_SUCCESS && status != NTStatus.STATUS_END_OF_FILE)
{ {
throw new Exception("Failed to read from file"); throw new Exception("Failed to read from file");
@@ -35,11 +36,16 @@ namespace FileTime.Providers.Smb
{ {
return Task.FromResult(Array.Empty<byte>()); return Task.FromResult(Array.Empty<byte>());
} }
_bytesRead += data.Length; _position += data.Length;
return Task.FromResult(data); return Task.FromResult(data);
} }
public void SetPosition(long position)
{
_position = position;
}
~SmbContentReader() ~SmbContentReader()
{ {
Dispose(false); Dispose(false);

View File

@@ -26,14 +26,16 @@ namespace FileTime.Providers.Smb
return Task.CompletedTask; 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) if (status != NTStatus.STATUS_SUCCESS)
{ {
throw new Exception("Failed to write to file"); throw new Exception("Failed to write to file");
} }
_writeOffset += numberOfBytesWritten; _writeOffset = index == null
? _writeOffset + numberOfBytesWritten
: index.Value + numberOfBytesWritten;
return Task.CompletedTask; return Task.CompletedTask;
} }

View File

@@ -1,13 +1,20 @@
using AsyncEvent; using AsyncEvent;
using FileTime.Core.Command; using FileTime.Core.Command;
using FileTime.Core.Extensions;
using FileTime.Core.Interactions;
using FileTime.Core.Models; using FileTime.Core.Models;
using FileTime.Core.Providers;
using FileTime.Core.Timeline; using FileTime.Core.Timeline;
using FileTime.Tools.Compression.Command.OperationHandlers;
using SharpCompress.Archives;
using SharpCompress.Common;
namespace FileTime.Tools.Compression.Command namespace FileTime.Tools.Compression.Command
{ {
public class CompressCommand : IExecutableCommand public class CompressCommand : IExecutableCommand, ITransportationCommand
{ {
public IList<AbsolutePath> Sources { get; } = new List<AbsolutePath>(); public IList<AbsolutePath> Sources { get; } = new List<AbsolutePath>();
public AbsolutePath? Target { get; set; }
public string DisplayLabel { get; } = "Compress"; public string DisplayLabel { get; } = "Compress";
public IReadOnlyList<string> CanRunMessages { get; } = new List<string>().AsReadOnly(); public IReadOnlyList<string> CanRunMessages { get; } = new List<string>().AsReadOnly();
@@ -16,6 +23,18 @@ namespace FileTime.Tools.Compression.Command
public int CurrentProgress { get; } public int CurrentProgress { get; }
public AsyncEventHandler ProgressChanged { get; } = new AsyncEventHandler(); public AsyncEventHandler ProgressChanged { get; } = new AsyncEventHandler();
public TransportMode? TransportMode { get; set; }
public bool TargetIsContainer => false;
public List<InputElement> Inputs { get; }
public List<object>? InputResults { get; set; }
public CompressCommand()
{
Inputs = new List<InputElement>()
{
InputElement.ForOptions("Compression method", Enum.GetValues<Models.CompressionType>().Cast<object>())
};
}
public Task<CanCommandRun> CanRun(PointInTime startPoint) public Task<CanCommandRun> CanRun(PointInTime startPoint)
{ {
@@ -25,12 +44,89 @@ namespace FileTime.Tools.Compression.Command
public Task<PointInTime> SimulateCommand(PointInTime startPoint) public Task<PointInTime> SimulateCommand(PointInTime startPoint)
{ {
//TODO: implement
return Task.FromResult(startPoint.WithDifferences(new List<Difference>())); return Task.FromResult(startPoint.WithDifferences(new List<Difference>()));
} }
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<IDisposable>();
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<IEnumerable<IDisposable>> TraverseTree(
IEnumerable<AbsolutePath> sources,
string basePath,
ICompressOperation operations)
{
var disposables = Enumerable.Empty<IDisposable>();
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<TEntry, TVolume> GetCompressOperation<TEntry, TVolume>(AbstractWritableArchive<TEntry, TVolume> archive, Action<Stream> saveTo)
where TEntry : IArchiveEntry
where TVolume : IVolume
{
return new CompressOperation<TEntry, TVolume>(archive, saveTo);
} }
} }
} }

View File

@@ -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<TEntry, TVolume> : ICompressOperation
where TEntry : IArchiveEntry
where TVolume : IVolume
{
private readonly AbstractWritableArchive<TEntry, TVolume> _archive;
private readonly Action<Stream> _saveTo;
private bool _disposed;
public CompressOperation(AbstractWritableArchive<TEntry, TVolume> archive, Action<Stream> saveTo)
{
_archive = archive;
_saveTo = saveTo;
}
public async Task<IEnumerable<IDisposable>> 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<IDisposable>();
}
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;
}
}
}

View File

@@ -0,0 +1,10 @@
using FileTime.Core.Models;
namespace FileTime.Tools.Compression.Command.OperationHandlers
{
public interface ICompressOperation : IDisposable
{
Task<IEnumerable<IDisposable>> CompressElement(IElement element, string key);
void SaveTo(Stream stream);
}
}

View File

@@ -0,0 +1,11 @@
namespace FileTime.Tools.Compression.Models
{
public enum CompressionType
{
Gzip,
Zip,
Tar,
TarBz2,
TarLz
}
}