using System; using System.IO; using System.Net.Http; using System.Runtime.InteropServices; using System.Threading; using System.Threading.Tasks; namespace Elwig.Helpers { static partial class Extensions { [LibraryImport("msvcrt.dll")] [UnmanagedCallConv(CallConvs = [typeof(System.Runtime.CompilerServices.CallConvCdecl)])] private static unsafe partial int memcmp(void* b1, void* b2, long count); public static unsafe int CompareBuffers(char[] buffer1, int offset1, char[] buffer2, int offset2, int count) { fixed (char* b1 = buffer1, b2 = buffer2) { return memcmp(b1 + offset1, b2 + offset2, count); } } public static string? ReadUntil(this StreamReader reader, string delimiter) { return ReadUntil(reader, delimiter.ToCharArray()); } public static string? ReadUntil(this StreamReader reader, char delimiter) { return ReadUntil(reader, [ delimiter ]); } public static string? ReadUntil(this StreamReader reader, char[] delimiter) { var buf = new char[512]; int bufSize = 0, ret; while (!reader.EndOfStream && bufSize < buf.Length - 1) { if ((ret = reader.Read()) == -1) return null; buf[bufSize++] = (char)ret; if (bufSize >= delimiter.Length && CompareBuffers(buf, bufSize - delimiter.Length, delimiter, 0, delimiter.Length) == 0) return new string(buf, 0, bufSize); } return null; } public static Task ReadUntilAsync(this StreamReader reader, string delimiter) { return ReadUntilAsync(reader, delimiter.ToCharArray()); } public static Task ReadUntilAsync(this StreamReader reader, char delimiter) { return ReadUntilAsync(reader, [ delimiter ]); } public static async Task ReadUntilAsync(this StreamReader reader, char[] delimiter) { var buf = new char[512]; int bufSize = 0; var tmpBuf = new char[1]; while (!reader.EndOfStream && bufSize < buf.Length - 1) { if ((await reader.ReadAsync(tmpBuf, 0, 1)) != 1) return null; buf[bufSize++] = tmpBuf[0]; if (bufSize >= delimiter.Length && CompareBuffers(buf, bufSize - delimiter.Length, delimiter, 0, delimiter.Length) == 0) return new string(buf, 0, bufSize); } return null; } public static async Task CopyToAsync(this Stream source, Stream destination, int bufferSize, IProgress? progress = null, CancellationToken cancellationToken = default) { ArgumentNullException.ThrowIfNull(source); if (!source.CanRead) throw new ArgumentException("Has to be readable", nameof(source)); ArgumentNullException.ThrowIfNull(destination); if (!destination.CanWrite) throw new ArgumentException("Has to be writable", nameof(destination)); ArgumentOutOfRangeException.ThrowIfNegative(bufferSize); var buffer = new byte[bufferSize]; long totalBytesRead = 0; int bytesRead; while ((bytesRead = await source.ReadAsync(buffer, cancellationToken).ConfigureAwait(false)) != 0) { await destination.WriteAsync(buffer.AsMemory(0, bytesRead), cancellationToken).ConfigureAwait(false); totalBytesRead += bytesRead; progress?.Report(totalBytesRead); } } public static async Task DownloadAsync(this HttpClient client, string requestUri, Stream destination, IProgress? progress = null, CancellationToken cancellationToken = default) { using var response = await client.GetAsync(requestUri, HttpCompletionOption.ResponseHeadersRead, cancellationToken); response.EnsureSuccessStatusCode(); var contentLength = response.Content.Headers.ContentLength; using var download = await response.Content.ReadAsStreamAsync(cancellationToken); if (progress == null || !contentLength.HasValue) { await download.CopyToAsync(destination, cancellationToken); return; } var relativeProgress = new Progress(totalBytes => progress.Report((double)totalBytes / contentLength.Value)); await download.CopyToAsync(destination, 81920, relativeProgress, cancellationToken); progress.Report(100.0); } } }