using System.Net;
namespace DeepDrftManager.Services;
///
/// An that streams a source stream to the wire while reporting cumulative
/// bytes written after each chunk. This is the single source of truth for both the upload progress
/// meter and the idle/heartbeat timeout: every reported tick advances the UI and resets the
/// idle deadline, so one mechanism feeds both concerns.
///
///
/// Wrap the audio payload (not the whole multipart container) so
/// returns the file length and the reported byte counts map directly onto "bytes of this file".
///
public sealed class ProgressStreamContent : HttpContent
{
// 80 KB: large enough to keep the socket fed on a healthy link, small enough that a stalled
// connection trips the idle window without a multi-MB write swallowing the whole heartbeat budget.
private const int CopyBufferSize = 81_920;
private readonly Stream _source;
private readonly long _length;
private readonly Action _onBytesWritten;
/// The payload stream. Read once, sequentially — not seekable-rewound.
/// Total bytes the source will yield; sets Content-Length and the meter denominator.
/// Invoked after each chunk with the cumulative bytes written so far.
public ProgressStreamContent(Stream source, long length, Action onBytesWritten)
{
_source = source;
_length = length;
_onBytesWritten = onBytesWritten;
}
// Token-aware overload (.NET 5+): HttpClient calls this on the send path and passes the request's
// CancellationToken, so the idle-heartbeat CTS aborts an in-flight read/write promptly — not just
// between chunks. The parameterless base override delegates here with CancellationToken.None.
protected override Task SerializeToStreamAsync(Stream stream, TransportContext? context, CancellationToken cancellationToken)
=> CopyAsync(stream, cancellationToken);
protected override Task SerializeToStreamAsync(Stream stream, TransportContext? context)
=> CopyAsync(stream, CancellationToken.None);
private async Task CopyAsync(Stream stream, CancellationToken cancellationToken)
{
var buffer = new byte[CopyBufferSize];
long written = 0;
int read;
while ((read = await _source.ReadAsync(buffer, cancellationToken)) > 0)
{
await stream.WriteAsync(buffer.AsMemory(0, read), cancellationToken);
written += read;
// Report after the bytes are on the wire — a tick means real forward progress, which is
// exactly the signal the idle heartbeat must reset on.
_onBytesWritten(written);
}
}
protected override bool TryComputeLength(out long length)
{
length = _length;
return true;
}
}