ContentReader for Compressed (+read content for provider)

This commit is contained in:
2023-09-05 22:24:25 +02:00
parent e6fd8d4ab5
commit def5ece688
15 changed files with 195 additions and 57 deletions

View File

@@ -0,0 +1,22 @@
using FileTime.Core.Models;
namespace FileTime.Core.ContentAccess;
internal static class Helper
{
internal static async Task<ParentElementReaderContext> GetParentElementReaderAsync(
IContentAccessorFactory contentAccessorFactory,
IElement element,
IContentProvider parentContentProvider)
{
var elementNativePath = element.NativePath!;
var supportedPath = (await parentContentProvider.GetSupportedPathPart(elementNativePath))!;
var parentElement = (IElement) await parentContentProvider.GetItemByNativePathAsync(supportedPath, element.PointInTime);
var contentReaderFactory = contentAccessorFactory.GetContentReaderFactory(parentElement.Provider);
var reader = await contentReaderFactory.CreateContentReaderAsync(parentElement);
var subPath = new NativePath(elementNativePath.Path.Substring(supportedPath.Path.Length + 2 + Constants.SubContentProviderRootContainer.Length));
return new ParentElementReaderContext(reader, subPath);
}
}

View File

@@ -0,0 +1,5 @@
using FileTime.Core.Models;
namespace FileTime.Core.ContentAccess;
public record ParentElementReaderContext(IContentReader ContentReader, NativePath SubNativePath);

View File

@@ -6,13 +6,18 @@ namespace FileTime.Core.ContentAccess;
public abstract class SubContentProviderBase : ContentProviderBase public abstract class SubContentProviderBase : ContentProviderBase
{ {
private readonly IContentAccessorFactory _contentAccessorFactory;
public IContentProvider ParentContentProvider { get; } public IContentProvider ParentContentProvider { get; }
protected SubContentProviderBase( protected SubContentProviderBase(
ITimelessContentProvider timelessContentProvider,
IContentAccessorFactory contentAccessorFactory,
IContentProvider parentContentProvider, IContentProvider parentContentProvider,
string name, string name
ITimelessContentProvider timelessContentProvider) : base(name, timelessContentProvider) ) : base(name, timelessContentProvider)
{ {
_contentAccessorFactory = contentAccessorFactory;
ParentContentProvider = parentContentProvider; ParentContentProvider = parentContentProvider;
} }
@@ -40,4 +45,7 @@ public abstract class SubContentProviderBase : ContentProviderBase
public override async ValueTask<NativePath?> GetSupportedPathPart(NativePath nativePath) public override async ValueTask<NativePath?> GetSupportedPathPart(NativePath nativePath)
=> await ParentContentProvider.GetSupportedPathPart(nativePath); => await ParentContentProvider.GetSupportedPathPart(nativePath);
protected async Task<ParentElementReaderContext> GetParentElementReaderAsync(IElement element)
=> await Helper.GetParentElementReaderAsync(_contentAccessorFactory, element, ParentContentProvider);
} }

View File

@@ -0,0 +1,18 @@
using FileTime.Core.Models;
namespace FileTime.Core.ContentAccess;
public abstract class SubContentReaderBase<TContentProvider> : IContentReaderFactory<TContentProvider> where TContentProvider : IContentProvider
{
private readonly IContentAccessorFactory _contentAccessorFactory;
protected SubContentReaderBase(IContentAccessorFactory contentAccessorFactory)
{
_contentAccessorFactory = contentAccessorFactory;
}
public abstract Task<IContentReader> CreateContentReaderAsync(IElement element);
protected async Task<ParentElementReaderContext> GetParentElementReaderAsync(IElement element, SubContentProviderBase provider)
=> await Helper.GetParentElementReaderAsync(_contentAccessorFactory, element, provider.ParentContentProvider);
}

View File

