Streamfab.keepstreams.generic.hook-smeagol-ther... «ESSENTIAL»
The pattern mirrors Read ; the hook receives the buffer before the inner write and again after the write completes. 3.4 Seek , SetLength , Flush All these methods follow the same pre‑hook → inner operation → post‑hook flow. The async variants are implemented using ValueTask when possible to avoid allocations. 3.5 Disposal protected override void Dispose(bool disposing)
(The exact name you gave is truncated, so the description is written to cover the most common “Hook‑Smeagol” implementation that lives inside the StreamFab.KeepStreams.Generic namespace.) Hook‑Smeagring (often abbreviated simply as Smeagol ) is a generic, stream‑interception hook that lives in the KeepStreams library. Its primary responsibilities are:
The async version ( DisposeAsync ) follows the same order with await . | Hook name | Typical use‑case | Sample code fragment | |-----------|------------------|----------------------| | LoggingHook | Write a line‑by‑line trace of every read/write, optionally throttling large payloads. | await logger.LogAsync($"bytesRead bytes read from ctx.StreamId"); | | CompressionHook | Transparent GZip/Deflate compression on the fly. | var compressor = new GZipStream(_inner, CompressionMode.Compress, leaveOpen:true); | | EncryptionHook | Apply AES‑CTR or ChaCha20 encryption per‑chunk. | Array.Copy(_cipher.TransformBlock(buffer, offset, count), 0, buffer, offset, count); | | MetricsHook | Emit Prometheus counters or OpenTelemetry spans for each operation. | meter.CreateHistogram<long>("stream.read.bytes").Record(bytesRead); | | ThrottlingHook | Enforce a max‑bytes‑per‑second quota. | await _rateLimiter.WaitAsync(bytesRead, cancellationToken); | Why the name “Smeagol”? In the original open‑source demo the author likened the hook to Smeagol – it “follows” the stream everywhere, silently observing and occasionally meddling. The name stuck and became part of the public API. 5. Extending the hook – writing your own THook 5.1 Minimal stub public sealed class MyCustomHook : IStreamHook
return bytesRead;
// 1. Pre‑hook (may adjust the requested length) _hook.BeforeReadAsync(_ctx, destination, cancellationToken);
return bytesRead;
// 1. Hook gets a chance to modify the request (e.g., apply a read‑limit) _hook.BeforeRead(_ctx, buffer, offset, count); StreamFab.KeepStreams.Generic.Hook-Smeagol-TheR...
var inner = provider.GetRequiredService<FileStream>(); var factory = provider.GetRequiredService<IHookFactory<MyCustomHook>>(); return new HookSmeagol<MyCustomHook>(inner, factory.Create(provider)); ); HookSmeagol can be stacked :
services.AddSingleton<IHookFactory<MyCustomHook>, MyCustomHookFactory>(); services.AddTransient(typeof(Stream), provider =>
public sealed class LoggingHook : IStreamHook { public void BeforeRead(IHookContext ctx, byte[] buffer, int offset, int count) => Console.WriteLine($"[LOG] About to read The pattern mirrors Read ; the hook receives
// Then the inner stream is disposed (unless the hook says otherwise) _inner.Dispose(); base.Dispose(disposing);
| Responsibility | Why it matters | |----------------|----------------| | inbound/outbound data flowing through any System.IO.Stream ‑derived object without breaking the original contract. | Enables logging, diagnostics, transformation, or throttling of data pipelines (e.g., network sockets, file streams, compression streams). | | Preserve the original stream’s semantics (async/sync, seeking, length, timeouts). | Guarantees drop‑in replacement – callers do not need to change their code. | | Compose multiple hooks (e.g., logging + encryption + compression) in a deterministic order. | Keeps the pipeline modular and testable. | | Dispose safely – the hook forwards Dispose / DisposeAsync while also releasing its own resources (buffers, diagnostic listeners). | Prevents resource leaks in long‑running services. |
public void BeforeRead(IHookContext ctx, byte[] buffer, int offset, int count) /* … */ public void AfterRead(IHookContext ctx, byte[] buffer, int offset, int bytesRead) /* … */ | await logger