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; } }