Compression
This commit is contained in:
@@ -0,0 +1,70 @@
|
||||
using FileTime.Core.ContentAccess;
|
||||
using FileTime.Core.Models;
|
||||
using SharpCompress.Archives;
|
||||
using SharpCompress.Common;
|
||||
|
||||
namespace FileTime.Tools.Compression;
|
||||
|
||||
public class CompressOperation<TEntry, TVolume> : ICompressOperation
|
||||
where TEntry : IArchiveEntry
|
||||
where TVolume : IVolume
|
||||
{
|
||||
private readonly IContentAccessorFactory _contentAccessorFactory;
|
||||
private readonly AbstractWritableArchive<TEntry, TVolume> _archive;
|
||||
private readonly Action<Stream> _saveTo;
|
||||
private bool _disposed;
|
||||
|
||||
public CompressOperation(
|
||||
IContentAccessorFactory contentAccessorFactory,
|
||||
AbstractWritableArchive<TEntry, TVolume> archive,
|
||||
Action<Stream> saveTo
|
||||
)
|
||||
{
|
||||
_contentAccessorFactory = contentAccessorFactory;
|
||||
_archive = archive;
|
||||
_saveTo = saveTo;
|
||||
}
|
||||
|
||||
public async Task<IEnumerable<IDisposable>> CompressElement(IElement element, string key)
|
||||
{
|
||||
if (element.Provider.SupportsContentStreams)
|
||||
{
|
||||
var contentReader = await _contentAccessorFactory.GetContentReaderFactory(element.Provider).CreateContentReaderAsync(element);
|
||||
|
||||
var contentReaderStream = contentReader.AsStream();
|
||||
|
||||
_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;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,18 @@
|
||||
<Project Sdk="Microsoft.NET.Sdk">
|
||||
|
||||
<PropertyGroup>
|
||||
<TargetFramework>net7.0</TargetFramework>
|
||||
<ImplicitUsings>enable</ImplicitUsings>
|
||||
<Nullable>enable</Nullable>
|
||||
<RootNamespace>FileTime.Tools.Compression</RootNamespace>
|
||||
</PropertyGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<PackageReference Include="SharpCompress" Version="0.33.0" />
|
||||
</ItemGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<ProjectReference Include="..\..\Core\FileTime.Core.Abstraction\FileTime.Core.Abstraction.csproj" />
|
||||
</ItemGroup>
|
||||
|
||||
</Project>
|
||||
@@ -0,0 +1,9 @@
|
||||
using FileTime.Core.Models;
|
||||
|
||||
namespace FileTime.Tools.Compression;
|
||||
|
||||
public interface ICompressOperation : IDisposable
|
||||
{
|
||||
Task<IEnumerable<IDisposable>> CompressElement(IElement element, string key);
|
||||
void SaveTo(Stream stream);
|
||||
}
|
||||
164
src/Tools/FileTime.Tools.Compression/CompressCommand.cs
Normal file
164
src/Tools/FileTime.Tools.Compression/CompressCommand.cs
Normal file
@@ -0,0 +1,164 @@
|
||||
using FileTime.Core.Command;
|
||||
using FileTime.Core.ContentAccess;
|
||||
using FileTime.Core.Enums;
|
||||
using FileTime.Core.Interactions;
|
||||
using FileTime.Core.Models;
|
||||
using FileTime.Core.Timeline;
|
||||
using FileTime.Tools.Extensions;
|
||||
using SharpCompress.Archives;
|
||||
using SharpCompress.Common;
|
||||
using SharpCompressCompressionType = SharpCompress.Common.CompressionType;
|
||||
|
||||
namespace FileTime.Tools.Compression;
|
||||
|
||||
public class CompressCommand : CommandBase, IExecutableCommand, ITransportationCommand, IRequireInputCommand
|
||||
{
|
||||
private readonly IList<IInputElement> _inputs;
|
||||
private readonly IUserCommunicationService _userCommunicationService;
|
||||
private readonly ITimelessContentProvider _timelessContentProvider;
|
||||
private readonly IContentAccessorFactory _contentAccessorFactory;
|
||||
private readonly OptionsInputElement<CompressionType> _compressionType;
|
||||
private readonly CancellationTokenSource _cancellationTokenSource = new();
|
||||
private readonly TextInputElement _targetFileName;
|
||||
public IReadOnlyList<FullName> Sources { get; }
|
||||
public FullName Target { get; }
|
||||
public TransportMode TransportMode { get; }
|
||||
|
||||
|
||||
internal CompressCommand(
|
||||
IUserCommunicationService userCommunicationService,
|
||||
ITimelessContentProvider timelessContentProvider,
|
||||
IContentAccessorFactory contentAccessorFactory,
|
||||
IReadOnlyCollection<FullName> sources,
|
||||
TransportMode mode,
|
||||
FullName targetFullName)
|
||||
{
|
||||
_userCommunicationService = userCommunicationService;
|
||||
_timelessContentProvider = timelessContentProvider;
|
||||
_contentAccessorFactory = contentAccessorFactory;
|
||||
ArgumentNullException.ThrowIfNull(sources);
|
||||
ArgumentNullException.ThrowIfNull(mode);
|
||||
ArgumentNullException.ThrowIfNull(targetFullName);
|
||||
|
||||
Sources = new List<FullName>(sources).AsReadOnly();
|
||||
TransportMode = mode;
|
||||
Target = targetFullName;
|
||||
|
||||
_targetFileName = new TextInputElement("File name");
|
||||
_compressionType = new OptionsInputElement<CompressionType>(
|
||||
"CompressionMethod",
|
||||
Enum.GetValues<CompressionType>()
|
||||
.Select(t => new OptionElement<CompressionType>(t.ToString(), t))
|
||||
);
|
||||
|
||||
_inputs = new List<IInputElement>
|
||||
{
|
||||
_targetFileName,
|
||||
_compressionType
|
||||
};
|
||||
}
|
||||
|
||||
|
||||
public override Task<CanCommandRun> CanRun(PointInTime currentTime)
|
||||
{
|
||||
//TODO:
|
||||
return Task.FromResult(CanCommandRun.True);
|
||||
}
|
||||
|
||||
public override Task<PointInTime> SimulateCommand(PointInTime currentTime)
|
||||
{
|
||||
var differences = new List<Difference>
|
||||
{
|
||||
new(DifferenceActionType.Create, new AbsolutePath(_timelessContentProvider, currentTime, Target, AbsolutePathType.Element))
|
||||
};
|
||||
return Task.FromResult(currentTime.WithDifferences(differences));
|
||||
}
|
||||
|
||||
public override void Cancel()
|
||||
=> _cancellationTokenSource.Cancel();
|
||||
|
||||
public async Task Execute()
|
||||
{
|
||||
var disposables = Enumerable.Empty<IDisposable>();
|
||||
|
||||
ICompressOperation? compressOperation = null;
|
||||
try
|
||||
{
|
||||
var compressionType = _compressionType.Value;
|
||||
|
||||
compressOperation = compressionType switch
|
||||
{
|
||||
CompressionType.Gzip => SharpCompress.Archives.GZip.GZipArchive.Create().Map(a => GetCompressOperation(a, (s) => a.SaveTo(s, new SharpCompress.Writers.WriterOptions(SharpCompressCompressionType.GZip)))),
|
||||
CompressionType.Zip => SharpCompress.Archives.Zip.ZipArchive.Create().Map(a => GetCompressOperation(a, a.SaveTo)),
|
||||
CompressionType.Tar => SharpCompress.Archives.Tar.TarArchive.Create().Map(a => GetCompressOperation(a, (s) => a.SaveTo(s, new SharpCompress.Writers.WriterOptions(SharpCompressCompressionType.None)))),
|
||||
CompressionType.TarBz2 => SharpCompress.Archives.Tar.TarArchive.Create().Map(a => GetCompressOperation(a, (s) => a.SaveTo(s, new SharpCompress.Writers.WriterOptions(SharpCompressCompressionType.BZip2)))),
|
||||
CompressionType.TarLz => SharpCompress.Archives.Tar.TarArchive.Create().Map(a => GetCompressOperation(a, (s) => a.SaveTo(s, new SharpCompress.Writers.WriterOptions(SharpCompressCompressionType.LZip)))),
|
||||
_ => throw new NotImplementedException()
|
||||
};
|
||||
|
||||
disposables = await TraverseTree(Sources, "", compressOperation);
|
||||
|
||||
var resolvedParent = await _timelessContentProvider.GetItemByFullNameAsync(
|
||||
Target,
|
||||
PointInTime.Present
|
||||
);
|
||||
|
||||
var newItemName = Target.GetChild(_targetFileName.Value!);
|
||||
await _contentAccessorFactory.GetItemCreator(resolvedParent.Provider).CreateElementAsync(resolvedParent.Provider, newItemName);
|
||||
var targetElement = (IElement) await _timelessContentProvider.GetItemByFullNameAsync(newItemName, PointInTime.Present);
|
||||
using var contentWriter = await _contentAccessorFactory.GetContentWriterFactory(resolvedParent.Provider).CreateContentWriterAsync(targetElement);
|
||||
await using var contentWriterStream = contentWriter.AsStream();
|
||||
compressOperation.SaveTo(contentWriterStream);
|
||||
|
||||
await contentWriterStream.FlushAsync(_cancellationTokenSource.Token);
|
||||
}
|
||||
finally
|
||||
{
|
||||
compressOperation?.Dispose();
|
||||
|
||||
foreach (var disposable in disposables)
|
||||
{
|
||||
disposable.Dispose();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private async Task<IEnumerable<IDisposable>> TraverseTree(
|
||||
IEnumerable<FullName> sources,
|
||||
string basePath,
|
||||
ICompressOperation operations,
|
||||
CancellationToken cancellationToken = default)
|
||||
{
|
||||
var disposables = Enumerable.Empty<IDisposable>();
|
||||
foreach (var source in sources)
|
||||
{
|
||||
var item = await _timelessContentProvider.GetItemByFullNameAsync(
|
||||
source,
|
||||
PointInTime.Present
|
||||
);
|
||||
|
||||
if (item is IContainer container)
|
||||
{
|
||||
var items = container.Items;
|
||||
|
||||
var childItems = items.Select(i => i.Path).ToList();
|
||||
var path = string.IsNullOrEmpty(basePath) ? container.Name : basePath + "\\" + container.Name;
|
||||
disposables = disposables.Concat(await TraverseTree(childItems, path, operations, cancellationToken));
|
||||
}
|
||||
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;
|
||||
}
|
||||
|
||||
private CompressOperation<TEntry, TVolume> GetCompressOperation<TEntry, TVolume>(AbstractWritableArchive<TEntry, TVolume> archive, Action<Stream> saveTo)
|
||||
where TEntry : IArchiveEntry where TVolume : IVolume
|
||||
=> new(_contentAccessorFactory, archive, saveTo);
|
||||
|
||||
public async Task ReadInputs()
|
||||
=> await _userCommunicationService.ReadInputs(_inputs);
|
||||
}
|
||||
@@ -0,0 +1,34 @@
|
||||
using FileTime.Core.Command;
|
||||
using FileTime.Core.ContentAccess;
|
||||
using FileTime.Core.Interactions;
|
||||
using FileTime.Core.Models;
|
||||
using FileTime.Core.Timeline;
|
||||
|
||||
namespace FileTime.Tools.Compression;
|
||||
|
||||
public class CompressCommandFactory : ITransportationCommandFactory<CompressCommand>
|
||||
{
|
||||
private readonly IUserCommunicationService _userCommunicationService;
|
||||
private readonly ITimelessContentProvider _timelessContentProvider;
|
||||
private readonly IContentAccessorFactory _contentAccessorFactory;
|
||||
|
||||
public CompressCommandFactory(
|
||||
IUserCommunicationService userCommunicationService,
|
||||
ITimelessContentProvider timelessContentProvider,
|
||||
IContentAccessorFactory contentAccessorFactory)
|
||||
{
|
||||
_userCommunicationService = userCommunicationService;
|
||||
_timelessContentProvider = timelessContentProvider;
|
||||
_contentAccessorFactory = contentAccessorFactory;
|
||||
}
|
||||
|
||||
public CompressCommand GenerateCommand(IReadOnlyCollection<FullName> sources, TransportMode mode, FullName targetFullName)
|
||||
=> new(
|
||||
_userCommunicationService,
|
||||
_timelessContentProvider,
|
||||
_contentAccessorFactory,
|
||||
sources,
|
||||
mode,
|
||||
targetFullName
|
||||
);
|
||||
}
|
||||
15
src/Tools/FileTime.Tools.Compression/CompressUserCommand.cs
Normal file
15
src/Tools/FileTime.Tools.Compression/CompressUserCommand.cs
Normal file
@@ -0,0 +1,15 @@
|
||||
using FileTime.App.Core.UserCommand;
|
||||
|
||||
namespace FileTime.Tools.Compression;
|
||||
|
||||
public class CompressUserCommand : IIdentifiableUserCommand
|
||||
{
|
||||
public const string CommandName = "compress";
|
||||
public static readonly CompressUserCommand Instance = new();
|
||||
private CompressUserCommand()
|
||||
{
|
||||
}
|
||||
|
||||
public string UserCommandID => CommandName;
|
||||
public string Title => "Compress";
|
||||
}
|
||||
10
src/Tools/FileTime.Tools.Compression/CompressionType.cs
Normal file
10
src/Tools/FileTime.Tools.Compression/CompressionType.cs
Normal file
@@ -0,0 +1,10 @@
|
||||
namespace FileTime.Tools.Compression;
|
||||
|
||||
public enum CompressionType
|
||||
{
|
||||
Gzip,
|
||||
Zip,
|
||||
Tar,
|
||||
TarBz2,
|
||||
TarLz
|
||||
}
|
||||
@@ -0,0 +1,49 @@
|
||||
using System.Collections.ObjectModel;
|
||||
using DeclarativeProperty;
|
||||
using FileTime.App.Core.Services;
|
||||
using FileTime.App.Core.Services.UserCommandHandler;
|
||||
using FileTime.App.Core.ViewModels;
|
||||
using FileTime.Core.Models;
|
||||
|
||||
namespace FileTime.Tools.Compression;
|
||||
|
||||
public class CompressionUserCommandHandler : AggregatedUserCommandHandler
|
||||
{
|
||||
private readonly IClipboardService _clipboardService;
|
||||
private readonly IDeclarativeProperty<ObservableCollection<FullName>?> _markedItems;
|
||||
private readonly IDeclarativeProperty<IItemViewModel?> _selectedItem;
|
||||
|
||||
public CompressionUserCommandHandler(
|
||||
IAppState appState,
|
||||
IClipboardService clipboardService)
|
||||
{
|
||||
_clipboardService = clipboardService;
|
||||
_markedItems = appState.SelectedTab.Map(t => t.MarkedItems).Switch();
|
||||
_selectedItem = appState.SelectedTab.Map(t => t.CurrentSelectedItem).Switch();
|
||||
|
||||
AddCommandHandler(new[]
|
||||
{
|
||||
new TypeUserCommandHandler<CompressUserCommand>(Compress)
|
||||
});
|
||||
}
|
||||
|
||||
private Task Compress()
|
||||
{
|
||||
_clipboardService.Clear();
|
||||
_clipboardService.SetCommand<CompressCommandFactory>();
|
||||
|
||||
if (_markedItems.Value is {Count: > 0} markedItems)
|
||||
{
|
||||
foreach (var markedItem in markedItems)
|
||||
{
|
||||
_clipboardService.AddContent(markedItem);
|
||||
}
|
||||
}
|
||||
else if (_selectedItem.Value?.BaseItem?.FullName is { } fullname)
|
||||
{
|
||||
_clipboardService.AddContent(fullname);
|
||||
}
|
||||
|
||||
return Task.CompletedTask;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,16 @@
|
||||
<Project Sdk="Microsoft.NET.Sdk">
|
||||
|
||||
<PropertyGroup>
|
||||
<TargetFramework>net7.0</TargetFramework>
|
||||
<ImplicitUsings>enable</ImplicitUsings>
|
||||
<Nullable>enable</Nullable>
|
||||
</PropertyGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<ProjectReference Include="..\..\AppCommon\FileTime.App.Core.Abstraction\FileTime.App.Core.Abstraction.csproj" />
|
||||
<ProjectReference Include="..\..\Core\FileTime.Core.Command\FileTime.Core.Command.csproj" />
|
||||
<ProjectReference Include="..\FileTime.Tools.Compression.Core\FileTime.Tools.Compression.Core.csproj" />
|
||||
<ProjectReference Include="..\FileTime.Tools\FileTime.Tools.csproj" />
|
||||
</ItemGroup>
|
||||
|
||||
</Project>
|
||||
24
src/Tools/FileTime.Tools.Compression/Startup.cs
Normal file
24
src/Tools/FileTime.Tools.Compression/Startup.cs
Normal file
@@ -0,0 +1,24 @@
|
||||
using FileTime.App.Core.Services;
|
||||
using Microsoft.Extensions.DependencyInjection;
|
||||
|
||||
namespace FileTime.Tools.Compression;
|
||||
|
||||
public class StartupHandler : IStartupHandler
|
||||
{
|
||||
public StartupHandler(IIdentifiableUserCommandService identifiableUserCommandService)
|
||||
{
|
||||
identifiableUserCommandService.AddIdentifiableUserCommand(CompressUserCommand.Instance);
|
||||
}
|
||||
public Task InitAsync() => Task.CompletedTask;
|
||||
}
|
||||
|
||||
public static class Startup
|
||||
{
|
||||
public static IServiceCollection AddCompression(this IServiceCollection services)
|
||||
{
|
||||
services.AddSingleton<IStartupHandler, StartupHandler>();
|
||||
services.AddSingleton<CompressCommandFactory>();
|
||||
services.AddSingleton<IUserCommandHandler, CompressionUserCommandHandler>();
|
||||
return services;
|
||||
}
|
||||
}
|
||||
@@ -2,6 +2,8 @@ namespace FileTime.Tools.Extensions;
|
||||
|
||||
public static class ObjectExtensions
|
||||
{
|
||||
public static TResult Map<T, TResult>(this T obj, Func<T, TResult> mapper)
|
||||
=> mapper(obj);
|
||||
public static TResult? MapNull<T, TResult>(this T obj, Func<TResult?> nullHandler, Func<T, TResult?> valueHandler)
|
||||
=> obj == null ? nullHandler() : valueHandler(obj);
|
||||
|
||||
|
||||
Reference in New Issue
Block a user