Skip to content

Instantly share code, notes, and snippets.

@aynurin
Last active October 23, 2022 13:28
Show Gist options
  • Save aynurin/5875898 to your computer and use it in GitHub Desktop.
Save aynurin/5875898 to your computer and use it in GitHub Desktop.
SeekStream allows seeking on non-seekable streams by buffering read data. The stream is not writable yet.
using System;
using System.IO;
namespace Contribs.aynurin
{
/// <summary>
/// SeekStream allows seeking on non-seekable streams by buffering read data. The stream is not writable yet.
/// </summary>
public class SeekStream : Stream
{
private readonly Stream _baseStream;
private readonly MemoryStream _innerStream;
private readonly int _bufferSize = 64 * 1024;
private readonly byte[] _buffer;
public SeekStream(Stream baseStream)
: this(baseStream, 64 * 1024)
{
}
public SeekStream(Stream baseStream, int bufferSize) : base()
{
_baseStream = baseStream;
_bufferSize = bufferSize;
_buffer = new byte[_bufferSize];
_innerStream = new MemoryStream();
}
public override bool CanRead
{
get { return _baseStream.CanRead; }
}
public override bool CanSeek
{
get { return _baseStream.CanRead; }
}
public override bool CanWrite
{
get { return false; }
}
public override long Position
{
get { return _innerStream.Position; }
set
{
if (value > _baseStream.Position)
FastForward(value);
_innerStream.Position = value;
}
}
public Stream BaseStream
{
get { return _baseStream; }
}
public override void Flush()
{
_baseStream.Flush();
}
private void FastForward(long position = -1)
{
while ((position == -1 || position > this.Length) && ReadChunk() > 0)
{
// fast-forward
}
}
private int ReadChunk()
{
int thisRead, read = 0;
long pos = _innerStream.Position;
do
{
thisRead = _baseStream.Read(_buffer, 0, _bufferSize - read);
_innerStream.Write(_buffer, 0, thisRead);
read += thisRead;
} while (read < _bufferSize && thisRead > 0);
_innerStream.Position = pos;
return read;
}
public override int Read(byte[] buffer, int offset, int count)
{
FastForward(offset + count);
return _innerStream.Read(buffer, offset, count);
}
public override int ReadByte()
{
FastForward(this.Position + 1);
return base.ReadByte();
}
public override long Seek(long offset, SeekOrigin origin)
{
long pos = -1;
if (origin == SeekOrigin.Begin)
pos = offset;
else if (origin == SeekOrigin.Current)
pos = _innerStream.Position + offset;
FastForward(pos);
return _innerStream.Seek(offset, origin);
}
protected override void Dispose(bool disposing)
{
base.Dispose(disposing);
if (!disposing)
{
_innerStream.Dispose();
_baseStream.Dispose();
}
}
public override long Length
{
get { return _innerStream.Length; }
}
public override void SetLength(long value)
{
throw new NotSupportedException();
}
public override void Write(byte[] buffer, int offset, int count)
{
throw new NotSupportedException();
}
}
}
using Contribs.aynurin;
using Microsoft.VisualStudio.TestTools.UnitTesting;
using System.IO;
namespace Tests.SeekStream
{
/// <summary>
/// a small test for <see cref="SeekStream" />
/// </summary>
[TestClass]
public class SeekStreamTests
{
[TestMethod]
public void ProcessTestFiles()
{
var stream1 = new MemoryStream(new byte[] { 1, 2, 3, 4, 5, 6, 7, 8, 9 });
var stream2 = new MemoryStream(new byte[] { 1, 2, 3, 4, 5, 6, 7, 8, 9 });
var seekStream = new global::Contribs.aynurin.SeekStream(stream1);
Assert.AreEqual(seekStream.ReadByte(), stream2.ReadByte());
seekStream.Seek(3, SeekOrigin.Begin);
stream2.Seek(3, SeekOrigin.Begin);
Assert.AreEqual(seekStream.ReadByte(), stream2.ReadByte());
seekStream.Seek(3, SeekOrigin.Current);
stream2.Seek(3, SeekOrigin.Current);
Assert.AreEqual(seekStream.ReadByte(), stream2.ReadByte());
seekStream.Seek(-2, SeekOrigin.Current);
stream2.Seek(-2, SeekOrigin.Current);
Assert.AreEqual(seekStream.ReadByte(), stream2.ReadByte());
seekStream.Seek(-2, SeekOrigin.End);
stream2.Seek(-2, SeekOrigin.End);
Assert.AreEqual(seekStream.ReadByte(), stream2.ReadByte());
}
}
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment