Browse compressed file content
This commit is contained in:
@@ -9,7 +9,7 @@ using SharpCompress.Archives;
|
||||
using SharpCompress.Common;
|
||||
using SharpCompressCompressionType = SharpCompress.Common.CompressionType;
|
||||
|
||||
namespace FileTime.Tools.Compression;
|
||||
namespace FileTime.Tools.Compression.Compress;
|
||||
|
||||
public class CompressCommand : CommandBase, IExecutableCommand, ITransportationCommand, IRequireInputCommand
|
||||
{
|
||||
@@ -4,7 +4,7 @@ using FileTime.Core.Interactions;
|
||||
using FileTime.Core.Models;
|
||||
using FileTime.Core.Timeline;
|
||||
|
||||
namespace FileTime.Tools.Compression;
|
||||
namespace FileTime.Tools.Compression.Compress;
|
||||
|
||||
public class CompressCommandFactory : ITransportationCommandFactory<CompressCommand>
|
||||
{
|
||||
@@ -1,6 +1,6 @@
|
||||
using FileTime.App.Core.UserCommand;
|
||||
|
||||
namespace FileTime.Tools.Compression;
|
||||
namespace FileTime.Tools.Compression.Compress;
|
||||
|
||||
public class CompressUserCommand : IIdentifiableUserCommand
|
||||
{
|
||||
@@ -4,6 +4,8 @@ using FileTime.App.Core.Services;
|
||||
using FileTime.App.Core.Services.UserCommandHandler;
|
||||
using FileTime.App.Core.ViewModels;
|
||||
using FileTime.Core.Models;
|
||||
using FileTime.Tools.Compression.Compress;
|
||||
using FileTime.Tools.Compression.Decompress;
|
||||
|
||||
namespace FileTime.Tools.Compression;
|
||||
|
||||
|
||||
@@ -0,0 +1,22 @@
|
||||
using System.Text;
|
||||
using FileTime.Core.ContentAccess;
|
||||
using FileTime.Core.Models;
|
||||
using FileTime.Core.Timeline;
|
||||
|
||||
namespace FileTime.Tools.Compression.ContentProvider;
|
||||
|
||||
public class CompressedContentProvider : SubContentProviderBase, ICompressedContentProvider
|
||||
{
|
||||
public CompressedContentProvider(
|
||||
IContentProvider parentContentProvider,
|
||||
ITimelessContentProvider timelessContentProvider
|
||||
)
|
||||
: base(parentContentProvider, "compression", timelessContentProvider)
|
||||
{
|
||||
}
|
||||
|
||||
public override Task<byte[]?> GetContentAsync(IElement element, int? maxLength = null, CancellationToken cancellationToken = default)
|
||||
=> Task.FromResult((byte[]?)"Not implemented..."u8.ToArray());
|
||||
|
||||
public override VolumeSizeInfo? GetVolumeSizeInfo(FullName path) => throw new NotImplementedException();
|
||||
}
|
||||
@@ -0,0 +1,19 @@
|
||||
using FileTime.Core.ContentAccess;
|
||||
using FileTime.Core.Timeline;
|
||||
|
||||
namespace FileTime.Tools.Compression.ContentProvider;
|
||||
|
||||
public class CompressedContentProviderFactory : ICompressedContentProviderFactory
|
||||
{
|
||||
private readonly ITimelessContentProvider _timelessContentProvider;
|
||||
|
||||
public CompressedContentProviderFactory(ITimelessContentProvider timelessContentProvider)
|
||||
{
|
||||
_timelessContentProvider = timelessContentProvider;
|
||||
}
|
||||
|
||||
public ICompressedContentProvider Create(IContentProvider parentContentProvider)
|
||||
{
|
||||
return new CompressedContentProvider(parentContentProvider, _timelessContentProvider);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,301 @@
|
||||
using System.Collections.ObjectModel;
|
||||
using FileTime.Core.ContentAccess;
|
||||
using FileTime.Core.Enums;
|
||||
using FileTime.Core.Models;
|
||||
using FileTime.Core.Timeline;
|
||||
using SharpCompress.Archives;
|
||||
using IContainer = FileTime.Core.Models.IContainer;
|
||||
|
||||
namespace FileTime.Tools.Compression.ContentProvider;
|
||||
|
||||
public sealed class CompressedSubContentProvider : ICompressedSubContentProvider
|
||||
{
|
||||
private static readonly string[] SupportedExtensions = {".zip", ".gz", ".7z"};
|
||||
|
||||
private readonly IContentAccessorFactory _contentAccessorFactory;
|
||||
private readonly ICompressedContentProviderFactory _compressedContentProviderFactory;
|
||||
private readonly ITimelessContentProvider _timelessContentProvider;
|
||||
|
||||
public CompressedSubContentProvider(
|
||||
IContentAccessorFactory contentAccessorFactory,
|
||||
ICompressedContentProviderFactory compressedContentProviderFactory,
|
||||
ITimelessContentProvider timelessContentProvider
|
||||
)
|
||||
{
|
||||
_contentAccessorFactory = contentAccessorFactory;
|
||||
_compressedContentProviderFactory = compressedContentProviderFactory;
|
||||
_timelessContentProvider = timelessContentProvider;
|
||||
}
|
||||
|
||||
public Task<bool> CanHandleAsync(IElement parentElement)
|
||||
=> Task.FromResult(
|
||||
parentElement.NativePath?.Path is { } path
|
||||
&& SupportedExtensions.Any(e => path.EndsWith(e))
|
||||
);
|
||||
|
||||
public async Task<IItem?> GetItemByFullNameAsync(
|
||||
IElement parentElement,
|
||||
FullName itemPath,
|
||||
PointInTime pointInTime,
|
||||
AbsolutePathType forceResolvePathType = AbsolutePathType.Unknown,
|
||||
ItemInitializationSettings itemInitializationSettings = default)
|
||||
{
|
||||
var parentContentReader = await _contentAccessorFactory.GetContentReaderFactory(parentElement.Provider).CreateContentReaderAsync(parentElement);
|
||||
var parentContentReaderStream = parentContentReader.AsStream();
|
||||
var archive = ArchiveFactory.Open(parentContentReaderStream);
|
||||
var disposables = new IDisposable[] {parentContentReader, parentContentReaderStream, archive};
|
||||
|
||||
if (itemPath.Path.Length == 0 || itemPath.Path == Constants.SubContentProviderRootContainer)
|
||||
{
|
||||
var rootFullNameBase = parentElement.FullName!.Path + Constants.SeparatorChar + Constants.SubContentProviderRootContainer;
|
||||
var rootNativePathBase = parentElement.NativePath!.Path + Constants.SeparatorChar + Constants.SubContentProviderRootContainer;
|
||||
|
||||
var rootFullName = new FullName(rootFullNameBase);
|
||||
var rootNativePath = new NativePath(rootNativePathBase);
|
||||
|
||||
var container = CreateContainer(
|
||||
archive,
|
||||
new FullName(":/"),
|
||||
rootFullName,
|
||||
rootNativePath,
|
||||
parentElement,
|
||||
parentElement.Parent!,
|
||||
itemInitializationSettings,
|
||||
disposables
|
||||
);
|
||||
return container;
|
||||
}
|
||||
|
||||
return ResolveNonRootChild(
|
||||
archive,
|
||||
parentElement,
|
||||
itemPath,
|
||||
itemInitializationSettings,
|
||||
disposables
|
||||
);
|
||||
}
|
||||
|
||||
private IItem ResolveNonRootChild(
|
||||
IArchive archive,
|
||||
IElement parentElement,
|
||||
FullName itemPath,
|
||||
ItemInitializationSettings itemInitializationSettings,
|
||||
ICollection<IDisposable> disposables)
|
||||
{
|
||||
var childFullNameBase = parentElement.FullName!.Path + Constants.SeparatorChar + itemPath.Path;
|
||||
var childNativePathBase = parentElement.NativePath!.Path + Constants.SeparatorChar + itemPath.Path;
|
||||
|
||||
var childFullName = new FullName(childFullNameBase);
|
||||
var childNativePath = new NativePath(childNativePathBase);
|
||||
|
||||
var isDirectory = false;
|
||||
var path = itemPath.Path
|
||||
.Substring(1 + Constants.SubContentProviderRootContainer.Length)
|
||||
.Replace(Constants.SeparatorChar, '/');
|
||||
|
||||
var pathWithSlash = path + '/';
|
||||
var size = 0L;
|
||||
foreach (var archiveEntry in archive.Entries)
|
||||
{
|
||||
if (archiveEntry.Key == path)
|
||||
{
|
||||
if (archiveEntry.IsDirectory)
|
||||
{
|
||||
isDirectory = true;
|
||||
break;
|
||||
}
|
||||
|
||||
size = archiveEntry.Size;
|
||||
break;
|
||||
}
|
||||
|
||||
if (archiveEntry.Key.StartsWith(pathWithSlash))
|
||||
{
|
||||
isDirectory = true;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
var parent = new AbsolutePath(
|
||||
_timelessContentProvider,
|
||||
parentElement.PointInTime,
|
||||
childFullName.GetParent()!,
|
||||
AbsolutePathType.Container
|
||||
);
|
||||
|
||||
if (isDirectory)
|
||||
{
|
||||
return CreateContainer(
|
||||
archive,
|
||||
itemPath,
|
||||
childFullName,
|
||||
childNativePath,
|
||||
parentElement,
|
||||
parent,
|
||||
itemInitializationSettings,
|
||||
disposables
|
||||
);
|
||||
}
|
||||
else
|
||||
{
|
||||
var element = CreateElement(
|
||||
itemPath,
|
||||
childFullName,
|
||||
childNativePath,
|
||||
parentElement,
|
||||
parent,
|
||||
size);
|
||||
|
||||
foreach (var disposable in disposables)
|
||||
{
|
||||
disposable.Dispose();
|
||||
}
|
||||
|
||||
return element;
|
||||
}
|
||||
}
|
||||
|
||||
private IContainer CreateContainer(
|
||||
IArchive archive,
|
||||
FullName itemPath,
|
||||
FullName fullName,
|
||||
NativePath nativePath,
|
||||
IElement parentElement,
|
||||
AbsolutePath parent,
|
||||
ItemInitializationSettings initializationSettings,
|
||||
ICollection<IDisposable> disposables)
|
||||
{
|
||||
var name = itemPath.Path.Split(Constants.SeparatorChar).Last();
|
||||
|
||||
var children = new ObservableCollection<AbsolutePath>();
|
||||
var exceptions = new ObservableCollection<Exception>();
|
||||
|
||||
var container = new Container(
|
||||
name,
|
||||
name,
|
||||
fullName,
|
||||
nativePath,
|
||||
parent,
|
||||
true,
|
||||
true,
|
||||
parentElement.CreatedAt,
|
||||
parentElement.ModifiedAt,
|
||||
SupportsDelete.False,
|
||||
false,
|
||||
"",
|
||||
_compressedContentProviderFactory.Create(parentElement.Provider),
|
||||
false,
|
||||
parentElement.PointInTime,
|
||||
exceptions,
|
||||
new ReadOnlyExtensionCollection(new ExtensionCollection()),
|
||||
children
|
||||
);
|
||||
|
||||
if (!initializationSettings.SkipChildInitialization)
|
||||
{
|
||||
LoadChildren(archive, container, itemPath, parentElement.PointInTime, children, exceptions);
|
||||
ThreadPool.QueueUserWorkItem(_ =>
|
||||
{
|
||||
try
|
||||
{
|
||||
container.StartLoading();
|
||||
//LoadChildren(archive, container, itemPath, parentElement.PointInTime, children, exceptions);
|
||||
}
|
||||
finally
|
||||
{
|
||||
container.StopLoading();
|
||||
foreach (var disposable in disposables)
|
||||
{
|
||||
disposable.Dispose();
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
return container;
|
||||
}
|
||||
|
||||
private void LoadChildren(
|
||||
IArchive archive,
|
||||
Container container,
|
||||
FullName itemPath,
|
||||
PointInTime pointInTime,
|
||||
ObservableCollection<AbsolutePath> children,
|
||||
ObservableCollection<Exception> exceptions)
|
||||
{
|
||||
var containerPath = itemPath.Path
|
||||
.Substring(1 + Constants.SubContentProviderRootContainer.Length);
|
||||
var containerLevel = containerPath.Length != 0
|
||||
? containerPath.Split(Constants.SeparatorChar).Length
|
||||
: 0;
|
||||
|
||||
var addedContainers = new List<string>();
|
||||
foreach (var archiveEntry in archive.Entries)
|
||||
{
|
||||
if (!archiveEntry.Key.StartsWith(containerPath)) continue;
|
||||
|
||||
var childPathParts = archiveEntry.Key.TrimEnd('/').Split('/');
|
||||
if (childPathParts.Length < containerLevel + 1) continue;
|
||||
var itemName = childPathParts[containerLevel];
|
||||
|
||||
if ((archiveEntry.IsDirectory && childPathParts.Length == containerLevel + 1)
|
||||
|| (!archiveEntry.IsDirectory && childPathParts.Length > containerLevel + 1))
|
||||
{
|
||||
//Container
|
||||
if (addedContainers.Contains(itemName)) continue;
|
||||
addedContainers.Add(itemName);
|
||||
|
||||
children.Add(new AbsolutePath(
|
||||
_timelessContentProvider,
|
||||
pointInTime,
|
||||
container.FullName.GetChild(itemName),
|
||||
AbsolutePathType.Container)
|
||||
);
|
||||
}
|
||||
else if (!archiveEntry.IsDirectory && childPathParts.Length == containerLevel + 1)
|
||||
{
|
||||
//Element
|
||||
children.Add(new AbsolutePath(
|
||||
_timelessContentProvider,
|
||||
pointInTime,
|
||||
container.FullName.GetChild(itemName),
|
||||
AbsolutePathType.Element)
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private IItem CreateElement(FullName itemPath,
|
||||
FullName fullName,
|
||||
NativePath nativePath,
|
||||
IElement parentElement,
|
||||
AbsolutePath parent,
|
||||
long size)
|
||||
{
|
||||
var name = itemPath.Path.Split(Constants.SeparatorChar).Last();
|
||||
|
||||
var exceptions = new ObservableCollection<Exception>();
|
||||
|
||||
var element = new Element(
|
||||
name,
|
||||
name,
|
||||
fullName,
|
||||
nativePath,
|
||||
parent,
|
||||
true,
|
||||
true,
|
||||
parentElement.CreatedAt,
|
||||
parentElement.ModifiedAt,
|
||||
SupportsDelete.False,
|
||||
false,
|
||||
"",
|
||||
size,
|
||||
_compressedContentProviderFactory.Create(parentElement.Provider),
|
||||
parentElement.PointInTime,
|
||||
exceptions,
|
||||
new ReadOnlyExtensionCollection(new ExtensionCollection())
|
||||
);
|
||||
|
||||
return element;
|
||||
}
|
||||
}
|
||||
@@ -4,7 +4,7 @@ using FileTime.Core.Models;
|
||||
using FileTime.Core.Timeline;
|
||||
using SharpCompress.Archives;
|
||||
|
||||
namespace FileTime.Tools.Compression;
|
||||
namespace FileTime.Tools.Compression.Decompress;
|
||||
|
||||
public class DecompressCommand : CommandBase, IExecutableCommand, ITransportationCommand, IDisposable
|
||||
{
|
||||
@@ -3,7 +3,7 @@ using FileTime.Core.ContentAccess;
|
||||
using FileTime.Core.Models;
|
||||
using FileTime.Core.Timeline;
|
||||
|
||||
namespace FileTime.Tools.Compression;
|
||||
namespace FileTime.Tools.Compression.Decompress;
|
||||
|
||||
public class DecompressCommandFactory : ITransportationCommandFactory<DecompressCommand>
|
||||
{
|
||||
@@ -1,6 +1,6 @@
|
||||
using FileTime.App.Core.UserCommand;
|
||||
|
||||
namespace FileTime.Tools.Compression;
|
||||
namespace FileTime.Tools.Compression.Decompress;
|
||||
|
||||
public class DecompressUserCommand : IIdentifiableUserCommand
|
||||
{
|
||||
@@ -9,8 +9,12 @@
|
||||
<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="..\..\Core\FileTime.Core.ContentAccess\FileTime.Core.ContentAccess.csproj" />
|
||||
<ProjectReference Include="..\..\Core\FileTime.Core.Models\FileTime.Core.Models.csproj" />
|
||||
<ProjectReference Include="..\FileTime.Tools.Compression.Core\FileTime.Tools.Compression.Core.csproj" />
|
||||
<ProjectReference Include="..\FileTime.Tools\FileTime.Tools.csproj" />
|
||||
</ItemGroup>
|
||||
|
||||
|
||||
|
||||
</Project>
|
||||
|
||||
@@ -1,5 +1,10 @@
|
||||
using FileTime.App.Core.Services;
|
||||
using FileTime.Core.ContentAccess;
|
||||
using FileTime.Tools.Compression.Compress;
|
||||
using FileTime.Tools.Compression.ContentProvider;
|
||||
using FileTime.Tools.Compression.Decompress;
|
||||
using Microsoft.Extensions.DependencyInjection;
|
||||
using Microsoft.Extensions.DependencyInjection.Extensions;
|
||||
|
||||
namespace FileTime.Tools.Compression;
|
||||
|
||||
@@ -21,6 +26,8 @@ public static class Startup
|
||||
services.AddSingleton<CompressCommandFactory>();
|
||||
services.AddSingleton<DecompressCommandFactory>();
|
||||
services.AddSingleton<IUserCommandHandler, CompressionUserCommandHandler>();
|
||||
services.TryAddSingleton<ICompressedContentProviderFactory, CompressedContentProviderFactory>();
|
||||
services.AddSingleton<ISubContentProvider, CompressedSubContentProvider>();
|
||||
return services;
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user