using System;
using System.Diagnostics;
using System.IO;
using System.Threading;
using System.Threading.Tasks;
namespace TwitchDownloaderCore.Models
{
// Modified from https://stackoverflow.com/a/32724000
public class ThrottledStream : Stream
{
public readonly Stream BaseStream;
public readonly int MaximumBytesPerSecond;
private Stopwatch _watch;
private long _totalBytesRead;
///
/// Initializes a new instance of the class
///
/// The base stream to be read from in a throttled manner
/// The maximum read bandwidth in kibibytes per second, capped at gigabit
public ThrottledStream(Stream @in, int throttleKib)
{
const int ONE_GIGABIT_IN_KIBIBYTES = 122_070;
MaximumBytesPerSecond = Math.Min(throttleKib, ONE_GIGABIT_IN_KIBIBYTES) * 1024;
BaseStream = @in;
}
public override bool CanRead => BaseStream.CanRead;
public override bool CanSeek => BaseStream.CanSeek;
public override bool CanWrite => false;
public override void Flush() { }
public override long Length => BaseStream.Length;
public override long Position
{
get => BaseStream.Position;
set => BaseStream.Position = value;
}
public override int Read(byte[] buffer, int offset, int count)
{
var newCount = GetBytesToReturn(count);
var read = BaseStream.Read(buffer, offset, newCount);
Interlocked.Add(ref _totalBytesRead, read);
return read;
}
public override int Read(Span buffer)
{
var newCount = GetBytesToReturn(buffer.Length);
var read = BaseStream.Read(buffer[..newCount]);
Interlocked.Add(ref _totalBytesRead, read);
return read;
}
public override long Seek(long offset, SeekOrigin origin)
{
return BaseStream.Seek(offset, origin);
}
public override void SetLength(long value) { }
public override void Write(byte[] buffer, int offset, int count) { }
public override void Write(ReadOnlySpan buffer) { }
private int GetBytesToReturn(int count)
{
return GetBytesToReturnAsync(count).GetAwaiter().GetResult();
}
private async Task GetBytesToReturnAsync(int count)
{
if (MaximumBytesPerSecond <= 0)
return count;
_watch ??= Stopwatch.StartNew();
var canSend = (long)(_watch.ElapsedMilliseconds * (MaximumBytesPerSecond / 1000.0));
var diff = (int)(canSend - _totalBytesRead);
if (diff <= 0)
{
var waitInSec = ((diff * -1.0) / (MaximumBytesPerSecond));
await Task.Delay((int)(waitInSec * 1000)).ConfigureAwait(false);
}
if (diff >= count) return count;
return diff > 0 ? diff : Math.Min(1024 * 8, count);
}
}
}