mirror of
https://github.com/RPCS3/discord-bot.git
synced 2026-01-31 01:25:22 +01:00
153 lines
4.8 KiB
C#
153 lines
4.8 KiB
C#
using System.Buffers;
|
|
using System.IO;
|
|
|
|
namespace CompatBot.Utils;
|
|
|
|
internal class BufferCopyStream : Stream, IDisposable
|
|
{
|
|
private readonly Stream baseStream;
|
|
private bool usedForReads;
|
|
private bool usedForWrites;
|
|
private long position;
|
|
private readonly int bufSize;
|
|
private readonly byte[] writeBuf;
|
|
private readonly byte[] readBuf;
|
|
private int bufStart, bufLength;
|
|
private readonly Lock sync = new();
|
|
private bool disposed;
|
|
|
|
public BufferCopyStream(Stream? baseStream, int bufferSize = 4096)
|
|
{
|
|
if (baseStream == null)
|
|
throw new ArgumentNullException(nameof(baseStream));
|
|
|
|
if (bufferSize < 1)
|
|
throw new ArgumentException("Buffer size cannot be non-positive", nameof(bufferSize));
|
|
|
|
this.baseStream = baseStream;
|
|
bufSize = bufferSize;
|
|
writeBuf = ArrayPool<byte>.Shared.Rent(bufSize);
|
|
readBuf = ArrayPool<byte>.Shared.Rent(16);
|
|
}
|
|
|
|
public override void Flush() => baseStream.Flush();
|
|
|
|
public override int Read(byte[] buffer, int offset, int count)
|
|
{
|
|
usedForReads = true;
|
|
if (usedForWrites)
|
|
throw new InvalidOperationException("Stream was used for writes before");
|
|
|
|
var useTempBuf = count < 16;
|
|
const int mask = ~0b1111;
|
|
try
|
|
{
|
|
int result;
|
|
if (useTempBuf)
|
|
{
|
|
result = baseStream.Read(readBuf, 0, count);
|
|
Buffer.BlockCopy(readBuf, 0, buffer, offset, result);
|
|
}
|
|
else
|
|
result = baseStream.Read(buffer, offset, count & mask); // make count divisible by 16 to workaround mega client issues
|
|
position += result;
|
|
CopyToBuf(buffer, offset, result);
|
|
return result;
|
|
}
|
|
catch (Exception e)
|
|
{
|
|
Config.Log.Error(e, $"Failed to read from the base stream: {nameof(buffer)}.Length={buffer.Length}, {nameof(offset)}={offset}, {nameof(count)}={count}");
|
|
throw;
|
|
}
|
|
}
|
|
|
|
public override long Seek(long offset, SeekOrigin origin) => throw new NotImplementedException();
|
|
|
|
public override void SetLength(long value) => throw new NotImplementedException();
|
|
|
|
public override void Write(byte[] buffer, int offset, int count)
|
|
{
|
|
usedForWrites = true;
|
|
if (usedForReads)
|
|
throw new InvalidOperationException("Stream was used for reads before");
|
|
|
|
baseStream.Write(buffer, offset, count);
|
|
position += count;
|
|
CopyToBuf(buffer, offset, count);
|
|
}
|
|
|
|
protected override void Dispose(bool disposing)
|
|
{
|
|
if (!disposed)
|
|
lock (sync)
|
|
{
|
|
if (!disposed)
|
|
{
|
|
ArrayPool<byte>.Shared.Return(writeBuf);
|
|
ArrayPool<byte>.Shared.Return(readBuf);
|
|
disposed = true;
|
|
}
|
|
}
|
|
baseStream.Dispose();
|
|
base.Dispose(disposing);
|
|
}
|
|
|
|
void IDisposable.Dispose()
|
|
{
|
|
if (!disposed)
|
|
lock (sync)
|
|
{
|
|
if (!disposed)
|
|
{
|
|
ArrayPool<byte>.Shared.Return(writeBuf);
|
|
ArrayPool<byte>.Shared.Return(readBuf);
|
|
disposed = true;
|
|
}
|
|
}
|
|
baseStream.Dispose();
|
|
base.Dispose();
|
|
}
|
|
|
|
private void CopyToBuf(byte[] buffer, int offset, int count)
|
|
{
|
|
if (count >= bufSize)
|
|
{
|
|
bufStart = 0;
|
|
bufLength = bufSize;
|
|
Buffer.BlockCopy(buffer, offset + count - bufSize, writeBuf, bufStart, bufLength);
|
|
}
|
|
else
|
|
{
|
|
// copy as much data as we can to the end of the buffer
|
|
bufStart = (bufStart + bufLength) % bufSize;
|
|
bufLength = Math.Min(bufSize - bufStart, count);
|
|
Buffer.BlockCopy(buffer, offset, writeBuf, bufStart, bufLength);
|
|
// if there's still more data, loop it around to the beginning
|
|
if (bufLength < count)
|
|
{
|
|
Buffer.BlockCopy(buffer, offset + bufLength, writeBuf, 0, count-bufLength);
|
|
bufLength = count;
|
|
}
|
|
}
|
|
}
|
|
|
|
public byte[] GetBufferedBytes()
|
|
{
|
|
var result = new byte[bufLength];
|
|
var partLength = Math.Min(bufSize - bufStart, bufLength);
|
|
Buffer.BlockCopy(writeBuf, bufStart, result, 0, partLength);
|
|
if (partLength < bufLength)
|
|
Buffer.BlockCopy(writeBuf, 0, result, partLength, bufLength - partLength);
|
|
return result;
|
|
}
|
|
|
|
public override bool CanRead => baseStream.CanRead;
|
|
public override bool CanSeek => false;
|
|
public override bool CanWrite => baseStream.CanWrite;
|
|
public override long Length => baseStream.Length;
|
|
public override long Position
|
|
{
|
|
get => position;
|
|
set => throw new InvalidOperationException();
|
|
}
|
|
} |