diff --git a/src/Core/FileTime.Core.ContentAccess/Helper.cs b/src/Core/FileTime.Core.ContentAccess/Helper.cs new file mode 100644 index 0000000..86de074 --- /dev/null +++ b/src/Core/FileTime.Core.ContentAccess/Helper.cs @@ -0,0 +1,22 @@ +using FileTime.Core.Models; + +namespace FileTime.Core.ContentAccess; + +internal static class Helper +{ + internal static async Task 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); + } +} \ No newline at end of file diff --git a/src/Core/FileTime.Core.ContentAccess/ParentElementReaderContext.cs b/src/Core/FileTime.Core.ContentAccess/ParentElementReaderContext.cs new file mode 100644 index 0000000..4d35b6a --- /dev/null +++ b/src/Core/FileTime.Core.ContentAccess/ParentElementReaderContext.cs @@ -0,0 +1,5 @@ +using FileTime.Core.Models; + +namespace FileTime.Core.ContentAccess; + +public record ParentElementReaderContext(IContentReader ContentReader, NativePath SubNativePath); \ No newline at end of file diff --git a/src/Core/FileTime.Core.ContentAccess/SubContentProviderBase.cs b/src/Core/FileTime.Core.ContentAccess/SubContentProviderBase.cs index cf5fe3b..b912a30 100644 --- a/src/Core/FileTime.Core.ContentAccess/SubContentProviderBase.cs +++ b/src/Core/FileTime.Core.ContentAccess/SubContentProviderBase.cs @@ -6,13 +6,18 @@ namespace FileTime.Core.ContentAccess; public abstract class SubContentProviderBase : ContentProviderBase { + private readonly IContentAccessorFactory _contentAccessorFactory; + public IContentProvider ParentContentProvider { get; } protected SubContentProviderBase( + ITimelessContentProvider timelessContentProvider, + IContentAccessorFactory contentAccessorFactory, IContentProvider parentContentProvider, - string name, - ITimelessContentProvider timelessContentProvider) : base(name, timelessContentProvider) + string name + ) : base(name, timelessContentProvider) { + _contentAccessorFactory = contentAccessorFactory; ParentContentProvider = parentContentProvider; } @@ -40,4 +45,7 @@ public abstract class SubContentProviderBase : ContentProviderBase public override async ValueTask GetSupportedPathPart(NativePath nativePath) => await ParentContentProvider.GetSupportedPathPart(nativePath); + + protected async Task GetParentElementReaderAsync(IElement element) + => await Helper.GetParentElementReaderAsync(_contentAccessorFactory, element, ParentContentProvider); } \ No newline at end of file diff --git a/src/Core/FileTime.Core.ContentAccess/SubContentReaderBase.cs b/src/Core/FileTime.Core.ContentAccess/SubContentReaderBase.cs new file mode 100644 index 0000000..e3ffcc5 --- /dev/null +++ b/src/Core/FileTime.Core.ContentAccess/SubContentReaderBase.cs @@ -0,0 +1,18 @@ +using FileTime.Core.Models; + +namespace FileTime.Core.ContentAccess; + +public abstract class SubContentReaderBase : IContentReaderFactory where TContentProvider : IContentProvider +{ + private readonly IContentAccessorFactory _contentAccessorFactory; + + protected SubContentReaderBase(IContentAccessorFactory contentAccessorFactory) + { + _contentAccessorFactory = contentAccessorFactory; + } + + public abstract Task CreateContentReaderAsync(IElement element); + + protected async Task GetParentElementReaderAsync(IElement element, SubContentProviderBase provider) + => await Helper.GetParentElementReaderAsync(_contentAccessorFactory, element, provider.ParentContentProvider); +} \ No newline at end of file diff --git a/src/Tools/FileTime.Tools.Compression/ContentProvider/CompressedContentProvider.cs b/src/Tools/FileTime.Tools.Compression/ContentProvider/CompressedContentProvider.cs index 74239fc..1ff67a0 100644 --- a/src/Tools/FileTime.Tools.Compression/ContentProvider/CompressedContentProvider.cs +++ b/src/Tools/FileTime.Tools.Compression/ContentProvider/CompressedContentProvider.cs @@ -1,22 +1,42 @@ -using System.Text; -using FileTime.Core.ContentAccess; +using FileTime.Core.ContentAccess; using FileTime.Core.Models; using FileTime.Core.Timeline; +using SharpCompress.Archives; namespace FileTime.Tools.Compression.ContentProvider; -public class CompressedContentProvider : SubContentProviderBase, ICompressedContentProvider +public sealed class CompressedContentProvider : SubContentProviderBase, ICompressedContentProvider { public CompressedContentProvider( - IContentProvider parentContentProvider, - ITimelessContentProvider timelessContentProvider + ITimelessContentProvider timelessContentProvider, + IContentAccessorFactory contentAccessorFactory, + IContentProvider parentContentProvider ) - : base(parentContentProvider, "compression", timelessContentProvider) + : base( + timelessContentProvider, + contentAccessorFactory, + parentContentProvider, + "compression") { } - public override Task GetContentAsync(IElement element, int? maxLength = null, CancellationToken cancellationToken = default) - => Task.FromResult((byte[]?)"Not implemented..."u8.ToArray()); + public override async Task GetContentAsync(IElement element, int? maxLength = null, CancellationToken cancellationToken = default) + { + 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(); } \ No newline at end of file diff --git a/src/Tools/FileTime.Tools.Compression/ContentProvider/CompressedContentProviderFactory.cs b/src/Tools/FileTime.Tools.Compression/ContentProvider/CompressedContentProviderFactory.cs index 5dc1c2d..6327c48 100644 --- a/src/Tools/FileTime.Tools.Compression/ContentProvider/CompressedContentProviderFactory.cs +++ b/src/Tools/FileTime.Tools.Compression/ContentProvider/CompressedContentProviderFactory.cs @@ -3,17 +3,19 @@ using FileTime.Core.Timeline; namespace FileTime.Tools.Compression.ContentProvider; -public class CompressedContentProviderFactory : ICompressedContentProviderFactory +public sealed class CompressedContentProviderFactory : ICompressedContentProviderFactory { private readonly ITimelessContentProvider _timelessContentProvider; + private readonly IContentAccessorFactory _contentAccessorFactory; - public CompressedContentProviderFactory(ITimelessContentProvider timelessContentProvider) + public CompressedContentProviderFactory( + ITimelessContentProvider timelessContentProvider, + IContentAccessorFactory contentAccessorFactory) { _timelessContentProvider = timelessContentProvider; + _contentAccessorFactory = contentAccessorFactory; } - public ICompressedContentProvider Create(IContentProvider parentContentProvider) - { - return new CompressedContentProvider(parentContentProvider, _timelessContentProvider); - } + public ICompressedContentProvider Create(IContentProvider parentContentProvider) + => new CompressedContentProvider(_timelessContentProvider, _contentAccessorFactory, parentContentProvider); } \ No newline at end of file diff --git a/src/Tools/FileTime.Tools.Compression/ContentProvider/CompressedContentReader.cs b/src/Tools/FileTime.Tools.Compression/ContentProvider/CompressedContentReader.cs new file mode 100644 index 0000000..66cba3a --- /dev/null +++ b/src/Tools/FileTime.Tools.Compression/ContentProvider/CompressedContentReader.cs @@ -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 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; +} \ No newline at end of file diff --git a/src/Tools/FileTime.Tools.Compression/ContentProvider/CompressedContentReaderFactory.cs b/src/Tools/FileTime.Tools.Compression/ContentProvider/CompressedContentReaderFactory.cs new file mode 100644 index 0000000..1ef0ede --- /dev/null +++ b/src/Tools/FileTime.Tools.Compression/ContentProvider/CompressedContentReaderFactory.cs @@ -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 +{ + + public CompressedContentReaderFactory(IContentAccessorFactory contentAccessorFactory) + : base(contentAccessorFactory) + { + } + public override async Task 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); + } +} \ No newline at end of file diff --git a/src/Tools/FileTime.Tools.Compression/Startup.cs b/src/Tools/FileTime.Tools.Compression/Startup.cs index 0441331..5b8e2b2 100644 --- a/src/Tools/FileTime.Tools.Compression/Startup.cs +++ b/src/Tools/FileTime.Tools.Compression/Startup.cs @@ -28,6 +28,7 @@ public static class Startup services.AddSingleton(); services.TryAddSingleton(); services.AddSingleton(); + services.TryAddSingleton, CompressedContentReaderFactory>(); return services; } } \ No newline at end of file diff --git a/src/Tools/FileTime.Tools.VirtualDiskSources/DiscUtilsInitializer.cs b/src/Tools/FileTime.Tools.VirtualDiskSources/DiscUtilsInitializer.cs index 498aa30..169b466 100644 --- a/src/Tools/FileTime.Tools.VirtualDiskSources/DiscUtilsInitializer.cs +++ b/src/Tools/FileTime.Tools.VirtualDiskSources/DiscUtilsInitializer.cs @@ -2,7 +2,7 @@ namespace FileTime.Tools.VirtualDiskSources; -public class DiscUtilsInitializer : IPreStartupHandler +public sealed class DiscUtilsInitializer : IPreStartupHandler { public Task InitAsync() { diff --git a/src/Tools/FileTime.Tools.VirtualDiskSources/VirtualDiskContentProvider.cs b/src/Tools/FileTime.Tools.VirtualDiskSources/VirtualDiskContentProvider.cs index 9facfe9..83d6c2a 100644 --- a/src/Tools/FileTime.Tools.VirtualDiskSources/VirtualDiskContentProvider.cs +++ b/src/Tools/FileTime.Tools.VirtualDiskSources/VirtualDiskContentProvider.cs @@ -5,43 +5,40 @@ using FileTime.Core.Timeline; namespace FileTime.Tools.VirtualDiskSources; -public class VirtualDiskContentProvider : SubContentProviderBase, IVirtualDiskContentProvider +public sealed class VirtualDiskContentProvider : SubContentProviderBase, IVirtualDiskContentProvider { private readonly IContentAccessorFactory _contentAccessorFactory; public VirtualDiskContentProvider( - IContentProvider parentContentProvider, ITimelessContentProvider timelessContentProvider, - IContentAccessorFactory contentAccessorFactory) - : base(parentContentProvider, "virtual-disk", timelessContentProvider) + IContentAccessorFactory contentAccessorFactory, + IContentProvider parentContentProvider) + : base( + timelessContentProvider, + contentAccessorFactory, + parentContentProvider, + "virtual-disk" + ) { _contentAccessorFactory = contentAccessorFactory; } public override async Task GetContentAsync(IElement element, int? maxLength = null, CancellationToken cancellationToken = default) { - var elementNativePath = element.NativePath!; - - var supportedPath = await ParentContentProvider.GetSupportedPathPart(elementNativePath); - 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); - + var parentElementContext = await GetParentElementReaderAsync(element); + var reader = parentElementContext.ContentReader; + var subPath = parentElementContext.SubNativePath.Path; + await using var readerStream = reader.AsStream(); using var discReader = new UdfReader(readerStream); - var subPath = elementNativePath.Path.Substring(supportedPath.Path.Length + 2 + Constants.SubContentProviderRootContainer.Length); var fileInfo = discReader.GetFileInfo(subPath); await using var contentReader = fileInfo.Open(FileMode.Open, FileAccess.Read); var data = new byte[1024 * 1024]; var readAsync = await contentReader.ReadAsync(data, cancellationToken); - return data[0..readAsync].ToArray(); + return data[..readAsync].ToArray(); } public override VolumeSizeInfo? GetVolumeSizeInfo(FullName path) diff --git a/src/Tools/FileTime.Tools.VirtualDiskSources/VirtualDiskContentProviderFactory.cs b/src/Tools/FileTime.Tools.VirtualDiskSources/VirtualDiskContentProviderFactory.cs index 6a334b9..bde0d59 100644 --- a/src/Tools/FileTime.Tools.VirtualDiskSources/VirtualDiskContentProviderFactory.cs +++ b/src/Tools/FileTime.Tools.VirtualDiskSources/VirtualDiskContentProviderFactory.cs @@ -3,7 +3,7 @@ using FileTime.Core.Timeline; namespace FileTime.Tools.VirtualDiskSources; -public class VirtualDiskContentProviderFactory : IVirtualDiskContentProviderFactory +public sealed class VirtualDiskContentProviderFactory : IVirtualDiskContentProviderFactory { private readonly ITimelessContentProvider _timelessContentProvider; private readonly IContentAccessorFactory _contentAccessorFactory; @@ -17,5 +17,5 @@ public class VirtualDiskContentProviderFactory : IVirtualDiskContentProviderFact } public IVirtualDiskContentProvider Create(IContentProvider parentContentProvider) - => new VirtualDiskContentProvider(parentContentProvider, _timelessContentProvider, _contentAccessorFactory); + => new VirtualDiskContentProvider(_timelessContentProvider, _contentAccessorFactory, parentContentProvider); } \ No newline at end of file diff --git a/src/Tools/FileTime.Tools.VirtualDiskSources/VirtualDiskContentReader.cs b/src/Tools/FileTime.Tools.VirtualDiskSources/VirtualDiskContentReader.cs index d5bceb8..684db96 100644 --- a/src/Tools/FileTime.Tools.VirtualDiskSources/VirtualDiskContentReader.cs +++ b/src/Tools/FileTime.Tools.VirtualDiskSources/VirtualDiskContentReader.cs @@ -2,7 +2,7 @@ namespace FileTime.Tools.VirtualDiskSources; -public class VirtualDiskContentReader : IContentReader +public sealed class VirtualDiskContentReader : IContentReader { private readonly Stream _stream; private readonly ICollection _disposables; diff --git a/src/Tools/FileTime.Tools.VirtualDiskSources/VirtualDiskContentReaderFactory.cs b/src/Tools/FileTime.Tools.VirtualDiskSources/VirtualDiskContentReaderFactory.cs index 2a25c0f..30a602d 100644 --- a/src/Tools/FileTime.Tools.VirtualDiskSources/VirtualDiskContentReaderFactory.cs +++ b/src/Tools/FileTime.Tools.VirtualDiskSources/VirtualDiskContentReaderFactory.cs @@ -4,37 +4,28 @@ using FileTime.Core.Models; namespace FileTime.Tools.VirtualDiskSources; -public class VirtualDiskContentReaderFactory : IContentReaderFactory +public sealed class VirtualDiskContentReaderFactory : SubContentReaderBase { - private readonly IContentAccessorFactory _contentAccessorFactory; - - public VirtualDiskContentReaderFactory(IContentAccessorFactory contentAccessorFactory) + public VirtualDiskContentReaderFactory(IContentAccessorFactory contentAccessorFactory) + : base(contentAccessorFactory) { - _contentAccessorFactory = contentAccessorFactory; } - public async Task CreateContentReaderAsync(IElement element) + public override async Task CreateContentReaderAsync(IElement element) { if (element.Provider is not VirtualDiskContentProvider provider) 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)); - - var elementNativePath = element.NativePath!; - - var supportedPath = (await provider.ParentContentProvider.GetSupportedPathPart(elementNativePath))!; - - var parentElement = (IElement) await provider.ParentContentProvider.GetItemByNativePathAsync(supportedPath, element.PointInTime); - - - var contentReaderFactory = _contentAccessorFactory.GetContentReaderFactory(parentElement.Provider); - var reader = await contentReaderFactory.CreateContentReaderAsync(parentElement); + + var parentElementReaderContext = await GetParentElementReaderAsync(element, provider); + var reader = parentElementReaderContext.ContentReader; + var subPath = parentElementReaderContext.SubNativePath; var readerStream = reader.AsStream(); 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.Path); var contentReader = fileInfo.Open(FileMode.Open, FileAccess.Read); diff --git a/src/Tools/FileTime.Tools.VirtualDiskSources/VirtualDiskSubContentProvider.cs b/src/Tools/FileTime.Tools.VirtualDiskSources/VirtualDiskSubContentProvider.cs index 1b55106..4667f96 100644 --- a/src/Tools/FileTime.Tools.VirtualDiskSources/VirtualDiskSubContentProvider.cs +++ b/src/Tools/FileTime.Tools.VirtualDiskSources/VirtualDiskSubContentProvider.cs @@ -8,7 +8,7 @@ using FileTime.Core.Timeline; namespace FileTime.Tools.VirtualDiskSources; -public class VirtualDiskSubContentProvider : IVirtualDiskSubContentProvider +public sealed class VirtualDiskSubContentProvider : IVirtualDiskSubContentProvider { private readonly IContentAccessorFactory _contentAccessorFactory; private readonly ITimelessContentProvider _timelessContentProvider;