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.Compress; public class CompressCommand : CommandBase, IExecutableCommand, ITransportationCommand, IRequireInputCommand { private readonly IList _inputs; private readonly IUserCommunicationService _userCommunicationService; private readonly ITimelessContentProvider _timelessContentProvider; private readonly IContentAccessorFactory _contentAccessorFactory; private readonly ICommandSchedulerNotifier _commandSchedulerNotifier; private readonly OptionsInputElement _compressionType; private readonly CancellationTokenSource _cancellationTokenSource = new(); private readonly TextInputElement _targetFileName; public IReadOnlyList Sources { get; } public FullName Target { get; } public TransportMode TransportMode { get; } internal CompressCommand( IUserCommunicationService userCommunicationService, ITimelessContentProvider timelessContentProvider, IContentAccessorFactory contentAccessorFactory, ICommandSchedulerNotifier commandSchedulerNotifier, IReadOnlyCollection sources, TransportMode mode, FullName targetFullName) { _userCommunicationService = userCommunicationService; _timelessContentProvider = timelessContentProvider; _contentAccessorFactory = contentAccessorFactory; _commandSchedulerNotifier = commandSchedulerNotifier; ArgumentNullException.ThrowIfNull(sources); ArgumentNullException.ThrowIfNull(mode); ArgumentNullException.ThrowIfNull(targetFullName); Sources = new List(sources).AsReadOnly(); TransportMode = mode; Target = targetFullName; _targetFileName = new TextInputElement("File name"); _compressionType = new OptionsInputElement( "CompressionMethod", Enum.GetValues() .Select(t => new OptionElement(t.ToString(), t)) ) { Value = CompressionType.Zip }; _inputs = new List { _targetFileName, _compressionType }; } public override Task CanRun(PointInTime currentTime) { //TODO: return Task.FromResult(CanCommandRun.True); } public override Task SimulateCommand(PointInTime currentTime) { var differences = new List { 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(); 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(); } } await _commandSchedulerNotifier.RefreshContainer(Target); } private async Task> TraverseTree( IEnumerable sources, string basePath, ICompressOperation operations, CancellationToken cancellationToken = default) { var disposables = Enumerable.Empty(); 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 GetCompressOperation(AbstractWritableArchive archive, Action saveTo) where TEntry : IArchiveEntry where TVolume : IVolume => new(_contentAccessorFactory, archive, saveTo); public async Task ReadInputs() => await _userCommunicationService.ReadInputs(_inputs); }