@@ -1,22 +1,42 @@
using System.Text; using FileTime.Core.ContentAccess;
using FileTime.Core.ContentAccess;
using FileTime.Core.Models; using FileTime.Core.Models;
using FileTime.Core.Timeline; using FileTime.Core.Timeline;
using SharpCompress.Archives;
namespace FileTime.Tools.Compression.ContentProvider; namespace FileTime.Tools.Compression.ContentProvider;
public class CompressedContentProvider : SubContentProviderBase, ICompressedContentProvider public sealed class CompressedContentProvider : SubContentProviderBase, ICompressedContentProvider
{ {
public CompressedContentProvider( public CompressedContentProvider(
IContentProvider parentContentProvider, ITimelessContentProvider timelessContentProvider,
ITimelessContentProvider timelessContentProvider IContentAccessorFactory contentAccessorFactory,
IContentProvider parentContentProvider
) )
: base(parentContentProvider, "compression", timelessContentProvider) : base(
timelessContentProvider,
contentAccessorFactory,
parentContentProvider,
"compression")
{ {
} }
public override Task<byte[]?> GetContentAsync(IElement element, int? maxLength = null, CancellationToken cancellationToken = default) public override async Task<byte[]?> GetContentAsync(IElement element, int? maxLength = null, CancellationToken cancellationToken = default)
=> Task.FromResult((byte[]?)"Not implemented..."u8.ToArray()); {
var parentElementContext = await GetParentElementReaderAsync(element);
var reader = parentElementContext.ContentReader;
var subPath = parentElementContext.SubNativePath.Path;
await using var readerStream = reader.AsStream();
using var archive = ArchiveFactory.Open(readerStream);
var entry = archive.Entries.First(e => e.Key == subPath);
await using var contentReader= entry.OpenEntryStream();
var data = new byte[1024 * 1024];
var readAsync = await contentReader.ReadAsync(data, cancellationToken);
return data[..readAsync].ToArray();
}
public override VolumeSizeInfo? GetVolumeSizeInfo(FullName path) => throw new NotImplementedException(); public override VolumeSizeInfo? GetVolumeSizeInfo(FullName path) => throw new NotImplementedException();
} }

View File

@@ -3,17 +3,19 @@ using FileTime.Core.Timeline;
namespace FileTime.Tools.Compression.ContentProvider; namespace FileTime.Tools.Compression.ContentProvider;
public class CompressedContentProviderFactory : ICompressedContentProviderFactory public sealed class CompressedContentProviderFactory : ICompressedContentProviderFactory
{ {
private readonly ITimelessContentProvider _timelessContentProvider; private readonly ITimelessContentProvider _timelessContentProvider;
private readonly IContentAccessorFactory _contentAccessorFactory;
public CompressedContentProviderFactory(ITimelessContentProvider timelessContentProvider) public CompressedContentProviderFactory(
ITimelessContentProvider timelessContentProvider,
IContentAccessorFactory contentAccessorFactory)
{ {
_timelessContentProvider = timelessContentProvider; _timelessContentProvider = timelessContentProvider;
_contentAccessorFactory = contentAccessorFactory;
} }
public ICompressedContentProvider Create(IContentProvider parentContentProvider) public ICompressedContentProvider Create(IContentProvider parentContentProvider)
{ => new CompressedContentProvider(_timelessContentProvider, _contentAccessorFactory, parentContentProvider);
return new CompressedContentProvider(parentContentProvider, _timelessContentProvider);
}
} }

View File

@@ -0,0 +1,40 @@
using FileTime.Core.ContentAccess;
using SharpCompress.Archives;
namespace FileTime.Tools.Compression.ContentProvider;
public sealed class CompressedContentReader : IContentReader
{
private readonly IDisposable[] _disposables;
private readonly Stream _stream;
public int PreferredBufferSize => 1024 * 1024;
public long? Position => _stream.Position;
public CompressedContentReader(IArchiveEntry entry, IDisposable[] disposables)
{
_disposables = disposables;
_stream = entry.OpenEntryStream();
}
public void Dispose()
{
_stream.Dispose();
foreach (var disposable in _disposables)
{
disposable.Dispose();
}
}
public async Task<byte[]> ReadBytesAsync(int bufferSize, int? offset = null)
{
var data = new byte[bufferSize];
var read = await _stream.ReadAsync(data, offset ?? 0, bufferSize);
return data[..read].ToArray();
}
public void SetPosition(long position) => _stream.Seek(position, SeekOrigin.Begin);
public Stream AsStream() => _stream;
}

View File

@@ -0,0 +1,34 @@
using FileTime.Core.ContentAccess;
using FileTime.Core.Models;
using SharpCompress.Archives;
namespace FileTime.Tools.Compression.ContentProvider;
public sealed class CompressedContentReaderFactory : SubContentReaderBase<CompressedContentProvider>
{
public CompressedContentReaderFactory(IContentAccessorFactory contentAccessorFactory)
: base(contentAccessorFactory)
{
}
public override async Task<IContentReader> CreateContentReaderAsync(IElement element)
{
if (element.Provider is not CompressedContentProvider provider)
throw new ArgumentException(
$"Provider must be {nameof(CompressedContentProvider)}, but it is " + element.Provider.GetType(),
nameof(element));
var parentElementReaderContext = await GetParentElementReaderAsync(element, provider);
var reader = parentElementReaderContext.ContentReader;
var subPath = parentElementReaderContext.SubNativePath;
var readerStream = reader.AsStream();
var archive = ArchiveFactory.Open(readerStream);
var entry = archive.Entries.First(e => e.Key == subPath.Path);
var disposables = new IDisposable[] {archive, readerStream};
return new CompressedContentReader(entry, disposables);
}
}

