Trying to do a trick with FileStreams - making a file look a few bytes shorter

ikantspelwurdz

Well-known member
Joined
Dec 8, 2009
Messages
49
Programming Experience
1-3
I'm not an expert on how stream's work in .NET. I have a file that is basically a 7z archive, except the first 667 bytes are in XML. 7zip.exe itself can extract this file just fine.

I am trying to read it using a freeware library called "SharpCompress." It chokes when reading this file. I thought that I could trick it by opening a FileStream to the file, and setting the position ahead to 667. Unfortunately, this did not work. In looking at the SharpCompress library, I found out why - here is a function in the SevenZipArchive class (it's in C#, apologies):
VB.NET:
        private void LoadFactory(Stream stream)
        {
            if (factory == null)
            {
                stream.Position = 0;
                factory = new SevenZipHeaderFactory(stream);
            }
        }
What I think I want to do is have a stream that starts reading at the file's 667 position, stops reading at the end, but reports its Position as 0 and its Length as 667 bytes less than what the file actually is. The object itself should not have access to the entire file - just the array of bytes I want from the file.

Is this possible? I'm not completely familiar with how Streams work, but it seems to me like this should be possible.
 

jmcilhinney

VB.NET Forum Moderator
Staff member
Joined
Aug 17, 2004
Messages
14,314
Location
Sydney, Australia
Programming Experience
10+
That LoadFactory method accepts any Stream so you can transfer only the data you want to a MemoryStream first, e.g.
Dim fileContents = File.ReadAllBytes("file path here")
Dim startPosition = 667

Using ms As New MemoryStream(fileContents, startPosition, fileContents.Length - startPosition)
    'Pass ms to LoadFactory here.
End Using
 

ikantspelwurdz

Well-known member
Joined
Dec 8, 2009
Messages
49
Programming Experience
1-3
The files can be several gigabytes in size. Loading it all into memory isn't going to work - the stream chaining needs to be "lazy."

I am at the moment experimenting with making my own stream class. I tried inheriting FileStream and overriding Position, Seek, and Length, but that didn't seem to work - setting Position invoked my Seek and not FileStream's Seek, giving wrong results. I'm going to try just inheriting Stream, and wrapping a FileStream as a private property, and to see if that works any better.
 

jmcilhinney

VB.NET Forum Moderator
Staff member
Joined
Aug 17, 2004
Messages
14,314
Location
Sydney, Australia
Programming Experience
10+
I'm going to try just inheriting Stream, and wrapping a FileStream as a private property, and to see if that works any better.
That sounds like the way to go. You could simply pass through every member to the corresponding member of the internal FileStream except the Position property and/or Seek method and have them use an offset that you set via the constructor and/or a property.
 

ikantspelwurdz

Well-known member
Joined
Dec 8, 2009
Messages
49
Programming Experience
1-3
The fact that inheritance didn't work was really bugging me. But I think I figured it out. Here's my new class code:
VB.NET:
Imports System.IO

Public Class FileBodyStream
    Inherits FileStream

    Private HeaderLength As Long

    Sub New(newPath As String, newHeaderLength As Long)
        MyBase.New(newPath, FileMode.Open)
        MyBase.Position = newHeaderLength
        HeaderLength = newHeaderLength
    End Sub

    Public Overrides Property Position As Long
        Get
            Return MyBase.Position - HeaderLength
        End Get
        Set(value As Long)
            MyBase.Position = value
        End Set
    End Property

    Public Overrides ReadOnly Property Length As Long
        Get
            Return MyBase.Length - HeaderLength
        End Get
    End Property

    Public Overrides Function Seek(offset As Long, origin As SeekOrigin) As Long
        If (origin = SeekOrigin.Begin) Then
            Return MyBase.Seek(offset + HeaderLength, origin)
        Else
            Return MyBase.Seek(offset, origin)
        End If
    End Function

End Class
The thing I was doing wrong before was that my position setter looked like this:
VB.NET:
MyBase.Position = value + HeaderLength
But when I call the Position setter, it invokes my offset-corrected "Seek" function, and not FileStream.Seek. Why would it do that? I thought calling MyBase would invoke the parent class's functions, and not my own. Anyway, the end result is that the new Position gets offset by the HeaderLength twice, which is wrong. By leaving the Position setter alone, the offset is applied just once, and everything seems to work. The Position getter still needs to be corrected, though.

Any comments or suggestions for my code? Am I forgetting anything? I do need to override Seek, because the SharpCompress library may call Seek directly.
 
Top Bottom