Here is what I came up with, based on the inspiration I got from renestein. Writing the custom stream was trickier than expected (and I'm still not 100% sure if all the locking stuff is correct), but first tests look promising.
So I'm no longer using SaveContent
, and my new GetContent
looks something like this:
protected override NodeContent GetContent(NodeBase node, NodeContentParameters contentParameters)
{
if (contentParameters.AccessType == NodeContentAccess.Read)
{
return NodeContent.CreateReadOnlyContent(new MemoryStream());
}
else
{
var stream = new MyFileContentStream();
MyOwnCode.ProvideStreamSoThatDataSinkCanStartReading(stream);
return NodeContent.CreateImmediateWriteContent(stream);
}
}
And this is the new stream class which allows reading from my data sink and writing by your library at the same time:
internal class MyFileContentStream : Stream
{
private byte[] CurrentBuffer = null;
private int ReadPointer = 0;
private readonly AutoResetEvent NotifyBufferEmpty = new AutoResetEvent(true);
private readonly ManualResetEvent NotifyDataAvailable = new ManualResetEvent(false);
public override void Write(byte[] buffer, int offset, int count)
{
// block until local buffer is empty
NotifyBufferEmpty.WaitOne();
// copy bytes
CurrentBuffer = new byte[count];
ReadPointer = 0;
Buffer.BlockCopy(buffer, offset, CurrentBuffer, 0, count);
// notify new data is available
NotifyDataAvailable.Set();
}
public override int Read(byte[] buffer, int offset, int count)
{
// block until data available
NotifyDataAvailable.WaitOne();
// notify end of stream when no more buffer available
if (CurrentBuffer == null)
return 0;
// copy bytes
var availableBytes = CurrentBuffer.Length - ReadPointer;
var readBytes = Math.Min(count, availableBytes);
Buffer.BlockCopy(CurrentBuffer, ReadPointer, buffer, offset, readBytes);
ReadPointer += readBytes;
// check if we have copied all bytes of the current buffer
if (CurrentBuffer.Length == ReadPointer)
{
CurrentBuffer = null;
// make sure we can no longer read
NotifyDataAvailable.Reset();
// notify buffer empty
NotifyBufferEmpty.Set();
}
return readBytes;
}
public override void Flush()
{
NotifyBufferEmpty.WaitOne();
CurrentBuffer = null;
ReadPointer = 0;
NotifyBufferEmpty.Set();
NotifyDataAvailable.Set();
}
protected override void Dispose(bool disposing)
{
Flush();
base.Dispose(disposing);
}
public override bool CanRead => true;
public override bool CanSeek => false;
public override bool CanWrite => true;
public override long Length => 0;
public override long Position { get => throw new NotSupportedException(); set => throw new NotSupportedException(); }
public override long Seek(long offset, SeekOrigin origin) => throw new NotSupportedException();
public override void SetLength(long value) => throw new NotSupportedException();
}