View File

@@ -28,6 +28,7 @@ public static class Startup
services.AddSingleton<IUserCommandHandler, CompressionUserCommandHandler>(); services.AddSingleton<IUserCommandHandler, CompressionUserCommandHandler>();
services.TryAddSingleton<ICompressedContentProviderFactory, CompressedContentProviderFactory>(); services.TryAddSingleton<ICompressedContentProviderFactory, CompressedContentProviderFactory>();
services.AddSingleton<ISubContentProvider, CompressedSubContentProvider>(); services.AddSingleton<ISubContentProvider, CompressedSubContentProvider>();
services.TryAddSingleton<IContentReaderFactory<CompressedContentProvider>, CompressedContentReaderFactory>();
return services; return services;
} }
} }

View File

@@ -2,7 +2,7 @@
namespace FileTime.Tools.VirtualDiskSources; namespace FileTime.Tools.VirtualDiskSources;
public class DiscUtilsInitializer : IPreStartupHandler public sealed class DiscUtilsInitializer : IPreStartupHandler
{ {
public Task InitAsync() public Task InitAsync()
{ {

View File

@@ -5,43 +5,40 @@ using FileTime.Core.Timeline;
namespace FileTime.Tools.VirtualDiskSources; namespace FileTime.Tools.VirtualDiskSources;
public class VirtualDiskContentProvider : SubContentProviderBase, IVirtualDiskContentProvider public sealed class VirtualDiskContentProvider : SubContentProviderBase, IVirtualDiskContentProvider
{ {
private readonly IContentAccessorFactory _contentAccessorFactory; private readonly IContentAccessorFactory _contentAccessorFactory;
public VirtualDiskContentProvider( public VirtualDiskContentProvider(
IContentProvider parentContentProvider,
ITimelessContentProvider timelessContentProvider, ITimelessContentProvider timelessContentProvider,
IContentAccessorFactory contentAccessorFactory) IContentAccessorFactory contentAccessorFactory,
: base(parentContentProvider, "virtual-disk", timelessContentProvider) IContentProvider parentContentProvider)
: base(
timelessContentProvider,
contentAccessorFactory,
parentContentProvider,
"virtual-disk"
)
{ {
_contentAccessorFactory = contentAccessorFactory; _contentAccessorFactory = contentAccessorFactory;
} }
public override async Task<byte[]?> GetContentAsync(IElement element, int? maxLength = null, CancellationToken cancellationToken = default) public override async Task<byte[]?> GetContentAsync(IElement element, int? maxLength = null, CancellationToken cancellationToken = default)
{ {
var elementNativePath = element.NativePath!; var parentElementContext = await GetParentElementReaderAsync(element);
var reader = parentElementContext.ContentReader;
var supportedPath = await ParentContentProvider.GetSupportedPathPart(elementNativePath); var subPath = parentElementContext.SubNativePath.Path;
if (supportedPath is null) return null;
var parentItem = await ParentContentProvider.GetItemByNativePathAsync(supportedPath, element.PointInTime);
if (parentItem is not IElement parentElement) return null;
var contentReaderFactory = _contentAccessorFactory.GetContentReaderFactory(parentElement.Provider);
var reader = await contentReaderFactory.CreateContentReaderAsync(parentElement);
await using var readerStream = reader.AsStream(); await using var readerStream = reader.AsStream();
using var discReader = new UdfReader(readerStream); using var discReader = new UdfReader(readerStream);
var subPath = elementNativePath.Path.Substring(supportedPath.Path.Length + 2 + Constants.SubContentProviderRootContainer.Length);
var fileInfo = discReader.GetFileInfo(subPath); var fileInfo = discReader.GetFileInfo(subPath);
await using var contentReader = fileInfo.Open(FileMode.Open, FileAccess.Read); await using var contentReader = fileInfo.Open(FileMode.Open, FileAccess.Read);
var data = new byte[1024 * 1024]; var data = new byte[1024 * 1024];
var readAsync = await contentReader.ReadAsync(data, cancellationToken); var readAsync = await contentReader.ReadAsync(data, cancellationToken);
return data[0..readAsync].ToArray(); return data[..readAsync].ToArray();
} }
public override VolumeSizeInfo? GetVolumeSizeInfo(FullName path) public override VolumeSizeInfo? GetVolumeSizeInfo(FullName path)

View File

@@ -3,7 +3,7 @@ using FileTime.Core.Timeline;
namespace FileTime.Tools.VirtualDiskSources; namespace FileTime.Tools.VirtualDiskSources;
public class VirtualDiskContentProviderFactory : IVirtualDiskContentProviderFactory public sealed class VirtualDiskContentProviderFactory : IVirtualDiskContentProviderFactory
{ {
private readonly ITimelessContentProvider _timelessContentProvider; private readonly ITimelessContentProvider _timelessContentProvider;
private readonly IContentAccessorFactory _contentAccessorFactory; private readonly IContentAccessorFactory _contentAccessorFactory;
@@ -17,5 +17,5 @@ public class VirtualDiskContentProviderFactory : IVirtualDiskContentProviderFact
} }
public IVirtualDiskContentProvider Create(IContentProvider parentContentProvider) public IVirtualDiskContentProvider Create(IContentProvider parentContentProvider)
=> new VirtualDiskContentProvider(parentContentProvider, _timelessContentProvider, _contentAccessorFactory); => new VirtualDiskContentProvider(_timelessContentProvider, _contentAccessorFactory, parentContentProvider);
} }

View File

@@ -2,7 +2,7 @@
namespace FileTime.Tools.VirtualDiskSources; namespace FileTime.Tools.VirtualDiskSources;
public class VirtualDiskContentReader : IContentReader public sealed class VirtualDiskContentReader : IContentReader
{ {
private readonly Stream _stream; private readonly Stream _stream;
private readonly ICollection<IDisposable> _disposables; private readonly ICollection<IDisposable> _disposables;

View File

@@ -4,37 +4,28 @@ using FileTime.Core.Models;
namespace FileTime.Tools.VirtualDiskSources; namespace FileTime.Tools.VirtualDiskSources;
public class VirtualDiskContentReaderFactory : IContentReaderFactory<VirtualDiskContentProvider> public sealed class VirtualDiskContentReaderFactory : SubContentReaderBase<VirtualDiskContentProvider>
{ {
private readonly IContentAccessorFactory _contentAccessorFactory;
public VirtualDiskContentReaderFactory(IContentAccessorFactory contentAccessorFactory) public VirtualDiskContentReaderFactory(IContentAccessorFactory contentAccessorFactory)
: base(contentAccessorFactory)
{ {
_contentAccessorFactory = contentAccessorFactory;
} }
public async Task<IContentReader> CreateContentReaderAsync(IElement element) public override async Task<IContentReader> CreateContentReaderAsync(IElement element)
{ {
if (element.Provider is not VirtualDiskContentProvider provider) if (element.Provider is not VirtualDiskContentProvider provider)
throw new ArgumentException( throw new ArgumentException(
"Provider must be VirtualDiskContentProvider, but it is " + element.Provider.GetType(), $"Provider must be {nameof(VirtualDiskContentProvider)}, but it is " + element.Provider.GetType(),
nameof(element)); nameof(element));
var elementNativePath = element.NativePath!; var parentElementReaderContext = await GetParentElementReaderAsync(element, provider);
var reader = parentElementReaderContext.ContentReader;
var supportedPath = (await provider.ParentContentProvider.GetSupportedPathPart(elementNativePath))!; var subPath = parentElementReaderContext.SubNativePath;
var parentElement = (IElement) await provider.ParentContentProvider.GetItemByNativePathAsync(supportedPath, element.PointInTime);
var contentReaderFactory = _contentAccessorFactory.GetContentReaderFactory(parentElement.Provider);
var reader = await contentReaderFactory.CreateContentReaderAsync(parentElement);
var readerStream = reader.AsStream(); var readerStream = reader.AsStream();
var discReader = new UdfReader(readerStream); var discReader = new UdfReader(readerStream);
var subPath = elementNativePath.Path.Substring(supportedPath.Path.Length + 2 + Constants.SubContentProviderRootContainer.Length); var fileInfo = discReader.GetFileInfo(subPath.Path);
var fileInfo = discReader.GetFileInfo(subPath);
var contentReader = fileInfo.Open(FileMode.Open, FileAccess.Read); var contentReader = fileInfo.Open(FileMode.Open, FileAccess.Read);

View File

@@ -8,7 +8,7 @@ using FileTime.Core.Timeline;
namespace FileTime.Tools.VirtualDiskSources; namespace FileTime.Tools.VirtualDiskSources;
public class VirtualDiskSubContentProvider : IVirtualDiskSubContentProvider public sealed class VirtualDiskSubContentProvider : IVirtualDiskSubContentProvider
{ {
private readonly IContentAccessorFactory _contentAccessorFactory; private readonly IContentAccessorFactory _contentAccessorFactory;
private readonly ITimelessContentProvider _timelessContentProvider; private readonly ITimelessContentProvider _timelessContentProvider;