Browse compressed file content

This commit is contained in:
2023-09-05 00:13:24 +02:00
parent 3a29991948
commit b85c19407e
19 changed files with 406 additions and 14 deletions

View File

@@ -6,12 +6,12 @@ namespace FileTime.Core.ContentAccess;
public interface ISubContentProvider public interface ISubContentProvider
{ {
Task<bool> CanHandleAsync(IElement parentElement);
Task<IItem?> GetItemByFullNameAsync( Task<IItem?> GetItemByFullNameAsync(
IElement parentElement, IElement parentElement,
FullName itemPath, FullName itemPath,
PointInTime pointInTime, PointInTime pointInTime,
AbsolutePathType forceResolvePathType = AbsolutePathType.Unknown, AbsolutePathType forceResolvePathType = AbsolutePathType.Unknown,
ItemInitializationSettings itemInitializationSettings = default); ItemInitializationSettings itemInitializationSettings = default);
Task<bool> CanHandleAsync(IElement parentElement);
} }

View File

@@ -0,0 +1,8 @@
using FileTime.Core.ContentAccess;
namespace FileTime.Tools.Compression.ContentProvider;
public interface ICompressedContentProvider : IContentProvider
{
}

View File

@@ -0,0 +1,8 @@
using FileTime.Core.ContentAccess;
namespace FileTime.Tools.Compression.ContentProvider;
public interface ICompressedContentProviderFactory
{
ICompressedContentProvider Create(IContentProvider parentContentProvider);
}

View File

@@ -0,0 +1,8 @@
using FileTime.Core.ContentAccess;
namespace FileTime.Tools.Compression.ContentProvider;
public interface ICompressedSubContentProvider : ISubContentProvider
{
}

View File

@@ -15,4 +15,5 @@
<ProjectReference Include="..\..\Core\FileTime.Core.Abstraction\FileTime.Core.Abstraction.csproj" /> <ProjectReference Include="..\..\Core\FileTime.Core.Abstraction\FileTime.Core.Abstraction.csproj" />
</ItemGroup> </ItemGroup>
</Project> </Project>

View File

@@ -9,7 +9,7 @@ using SharpCompress.Archives;
using SharpCompress.Common; using SharpCompress.Common;
using SharpCompressCompressionType = SharpCompress.Common.CompressionType; using SharpCompressCompressionType = SharpCompress.Common.CompressionType;
namespace FileTime.Tools.Compression; namespace FileTime.Tools.Compression.Compress;
public class CompressCommand : CommandBase, IExecutableCommand, ITransportationCommand, IRequireInputCommand public class CompressCommand : CommandBase, IExecutableCommand, ITransportationCommand, IRequireInputCommand
{ {

View File

@@ -4,7 +4,7 @@ using FileTime.Core.Interactions;
using FileTime.Core.Models; using FileTime.Core.Models;
using FileTime.Core.Timeline; using FileTime.Core.Timeline;
namespace FileTime.Tools.Compression; namespace FileTime.Tools.Compression.Compress;
public class CompressCommandFactory : ITransportationCommandFactory<CompressCommand> public class CompressCommandFactory : ITransportationCommandFactory<CompressCommand>
{ {

View File

@@ -1,6 +1,6 @@
using FileTime.App.Core.UserCommand; using FileTime.App.Core.UserCommand;
namespace FileTime.Tools.Compression; namespace FileTime.Tools.Compression.Compress;
public class CompressUserCommand : IIdentifiableUserCommand public class CompressUserCommand : IIdentifiableUserCommand
{ {

View File

@@ -4,6 +4,8 @@ using FileTime.App.Core.Services;
using FileTime.App.Core.Services.UserCommandHandler; using FileTime.App.Core.Services.UserCommandHandler;
using FileTime.App.Core.ViewModels; using FileTime.App.Core.ViewModels;
using FileTime.Core.Models; using FileTime.Core.Models;
using FileTime.Tools.Compression.Compress;
using FileTime.Tools.Compression.Decompress;
namespace FileTime.Tools.Compression; namespace FileTime.Tools.Compression;

View File

@@ -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();
}

View File

@@ -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);
}
}

View File

@@ -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;
}
}

View File

@@ -4,7 +4,7 @@ using FileTime.Core.Models;
using FileTime.Core.Timeline; using FileTime.Core.Timeline;
using SharpCompress.Archives; using SharpCompress.Archives;
namespace FileTime.Tools.Compression; namespace FileTime.Tools.Compression.Decompress;
public class DecompressCommand : CommandBase, IExecutableCommand, ITransportationCommand, IDisposable public class DecompressCommand : CommandBase, IExecutableCommand, ITransportationCommand, IDisposable
{ {

View File

@@ -3,7 +3,7 @@ using FileTime.Core.ContentAccess;
using FileTime.Core.Models; using FileTime.Core.Models;
using FileTime.Core.Timeline; using FileTime.Core.Timeline;
namespace FileTime.Tools.Compression; namespace FileTime.Tools.Compression.Decompress;
public class DecompressCommandFactory : ITransportationCommandFactory<DecompressCommand> public class DecompressCommandFactory : ITransportationCommandFactory<DecompressCommand>
{ {

View File

@@ -1,6 +1,6 @@
using FileTime.App.Core.UserCommand; using FileTime.App.Core.UserCommand;
namespace FileTime.Tools.Compression; namespace FileTime.Tools.Compression.Decompress;
public class DecompressUserCommand : IIdentifiableUserCommand public class DecompressUserCommand : IIdentifiableUserCommand
{ {

View File

@@ -9,8 +9,12 @@
<ItemGroup> <ItemGroup>
<ProjectReference Include="..\..\AppCommon\FileTime.App.Core.Abstraction\FileTime.App.Core.Abstraction.csproj" /> <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.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.Compression.Core\FileTime.Tools.Compression.Core.csproj" />
<ProjectReference Include="..\FileTime.Tools\FileTime.Tools.csproj" /> <ProjectReference Include="..\FileTime.Tools\FileTime.Tools.csproj" />
</ItemGroup> </ItemGroup>
</Project> </Project>

View File

@@ -1,5 +1,10 @@
using FileTime.App.Core.Services; 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;
using Microsoft.Extensions.DependencyInjection.Extensions;
namespace FileTime.Tools.Compression; namespace FileTime.Tools.Compression;
@@ -21,6 +26,8 @@ public static class Startup
services.AddSingleton<CompressCommandFactory>(); services.AddSingleton<CompressCommandFactory>();
services.AddSingleton<DecompressCommandFactory>(); services.AddSingleton<DecompressCommandFactory>();
services.AddSingleton<IUserCommandHandler, CompressionUserCommandHandler>(); services.AddSingleton<IUserCommandHandler, CompressionUserCommandHandler>();
services.TryAddSingleton<ICompressedContentProviderFactory, CompressedContentProviderFactory>();
services.AddSingleton<ISubContentProvider, CompressedSubContentProvider>();
return services; return services;
} }
} }

View File

@@ -28,7 +28,6 @@ public class VirtualDiskContentProvider : SubContentProviderBase, IVirtualDiskCo
var parentItem = await ParentContentProvider.GetItemByNativePathAsync(supportedPath, element.PointInTime); var parentItem = await ParentContentProvider.GetItemByNativePathAsync(supportedPath, element.PointInTime);
if (parentItem is not IElement parentElement) return null; if (parentItem is not IElement parentElement) return null;
var contentReaderFactory = _contentAccessorFactory.GetContentReaderFactory(parentElement.Provider); var contentReaderFactory = _contentAccessorFactory.GetContentReaderFactory(parentElement.Provider);
var reader = await contentReaderFactory.CreateContentReaderAsync(parentElement); var reader = await contentReaderFactory.CreateContentReaderAsync(parentElement);

View File

@@ -79,7 +79,12 @@ public class VirtualDiskSubContentProvider : IVirtualDiskSubContentProvider
var childFullName = new FullName(childFullNameBase); var childFullName = new FullName(childFullNameBase);
var childNativePath = new NativePath(childNativePathBase); var childNativePath = new NativePath(childNativePathBase);
var parent = new AbsolutePath(_timelessContentProvider, pointInTime, childFullName.GetParent()!, AbsolutePathType.Container); var parent = new AbsolutePath(
_timelessContentProvider,
pointInTime,
childFullName.GetParent()!,
AbsolutePathType.Container
);
var container = discReader.Root; var container = discReader.Root;
for (var i = 1; i < pathParts.Length - 1; i++) for (var i = 1; i < pathParts.Length - 1; i++)
@@ -125,7 +130,7 @@ public class VirtualDiskSubContentProvider : IVirtualDiskSubContentProvider
private IContainer CreateContainer( private IContainer CreateContainer(
UdfReader discReader, UdfReader discReader,
DiscDirectoryInfo sourceContainer, DiscDirectoryInfo sourceContainer,
FullName fullname, FullName fullName,
NativePath nativePath, NativePath nativePath,
IContentProvider parentContentProvider, IContentProvider parentContentProvider,
AbsolutePath parent, AbsolutePath parent,
@@ -137,7 +142,7 @@ public class VirtualDiskSubContentProvider : IVirtualDiskSubContentProvider
var container = new Container( var container = new Container(
sourceContainer.Name, sourceContainer.Name,
sourceContainer.Name, sourceContainer.Name,
fullname, fullName,
nativePath, nativePath,
parent, parent,
true, true,
@@ -159,8 +164,16 @@ public class VirtualDiskSubContentProvider : IVirtualDiskSubContentProvider
{ {
ThreadPool.QueueUserWorkItem(_ => ThreadPool.QueueUserWorkItem(_ =>
{ {
try
{
container.StartLoading();
LoadChildren(container, sourceContainer, children, pointInTime, exceptions); LoadChildren(container, sourceContainer, children, pointInTime, exceptions);
}
finally
{
discReader.Dispose(); discReader.Dispose();
container.StopLoading();
}
}); });
} }