aboutsummaryrefslogtreecommitdiff
path: root/lib/Utils/src/IO
diff options
context:
space:
mode:
authorLibravatar vnugent <public@vaughnnugent.com>2023-01-08 16:01:54 -0500
committerLibravatar vnugent <public@vaughnnugent.com>2023-01-08 16:01:54 -0500
commitde94d788e9a47432a7630a8215896b8dd3628599 (patch)
tree666dec06eef861d101cb6948aff52a3d354c8d73 /lib/Utils/src/IO
parentbe6dc557a3b819248b014992eb96c1cb21f8112b (diff)
Reorder + analyzer cleanup
Diffstat (limited to 'lib/Utils/src/IO')
-rw-r--r--lib/Utils/src/IO/ArrayPoolStreamBuffer.cs70
-rw-r--r--lib/Utils/src/IO/BackingStream.cs181
-rw-r--r--lib/Utils/src/IO/FileOperations.cs105
-rw-r--r--lib/Utils/src/IO/IDataAccumulator.cs64
-rw-r--r--lib/Utils/src/IO/ISlindingWindowBuffer.cs91
-rw-r--r--lib/Utils/src/IO/IVnTextReader.cs72
-rw-r--r--lib/Utils/src/IO/InMemoryTemplate.cs196
-rw-r--r--lib/Utils/src/IO/IsolatedStorageDirectory.cs154
-rw-r--r--lib/Utils/src/IO/SlidingWindowBufferExtensions.cs213
-rw-r--r--lib/Utils/src/IO/TemporayIsolatedFile.cs57
-rw-r--r--lib/Utils/src/IO/VnMemoryStream.cs469
-rw-r--r--lib/Utils/src/IO/VnStreamReader.cs180
-rw-r--r--lib/Utils/src/IO/VnStreamWriter.cs292
-rw-r--r--lib/Utils/src/IO/VnTextReaderExtensions.cs223
-rw-r--r--lib/Utils/src/IO/WriteOnlyBufferedStream.cs255
15 files changed, 2622 insertions, 0 deletions
diff --git a/lib/Utils/src/IO/ArrayPoolStreamBuffer.cs b/lib/Utils/src/IO/ArrayPoolStreamBuffer.cs
new file mode 100644
index 0000000..df366e3
--- /dev/null
+++ b/lib/Utils/src/IO/ArrayPoolStreamBuffer.cs
@@ -0,0 +1,70 @@
+/*
+* Copyright (c) 2022 Vaughn Nugent
+*
+* Library: VNLib
+* Package: VNLib.Utils
+* File: ArrayPoolStreamBuffer.cs
+*
+* ArrayPoolStreamBuffer.cs is part of VNLib.Utils which is part of the larger
+* VNLib collection of libraries and utilities.
+*
+* VNLib.Utils is free software: you can redistribute it and/or modify
+* it under the terms of the GNU General Public License as published
+* by the Free Software Foundation, either version 2 of the License,
+* or (at your option) any later version.
+*
+* VNLib.Utils is distributed in the hope that it will be useful,
+* but WITHOUT ANY WARRANTY; without even the implied warranty of
+* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+* General Public License for more details.
+*
+* You should have received a copy of the GNU General Public License
+* along with VNLib.Utils. If not, see http://www.gnu.org/licenses/.
+*/
+
+using System;
+using System.Buffers;
+
+namespace VNLib.Utils.IO
+{
+ internal class ArrayPoolStreamBuffer<T> : ISlindingWindowBuffer<T>
+ {
+ private readonly ArrayPool<T> _pool;
+ private T[] _buffer;
+
+ public ArrayPoolStreamBuffer(ArrayPool<T> pool, int bufferSize)
+ {
+ _pool = pool;
+ _buffer = _pool.Rent(bufferSize);
+ }
+
+ public int WindowStartPos { get; set; }
+ public int WindowEndPos { get; set; }
+
+ public Memory<T> Buffer => _buffer.AsMemory();
+
+ public void Advance(int count)
+ {
+ WindowEndPos += count;
+ }
+
+ public void AdvanceStart(int count)
+ {
+ WindowStartPos += count;
+ }
+
+ public void Close()
+ {
+ //Return buffer to pool
+ _pool.Return(_buffer);
+ _buffer = null;
+ }
+
+ public void Reset()
+ {
+ //Reset window positions
+ WindowStartPos = 0;
+ WindowEndPos = 0;
+ }
+ }
+} \ No newline at end of file
diff --git a/lib/Utils/src/IO/BackingStream.cs b/lib/Utils/src/IO/BackingStream.cs
new file mode 100644
index 0000000..cb56b09
--- /dev/null
+++ b/lib/Utils/src/IO/BackingStream.cs
@@ -0,0 +1,181 @@
+/*
+* Copyright (c) 2022 Vaughn Nugent
+*
+* Library: VNLib
+* Package: VNLib.Utils
+* File: BackingStream.cs
+*
+* BackingStream.cs is part of VNLib.Utils which is part of the larger
+* VNLib collection of libraries and utilities.
+*
+* VNLib.Utils is free software: you can redistribute it and/or modify
+* it under the terms of the GNU General Public License as published
+* by the Free Software Foundation, either version 2 of the License,
+* or (at your option) any later version.
+*
+* VNLib.Utils is distributed in the hope that it will be useful,
+* but WITHOUT ANY WARRANTY; without even the implied warranty of
+* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+* General Public License for more details.
+*
+* You should have received a copy of the GNU General Public License
+* along with VNLib.Utils. If not, see http://www.gnu.org/licenses/.
+*/
+
+using System;
+using System.IO;
+using System.Threading;
+using System.Threading.Tasks;
+
+namespace VNLib.Utils.IO
+{
+ /// <summary>
+ /// Provides basic stream support sync/async stream operations to a
+ /// backing stream with virtual event methods. Provides a pass-through
+ /// as best as possbile.
+ /// </summary>
+ public abstract class BackingStream<T> : Stream where T: Stream
+ {
+ /// <summary>
+ /// The backing/underlying stream operations are being performed on
+ /// </summary>
+ protected T BaseStream { get; set; }
+ /// <summary>
+ /// A value that will cause all calls to write to throw <see cref="NotSupportedException"/>
+ /// </summary>
+ protected bool ForceReadOnly { get; set; }
+ ///<inheritdoc/>
+ public override bool CanRead => BaseStream.CanRead;
+ ///<inheritdoc/>
+ public override bool CanSeek => BaseStream.CanSeek;
+ ///<inheritdoc/>
+ public override bool CanWrite => BaseStream.CanWrite && !ForceReadOnly;
+ ///<inheritdoc/>
+ public override long Length => BaseStream.Length;
+ ///<inheritdoc/>
+ public override int WriteTimeout { get => BaseStream.WriteTimeout; set => BaseStream.WriteTimeout = value; }
+ ///<inheritdoc/>
+ public override int ReadTimeout { get => BaseStream.ReadTimeout; set => BaseStream.ReadTimeout = value; }
+ ///<inheritdoc/>
+ public override long Position { get => BaseStream.Position; set => BaseStream.Position = value; }
+ ///<inheritdoc/>
+ public override void Flush()
+ {
+ BaseStream.Flush();
+ OnFlush();
+ }
+ ///<inheritdoc/>
+ public override int Read(byte[] buffer, int offset, int count) => BaseStream.Read(buffer, offset, count);
+ ///<inheritdoc/>
+ public override int Read(Span<byte> buffer) => BaseStream.Read(buffer);
+ ///<inheritdoc/>
+ public override long Seek(long offset, SeekOrigin origin) => BaseStream.Seek(offset, origin);
+ ///<inheritdoc/>
+ public override void SetLength(long value) => BaseStream.SetLength(value);
+ ///<inheritdoc/>
+ public override void Write(byte[] buffer, int offset, int count)
+ {
+ if (ForceReadOnly)
+ {
+ throw new NotSupportedException("Stream is set to readonly mode");
+ }
+ BaseStream.Write(buffer, offset, count);
+ //Call onwrite function
+ OnWrite(count);
+ }
+ ///<inheritdoc/>
+ public override void Write(ReadOnlySpan<byte> buffer)
+ {
+ if (ForceReadOnly)
+ {
+ throw new NotSupportedException("Stream is set to readonly mode");
+ }
+ BaseStream.Write(buffer);
+ //Call onwrite function
+ OnWrite(buffer.Length);
+ }
+ ///<inheritdoc/>
+ public override void Close()
+ {
+ BaseStream.Close();
+ //Call on close function
+ OnClose();
+ }
+
+ /// <summary>
+ /// Raised directly after the base stream is closed, when a call to close is made
+ /// </summary>
+ protected virtual void OnClose() { }
+ /// <summary>
+ /// Raised directly after the base stream is flushed, when a call to flush is made
+ /// </summary>
+ protected virtual void OnFlush() { }
+ /// <summary>
+ /// Raised directly after a successfull write operation.
+ /// </summary>
+ /// <param name="count">The number of bytes written to the stream</param>
+ protected virtual void OnWrite(int count) { }
+
+ ///<inheritdoc/>
+ public override Task<int> ReadAsync(byte[] buffer, int offset, int count, CancellationToken cancellationToken)
+ {
+ return BaseStream.ReadAsync(buffer, offset, count, cancellationToken);
+ }
+ ///<inheritdoc/>
+ public override ValueTask<int> ReadAsync(Memory<byte> buffer, CancellationToken cancellationToken = default)
+ {
+ return BaseStream.ReadAsync(buffer, cancellationToken);
+ }
+ ///<inheritdoc/>
+ public override void CopyTo(Stream destination, int bufferSize) => BaseStream.CopyTo(destination, bufferSize);
+ ///<inheritdoc/>
+ public override Task CopyToAsync(Stream destination, int bufferSize, CancellationToken cancellationToken)
+ {
+ return BaseStream.CopyToAsync(destination, bufferSize, cancellationToken);
+ }
+ ///<inheritdoc/>
+ public override async Task WriteAsync(byte[] buffer, int offset, int count, CancellationToken cancellationToken)
+ {
+ if (ForceReadOnly)
+ {
+ throw new NotSupportedException("Stream is set to readonly mode");
+ }
+ //We want to maintain pass through as much as possible, so supress warning
+#pragma warning disable CA1835 // Prefer the 'Memory'-based overloads for 'ReadAsync' and 'WriteAsync'
+ await BaseStream.WriteAsync(buffer, offset, count, cancellationToken);
+#pragma warning restore CA1835 // Prefer the 'Memory'-based overloads for 'ReadAsync' and 'WriteAsync'
+
+ //Call on-write and pass the number of bytes written
+ OnWrite(count);
+ }
+ ///<inheritdoc/>
+ public override async ValueTask WriteAsync(ReadOnlyMemory<byte> buffer, CancellationToken cancellationToken = default)
+ {
+ if (ForceReadOnly)
+ {
+ throw new NotSupportedException("Stream is set to readonly mode");
+ }
+ await BaseStream.WriteAsync(buffer, cancellationToken);
+ //Call on-write and pass the length
+ OnWrite(buffer.Length);
+ }
+ ///<inheritdoc/>
+ public override async Task FlushAsync(CancellationToken cancellationToken)
+ {
+ await BaseStream.FlushAsync(cancellationToken);
+ //Call onflush
+ OnFlush();
+ }
+
+ ///<inheritdoc/>
+ public override async ValueTask DisposeAsync()
+ {
+ //Dispose the base stream and await it
+ await BaseStream.DisposeAsync();
+ //Call onclose
+ OnClose();
+ //Suppress finalize
+ GC.SuppressFinalize(this);
+ }
+ }
+} \ No newline at end of file
diff --git a/lib/Utils/src/IO/FileOperations.cs b/lib/Utils/src/IO/FileOperations.cs
new file mode 100644
index 0000000..e040da4
--- /dev/null
+++ b/lib/Utils/src/IO/FileOperations.cs
@@ -0,0 +1,105 @@
+/*
+* Copyright (c) 2022 Vaughn Nugent
+*
+* Library: VNLib
+* Package: VNLib.Utils
+* File: FileOperations.cs
+*
+* FileOperations.cs is part of VNLib.Utils which is part of the larger
+* VNLib collection of libraries and utilities.
+*
+* VNLib.Utils is free software: you can redistribute it and/or modify
+* it under the terms of the GNU General Public License as published
+* by the Free Software Foundation, either version 2 of the License,
+* or (at your option) any later version.
+*
+* VNLib.Utils is distributed in the hope that it will be useful,
+* but WITHOUT ANY WARRANTY; without even the implied warranty of
+* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+* General Public License for more details.
+*
+* You should have received a copy of the GNU General Public License
+* along with VNLib.Utils. If not, see http://www.gnu.org/licenses/.
+*/
+
+using System;
+using System.IO;
+using System.Runtime.InteropServices;
+
+namespace VNLib.Utils.IO
+{
+ /// <summary>
+ /// Contains cross-platform optimized filesystem operations.
+ /// </summary>
+ public static class FileOperations
+ {
+ public const int INVALID_FILE_ATTRIBUTES = -1;
+
+ [DllImport("Shlwapi", SetLastError = true, CharSet = CharSet.Auto)]
+ [DefaultDllImportSearchPaths(DllImportSearchPath.System32)]
+ [return:MarshalAs(UnmanagedType.Bool)]
+ private static unsafe extern bool PathFileExists(char* path);
+ [DllImport("kernel32", SetLastError = true, CharSet = CharSet.Auto)]
+ [DefaultDllImportSearchPaths(DllImportSearchPath.System32)]
+ [return:MarshalAs(UnmanagedType.I4)]
+ private static unsafe extern int GetFileAttributes(char* path);
+
+ static readonly bool IsWindows = OperatingSystem.IsWindows();
+ /// <summary>
+ /// Determines if a file exists. If application is current running in the Windows operating system, Shlwapi.PathFileExists is invoked,
+ /// otherwise <see cref="File.Exists(string?)"/> is invoked
+ /// </summary>
+ /// <param name="filePath">the path to the file</param>
+ /// <returns>True if the file can be opened, false otherwise</returns>
+ public static bool FileExists(string filePath)
+ {
+ //If windows is detected, use the unmanged function
+ if (!IsWindows)
+ {
+ return File.Exists(filePath);
+ }
+ unsafe
+ {
+ //Get a char pointer to the file path
+ fixed (char* path = filePath)
+ {
+ //Invoke the winap file function
+ return PathFileExists(path);
+ }
+ }
+ }
+
+ /// <summary>
+ /// If Windows is detected at load time, gets the attributes for the specified file.
+ /// </summary>
+ /// <param name="filePath">The path to the existing file</param>
+ /// <returns>The attributes of the file </returns>
+ /// <exception cref="PathTooLongException"></exception>
+ /// <exception cref="FileNotFoundException"></exception>
+ /// <exception cref="UnauthorizedAccessException"></exception>
+ public static FileAttributes GetAttributes(string filePath)
+ {
+ //If windows is detected, use the unmanged function
+ if (!IsWindows)
+ {
+ return File.GetAttributes(filePath);
+ }
+ unsafe
+ {
+ //Get a char pointer to the file path
+ fixed (char* path = filePath)
+ {
+ //Invoke the winap file function and cast the returned int value to file attributes
+ int attr = GetFileAttributes(path);
+ //Check for error
+ if (attr == INVALID_FILE_ATTRIBUTES)
+ {
+ throw new FileNotFoundException("The requested file was not found", filePath);
+ }
+ //Cast to file attributes and return
+ return (FileAttributes)attr;
+ }
+ }
+ }
+ }
+} \ No newline at end of file
diff --git a/lib/Utils/src/IO/IDataAccumulator.cs b/lib/Utils/src/IO/IDataAccumulator.cs
new file mode 100644
index 0000000..5129a55
--- /dev/null
+++ b/lib/Utils/src/IO/IDataAccumulator.cs
@@ -0,0 +1,64 @@
+/*
+* Copyright (c) 2022 Vaughn Nugent
+*
+* Library: VNLib
+* Package: VNLib.Utils
+* File: IDataAccumulator.cs
+*
+* IDataAccumulator.cs is part of VNLib.Utils which is part of the larger
+* VNLib collection of libraries and utilities.
+*
+* VNLib.Utils is free software: you can redistribute it and/or modify
+* it under the terms of the GNU General Public License as published
+* by the Free Software Foundation, either version 2 of the License,
+* or (at your option) any later version.
+*
+* VNLib.Utils is distributed in the hope that it will be useful,
+* but WITHOUT ANY WARRANTY; without even the implied warranty of
+* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+* General Public License for more details.
+*
+* You should have received a copy of the GNU General Public License
+* along with VNLib.Utils. If not, see http://www.gnu.org/licenses/.
+*/
+
+using System;
+
+namespace VNLib.Utils.IO
+{
+ /// <summary>
+ /// A data structure that represents a sliding window over a buffer
+ /// for resetable forward-only reading or writing
+ /// </summary>
+ /// <typeparam name="T">The accumuation data type</typeparam>
+ public interface IDataAccumulator<T>
+ {
+ /// <summary>
+ /// Gets the number of available items within the buffer
+ /// </summary>
+ int AccumulatedSize { get; }
+ /// <summary>
+ /// The number of elements remaining in the buffer
+ /// </summary>
+ int RemainingSize { get; }
+ /// <summary>
+ /// The remaining space in the internal buffer as a contiguous segment
+ /// </summary>
+ Span<T> Remaining { get; }
+ /// <summary>
+ /// The buffer window over the accumulated data
+ /// </summary>
+ Span<T> Accumulated { get; }
+
+ /// <summary>
+ /// Advances the accumulator buffer window by the specified amount
+ /// </summary>
+ /// <param name="count">The number of elements accumulated</param>
+ void Advance(int count);
+
+ /// <summary>
+ /// Resets the internal state of the accumulator
+ /// </summary>
+ void Reset();
+ }
+} \ No newline at end of file
diff --git a/lib/Utils/src/IO/ISlindingWindowBuffer.cs b/lib/Utils/src/IO/ISlindingWindowBuffer.cs
new file mode 100644
index 0000000..ff4e142
--- /dev/null
+++ b/lib/Utils/src/IO/ISlindingWindowBuffer.cs
@@ -0,0 +1,91 @@
+/*
+* Copyright (c) 2022 Vaughn Nugent
+*
+* Library: VNLib
+* Package: VNLib.Utils
+* File: ISlindingWindowBuffer.cs
+*
+* ISlindingWindowBuffer.cs is part of VNLib.Utils which is part of the larger
+* VNLib collection of libraries and utilities.
+*
+* VNLib.Utils is free software: you can redistribute it and/or modify
+* it under the terms of the GNU General Public License as published
+* by the Free Software Foundation, either version 2 of the License,
+* or (at your option) any later version.
+*
+* VNLib.Utils is distributed in the hope that it will be useful,
+* but WITHOUT ANY WARRANTY; without even the implied warranty of
+* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+* General Public License for more details.
+*
+* You should have received a copy of the GNU General Public License
+* along with VNLib.Utils. If not, see http://www.gnu.org/licenses/.
+*/
+
+using System;
+
+namespace VNLib.Utils.IO
+{
+ /// <summary>
+ /// Represents a sliding window buffer for reading/wiriting data
+ /// </summary>
+ /// <typeparam name="T"></typeparam>
+ public interface ISlindingWindowBuffer<T> : IDataAccumulator<T>
+ {
+ /// <summary>
+ /// The number of elements remaining in the buffer
+ /// </summary>
+ int IDataAccumulator<T>.RemainingSize => Buffer.Length - WindowEndPos;
+ /// <summary>
+ /// The remaining space in the internal buffer as a contiguous segment
+ /// </summary>
+ Span<T> IDataAccumulator<T>.Remaining => RemainingBuffer.Span;
+ /// <summary>
+ /// The buffer window over the accumulated data
+ /// </summary>
+ Span<T> IDataAccumulator<T>.Accumulated => AccumulatedBuffer.Span;
+ /// <summary>
+ /// Gets the number of available items within the buffer
+ /// </summary>
+ int IDataAccumulator<T>.AccumulatedSize => WindowEndPos - WindowStartPos;
+
+ /// <summary>
+ /// The starting positon of the available data within the buffer
+ /// </summary>
+ int WindowStartPos { get; }
+ /// <summary>
+ /// The ending position of the available data within the buffer
+ /// </summary>
+ int WindowEndPos { get; }
+ /// <summary>
+ /// Buffer memory wrapper
+ /// </summary>
+ Memory<T> Buffer { get; }
+
+ /// <summary>
+ /// Releases resources used by the current instance
+ /// </summary>
+ void Close();
+ /// <summary>
+ /// <para>
+ /// Advances the begining of the accumulated data window.
+ /// </para>
+ /// <para>
+ /// This method is used during reading to singal that data
+ /// has been read from the internal buffer and the
+ /// accumulator window can be shifted.
+ /// </para>
+ /// </summary>
+ /// <param name="count">The number of elements to shift by</param>
+ void AdvanceStart(int count);
+
+ /// <summary>
+ /// Gets a window within the buffer of available buffered data
+ /// </summary>
+ Memory<T> AccumulatedBuffer => Buffer[WindowStartPos..WindowEndPos];
+ /// <summary>
+ /// Gets the available buffer window to write data to
+ /// </summary>
+ Memory<T> RemainingBuffer => Buffer[WindowEndPos..];
+ }
+} \ No newline at end of file
diff --git a/lib/Utils/src/IO/IVnTextReader.cs b/lib/Utils/src/IO/IVnTextReader.cs
new file mode 100644
index 0000000..625ba78
--- /dev/null
+++ b/lib/Utils/src/IO/IVnTextReader.cs
@@ -0,0 +1,72 @@
+/*
+* Copyright (c) 2022 Vaughn Nugent
+*
+* Library: VNLib
+* Package: VNLib.Utils
+* File: IVnTextReader.cs
+*
+* IVnTextReader.cs is part of VNLib.Utils which is part of the larger
+* VNLib collection of libraries and utilities.
+*
+* VNLib.Utils is free software: you can redistribute it and/or modify
+* it under the terms of the GNU General Public License as published
+* by the Free Software Foundation, either version 2 of the License,
+* or (at your option) any later version.
+*
+* VNLib.Utils is distributed in the hope that it will be useful,
+* but WITHOUT ANY WARRANTY; without even the implied warranty of
+* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+* General Public License for more details.
+*
+* You should have received a copy of the GNU General Public License
+* along with VNLib.Utils. If not, see http://www.gnu.org/licenses/.
+*/
+
+using System;
+using System.IO;
+using System.Text;
+
+namespace VNLib.Utils.IO
+{
+ /// <summary>
+ /// Represents a streaming text reader with internal buffers
+ /// </summary>
+ public interface IVnTextReader
+ {
+ /// <summary>
+ /// The base stream to read data from
+ /// </summary>
+ Stream BaseStream { get; }
+ /// <summary>
+ /// The character encoding used by the TextReader
+ /// </summary>
+ Encoding Encoding { get; }
+ /// <summary>
+ /// Number of available bytes of buffered data within the current buffer window
+ /// </summary>
+ int Available { get; }
+ /// <summary>
+ /// Gets or sets the line termination used to deliminate a line of data
+ /// </summary>
+ ReadOnlyMemory<byte> LineTermination { get; }
+ /// <summary>
+ /// The unread/available data within the internal buffer
+ /// </summary>
+ Span<byte> BufferedDataWindow { get; }
+ /// <summary>
+ /// Shifts the sliding buffer window by the specified number of bytes.
+ /// </summary>
+ /// <param name="count">The number of bytes read from the buffer</param>
+ void Advance(int count);
+ /// <summary>
+ /// Reads data from the stream into the remaining buffer space for processing
+ /// </summary>
+ void FillBuffer();
+ /// <summary>
+ /// Compacts the available buffer space back to the begining of the buffer region
+ /// and determines if there is room for more data to be buffered
+ /// </summary>
+ /// <returns>The remaining buffer space if any</returns>
+ ERRNO CompactBufferWindow();
+ }
+} \ No newline at end of file
diff --git a/lib/Utils/src/IO/InMemoryTemplate.cs b/lib/Utils/src/IO/InMemoryTemplate.cs
new file mode 100644
index 0000000..ae8bf79
--- /dev/null
+++ b/lib/Utils/src/IO/InMemoryTemplate.cs
@@ -0,0 +1,196 @@
+/*
+* Copyright (c) 2022 Vaughn Nugent
+*
+* Library: VNLib
+* Package: VNLib.Utils
+* File: InMemoryTemplate.cs
+*
+* InMemoryTemplate.cs is part of VNLib.Utils which is part of the larger
+* VNLib collection of libraries and utilities.
+*
+* VNLib.Utils is free software: you can redistribute it and/or modify
+* it under the terms of the GNU General Public License as published
+* by the Free Software Foundation, either version 2 of the License,
+* or (at your option) any later version.
+*
+* VNLib.Utils is distributed in the hope that it will be useful,
+* but WITHOUT ANY WARRANTY; without even the implied warranty of
+* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+* General Public License for more details.
+*
+* You should have received a copy of the GNU General Public License
+* along with VNLib.Utils. If not, see http://www.gnu.org/licenses/.
+*/
+
+using System;
+using System.IO;
+using System.Threading;
+using System.Threading.Tasks;
+
+using VNLib.Utils.Extensions;
+
+namespace VNLib.Utils.IO
+{
+ /// <summary>
+ /// Represents a lazily loaded file stored in memory, with a change mointor
+ /// that reloads the template if the file was modified in the filesystem
+ /// </summary>
+ public abstract class InMemoryTemplate : VnDisposeable
+ {
+ protected ManualResetEventSlim TemplateLock;
+ private readonly FileSystemWatcher? Watcher;
+ private bool Modified;
+ private VnMemoryStream templateBuffer;
+ protected readonly FileInfo TemplateFile;
+
+ /// <summary>
+ /// Gets the name of the template
+ /// </summary>
+ public abstract string TemplateName { get; }
+
+ /// <summary>
+ /// Creates a new in-memory copy of a file that will detect changes and refresh
+ /// </summary>
+ /// <param name="listenForChanges">Should changes to the template file be moniored for changes, and reloaded as necessary</param>
+ /// <param name="path">The path of the file template</param>
+ protected InMemoryTemplate(string path, bool listenForChanges = true)
+ {
+ TemplateFile = new FileInfo(path);
+ TemplateLock = new(true);
+ //Make sure the file exists
+ if (!TemplateFile.Exists)
+ {
+ throw new FileNotFoundException("Template file does not exist");
+ }
+ if (listenForChanges)
+ {
+ //Setup a watcher to reload the template when modified
+ Watcher = new FileSystemWatcher(TemplateFile.DirectoryName!)
+ {
+ EnableRaisingEvents = true,
+ IncludeSubdirectories = false,
+ NotifyFilter = NotifyFilters.LastWrite | NotifyFilters.Size
+ };
+ Watcher.Changed += Watcher_Changed;
+ }
+ //Set modified flag to make sure the template is read on first use
+ this.Modified = true;
+ }
+
+ private void Watcher_Changed(object sender, FileSystemEventArgs e)
+ {
+ //Make sure the event was raied for this template
+ if (!e.FullPath.Equals(TemplateFile.FullName, StringComparison.OrdinalIgnoreCase))
+ {
+ return;
+ }
+ TemplateLock.Reset();
+ try
+ {
+ //Set modified flag
+ Modified = true;
+ //Refresh the fileinfo object
+ TemplateFile.Refresh();
+ //Invoke onmodifed function
+ OnModifed();
+ }
+ finally
+ {
+ TemplateLock.Set();
+ }
+ }
+
+ /// <summary>
+ /// Gets a cached copy of the template data
+ /// </summary>
+ protected VnMemoryStream GetTemplateData()
+ {
+ //Make sure access is synchronized incase the file gets updated during access on another thread
+ TemplateLock.Wait();
+ //Determine if the file has been modified and needs to be reloaded
+ if (Modified)
+ {
+ TemplateLock.Reset();
+ try
+ {
+ //Read a new copy of the templte into mem
+ ReadFile();
+ }
+ finally
+ {
+ TemplateLock.Set();
+ }
+ }
+ //Return a copy of the memory stream
+ return templateBuffer.GetReadonlyShallowCopy();
+ }
+ /// <summary>
+ /// Updates the internal copy of the file to its memory representation
+ /// </summary>
+ protected void ReadFile()
+ {
+ //Open the file stream
+ using FileStream fs = TemplateFile.OpenRead();
+ //Dispose the old template buffer
+ templateBuffer?.Dispose();
+ //Create a new stream for storing the cached copy
+ VnMemoryStream newBuf = new();
+ try
+ {
+ fs.CopyTo(newBuf, null);
+ }
+ catch
+ {
+ newBuf.Dispose();
+ throw;
+ }
+ //Create the readonly copy
+ templateBuffer = VnMemoryStream.CreateReadonly(newBuf);
+ //Clear the modified flag
+ Modified = false;
+ }
+ /// <summary>
+ /// Updates the internal copy of the file to its memory representation, asynchronously
+ /// </summary>
+ /// <param name="cancellationToken"></param>
+ /// <returns>A task that completes when the file has been copied into memory</returns>
+ protected async Task ReadFileAsync(CancellationToken cancellationToken = default)
+ {
+ //Open the file stream
+ await using FileStream fs = TemplateFile.OpenRead();
+ //Dispose the old template buffer
+ templateBuffer?.Dispose();
+ //Create a new stream for storing the cached copy
+ VnMemoryStream newBuf = new();
+ try
+ {
+ //Copy async
+ await fs.CopyToAsync(newBuf, 8192, Memory.Memory.Shared, cancellationToken);
+ }
+ catch
+ {
+ newBuf.Dispose();
+ throw;
+ }
+ //Create the readonly copy
+ templateBuffer = VnMemoryStream.CreateReadonly(newBuf);
+ //Clear the modified flag
+ Modified = false;
+ }
+
+ /// <summary>
+ /// Invoked when the template file has been modifed. Note: This event is raised
+ /// while the <see cref="TemplateLock"/> is held.
+ /// </summary>
+ protected abstract void OnModifed();
+
+ ///<inheritdoc/>
+ protected override void Free()
+ {
+ //Dispose the watcher
+ Watcher?.Dispose();
+ //free the stream
+ templateBuffer?.Dispose();
+ }
+ }
+} \ No newline at end of file
diff --git a/lib/Utils/src/IO/IsolatedStorageDirectory.cs b/lib/Utils/src/IO/IsolatedStorageDirectory.cs
new file mode 100644
index 0000000..65460ff
--- /dev/null
+++ b/lib/Utils/src/IO/IsolatedStorageDirectory.cs
@@ -0,0 +1,154 @@
+/*
+* Copyright (c) 2022 Vaughn Nugent
+*
+* Library: VNLib
+* Package: VNLib.Utils
+* File: IsolatedStorageDirectory.cs
+*
+* IsolatedStorageDirectory.cs is part of VNLib.Utils which is part of the larger
+* VNLib collection of libraries and utilities.
+*
+* VNLib.Utils is free software: you can redistribute it and/or modify
+* it under the terms of the GNU General Public License as published
+* by the Free Software Foundation, either version 2 of the License,
+* or (at your option) any later version.
+*
+* VNLib.Utils is distributed in the hope that it will be useful,
+* but WITHOUT ANY WARRANTY; without even the implied warranty of
+* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+* General Public License for more details.
+*
+* You should have received a copy of the GNU General Public License
+* along with VNLib.Utils. If not, see http://www.gnu.org/licenses/.
+*/
+
+using System;
+using System.IO;
+using System.IO.IsolatedStorage;
+
+namespace VNLib.Utils.IO
+{
+ /// <summary>
+ /// Represents an open directory within an <see cref="IsolatedStorageFile"/> store for which files can be created, opened, or deleted.
+ /// </summary>
+ public sealed class IsolatedStorageDirectory : IsolatedStorage
+ {
+ private readonly string DirectoryPath;
+ private readonly IsolatedStorageFile Storage;
+ /// <summary>
+ /// Creates a new <see cref="IsolatedStorageDirectory"/> within the specified file using the directory name.
+ /// </summary>
+ /// <param name="storage">A configured and open <see cref="IsolatedStorageFile"/></param>
+ /// <param name="dir">The directory name to open or create within the store</param>
+ public IsolatedStorageDirectory(IsolatedStorageFile storage, string dir)
+ {
+ this.Storage = storage;
+ this.DirectoryPath = dir;
+ //If the directory doesnt exist, create it
+ if (!this.Storage.DirectoryExists(dir))
+ this.Storage.CreateDirectory(dir);
+ }
+
+ private IsolatedStorageDirectory(IsolatedStorageDirectory parent, string dirName)
+ {
+ //Store ref to parent dir
+ Parent = parent;
+ //Referrence store
+ this.Storage = parent.Storage;
+ //Add the name of this dir to the end of the specified dir path
+ this.DirectoryPath = Path.Combine(parent.DirectoryPath, dirName);
+ }
+
+ /// <summary>
+ /// Creates a file by its path name within the currnet directory
+ /// </summary>
+ /// <param name="fileName">The name of the file</param>
+ /// <returns>The open file</returns>
+ /// <exception cref="IsolatedStorageException"></exception>
+ /// <exception cref="ObjectDisposedException"></exception>
+ /// <exception cref="DirectoryNotFoundException"></exception>
+ public IsolatedStorageFileStream CreateFile(string fileName)
+ {
+ return this.Storage.CreateFile(Path.Combine(DirectoryPath, fileName));
+ }
+ /// <summary>
+ /// Removes a file from the current directory
+ /// </summary>
+ /// <param name="fileName">The path of the file to remove</param>
+ /// <exception cref="IsolatedStorageException"></exception>
+ public void DeleteFile(string fileName)
+ {
+ this.Storage.DeleteFile(Path.Combine(this.DirectoryPath, fileName));
+ }
+ /// <summary>
+ /// Opens a file that exists within the current directory
+ /// </summary>
+ /// <param name="fileName">Name with extension of the file</param>
+ /// <param name="mode">File mode</param>
+ /// <param name="access">File access</param>
+ /// <returns>The open <see cref="IsolatedStorageFileStream"/> from the current directory</returns>
+ public IsolatedStorageFileStream OpenFile(string fileName, FileMode mode, FileAccess access)
+ {
+ return this.Storage.OpenFile(Path.Combine(DirectoryPath, fileName), mode, access);
+ }
+ /// <summary>
+ /// Opens a file that exists within the current directory
+ /// </summary>
+ /// <param name="fileName">Name with extension of the file</param>
+ /// <param name="mode">File mode</param>
+ /// <param name="access">File access</param>
+ /// <param name="share">The file shareing mode</param>
+ /// <returns>The open <see cref="IsolatedStorageFileStream"/> from the current directory</returns>
+ public IsolatedStorageFileStream OpenFile(string fileName, FileMode mode, FileAccess access, FileShare share)
+ {
+ return this.Storage.OpenFile(Path.Combine(DirectoryPath, fileName), mode, access, share);
+ }
+
+ /// <summary>
+ /// Determiens if the specified file path refers to an existing file within the directory
+ /// </summary>
+ /// <param name="fileName">The name of the file to search for</param>
+ /// <returns>True if the file exists within the current directory</returns>
+ /// <exception cref="ArgumentNullException"></exception>
+ /// <exception cref="ObjectDisposedException"></exception>
+ /// <exception cref="IsolatedStorageException"></exception>
+ /// <exception cref="InvalidOperationException"></exception>
+ public bool FileExists(string fileName)
+ {
+ return this.Storage.FileExists(Path.Combine(this.DirectoryPath, fileName));
+ }
+
+ /// <summary>
+ /// Removes the directory and its contents from the store
+ /// </summary>
+ public override void Remove()
+ {
+ Storage.DeleteDirectory(this.DirectoryPath);
+ }
+
+ public override long AvailableFreeSpace => Storage.AvailableFreeSpace;
+ public override long Quota => Storage.Quota;
+ public override long UsedSize => Storage.UsedSize;
+ public override bool IncreaseQuotaTo(long newQuotaSize) => Storage.IncreaseQuotaTo(newQuotaSize);
+
+ /// <summary>
+ /// The parent <see cref="IsolatedStorageDirectory"/> this directory is a child within. null if there are no parent directories
+ /// above this dir
+ /// </summary>
+
+ public IsolatedStorageDirectory? Parent { get; }
+#nullable disable
+
+ /// <summary>
+ /// Creates a child directory within the current directory
+ /// </summary>
+ /// <param name="directoryName">The name of the child directory</param>
+ /// <returns>A new <see cref="IsolatedStorageDirectory"/> for which <see cref="IsolatedStorageFileStream"/>s can be opened/created</returns>
+ /// <exception cref="ArgumentException"></exception>
+ /// <exception cref="ArgumentNullException"></exception>
+ public IsolatedStorageDirectory CreateChildDirectory(string directoryName)
+ {
+ return new IsolatedStorageDirectory(this, directoryName);
+ }
+ }
+} \ No newline at end of file
diff --git a/lib/Utils/src/IO/SlidingWindowBufferExtensions.cs b/lib/Utils/src/IO/SlidingWindowBufferExtensions.cs
new file mode 100644
index 0000000..0509061
--- /dev/null
+++ b/lib/Utils/src/IO/SlidingWindowBufferExtensions.cs
@@ -0,0 +1,213 @@
+/*
+* Copyright (c) 2022 Vaughn Nugent
+*
+* Library: VNLib
+* Package: VNLib.Utils
+* File: SlidingWindowBufferExtensions.cs
+*
+* SlidingWindowBufferExtensions.cs is part of VNLib.Utils which is part of the larger
+* VNLib collection of libraries and utilities.
+*
+* VNLib.Utils is free software: you can redistribute it and/or modify
+* it under the terms of the GNU General Public License as published
+* by the Free Software Foundation, either version 2 of the License,
+* or (at your option) any later version.
+*
+* VNLib.Utils is distributed in the hope that it will be useful,
+* but WITHOUT ANY WARRANTY; without even the implied warranty of
+* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+* General Public License for more details.
+*
+* You should have received a copy of the GNU General Public License
+* along with VNLib.Utils. If not, see http://www.gnu.org/licenses/.
+*/
+
+using System;
+using System.IO;
+using System.Threading;
+using System.Threading.Tasks;
+
+using VNLib.Utils.Memory;
+using VNLib.Utils.Extensions;
+using System.Runtime.CompilerServices;
+
+namespace VNLib.Utils.IO
+{
+ /// <summary>
+ /// Extention methods for <see cref="ISlindingWindowBuffer{T}"/>
+ /// </summary>
+ public static class SlidingWindowBufferExtensions
+ {
+ /// <summary>
+ /// Shifts/resets the current buffered data window down to the
+ /// begining of the buffer if the buffer window is shifted away
+ /// from the begining.
+ /// </summary>
+ /// <returns>The number of bytes of available space in the buffer</returns>
+ public static ERRNO CompactBufferWindow<T>(this ISlindingWindowBuffer<T> sBuf)
+ {
+ //Nothing to compact if the starting data pointer is at the beining of the window
+ if (sBuf.WindowStartPos > 0)
+ {
+ //Get span over engire buffer
+ Span<T> buffer = sBuf.Buffer.Span;
+ //Get data within window
+ Span<T> usedData = sBuf.Accumulated;
+ //Copy remaining to the begining of the buffer
+ usedData.CopyTo(buffer);
+
+ //Reset positions, then advance to the specified size
+ sBuf.Reset();
+ sBuf.Advance(usedData.Length);
+ }
+ //Return the number of bytes of available space
+ return sBuf.RemainingSize;
+ }
+
+ /// <summary>
+ /// Appends the specified data to the end of the buffer
+ /// </summary>
+ /// <typeparam name="T"></typeparam>
+ /// <param name="sBuf"></param>
+ /// <param name="val">The value to append to the end of the buffer</param>
+ /// <exception cref="IndexOutOfRangeException"></exception>
+ [MethodImpl(MethodImplOptions.AggressiveInlining)]
+ public static void Append<T>(this IDataAccumulator<T> sBuf, T val)
+ {
+ //Set the value at first position
+ sBuf.Remaining[0] = val;
+ //Advance by 1
+ sBuf.Advance(1);
+ }
+ /// <summary>
+ /// Appends the specified data to the end of the buffer
+ /// </summary>
+ /// <typeparam name="T"></typeparam>
+ /// <param name="sBuf"></param>
+ /// <param name="val">The value to append to the end of the buffer</param>
+ /// <exception cref="ArgumentException"></exception>
+ [MethodImpl(MethodImplOptions.AggressiveInlining)]
+ public static void Append<T>(this IDataAccumulator<T> sBuf, ReadOnlySpan<T> val)
+ {
+ val.CopyTo(sBuf.Remaining);
+ sBuf.Advance(val.Length);
+ }
+ /// <summary>
+ /// Formats and appends a value type to the accumulator with proper endianess
+ /// </summary>
+ /// <typeparam name="T">The value type to appent</typeparam>
+ /// <param name="accumulator">The binary accumulator to append</param>
+ /// <param name="value">The value type to append</param>
+ [MethodImpl(MethodImplOptions.AggressiveInlining)]
+ public static void Append<T>(this IDataAccumulator<byte> accumulator, T value) where T: unmanaged
+ {
+ //Use forward reader for the memory extension to append a value type to a binary accumulator
+ ForwardOnlyWriter<byte> w = new(accumulator.Remaining);
+ w.Append(value);
+ accumulator.Advance(w.Written);
+ }
+
+ /// <summary>
+ /// Attempts to write as much data as possible to the remaining space
+ /// in the buffer and returns the number of bytes accumulated.
+ /// </summary>
+ /// <typeparam name="T"></typeparam>
+ /// <param name="accumulator"></param>
+ /// <param name="value">The value to accumulate</param>
+ /// <returns>The number of bytes accumulated</returns>
+ public static ERRNO TryAccumulate<T>(this IDataAccumulator<T> accumulator, ReadOnlySpan<T> value)
+ {
+ //Calc data size and reserve space for final crlf
+ int dataToCopy = Math.Min(value.Length, accumulator.RemainingSize);
+
+ //Write as much data as possible
+ accumulator.Append(value[..dataToCopy]);
+
+ //Return number of bytes not written
+ return dataToCopy;
+ }
+
+ /// <summary>
+ /// Appends a <see cref="ISpanFormattable"/> instance to the end of the accumulator
+ /// </summary>
+ /// <typeparam name="T"></typeparam>
+ /// <param name="accumulator"></param>
+ /// <param name="formattable">The formattable instance to write to the accumulator</param>
+ /// <param name="format">The format arguments</param>
+ [MethodImpl(MethodImplOptions.AggressiveInlining)]
+ public static void Append<T>(this IDataAccumulator<char> accumulator, in T formattable, ReadOnlySpan<char> format = default) where T : struct, ISpanFormattable
+ {
+ ForwardOnlyWriter<char> writer = new(accumulator.Remaining);
+ writer.Append(formattable, format);
+ accumulator.Advance(writer.Written);
+ }
+
+ /// <summary>
+ /// Uses the remaining data buffer to compile a <see cref="IStringSerializeable"/>
+ /// instance, then advances the accumulator by the number of characters used.
+ /// </summary>
+ /// <typeparam name="T"></typeparam>
+ /// <param name="accumulator"></param>
+ /// <param name="compileable">The <see cref="IStringSerializeable"/> instance to compile</param>
+ public static void Append<T>(this IDataAccumulator<char> accumulator, in T compileable) where T : IStringSerializeable
+ {
+ //Write directly to the remaining space
+ int written = compileable.Compile(accumulator.Remaining);
+ //Advance the writer
+ accumulator.Advance(written);
+ }
+
+ /// <summary>
+ /// Reads available data from the current window and writes as much as possible it to the supplied buffer
+ /// and advances the buffer window
+ /// </summary>
+ /// <typeparam name="T">Element type</typeparam>
+ /// <param name="sBuf"></param>
+ /// <param name="buffer">The output buffer to write data to</param>
+ /// <returns>The number of elements written to the buffer</returns>
+ public static ERRNO Read<T>(this ISlindingWindowBuffer<T> sBuf, in Span<T> buffer)
+ {
+ //Calculate the amount of data to copy
+ int dataToCopy = Math.Min(buffer.Length, sBuf.AccumulatedSize);
+ //Copy the data to the buffer
+ sBuf.Accumulated[..dataToCopy].CopyTo(buffer);
+ //Advance the window
+ sBuf.AdvanceStart(dataToCopy);
+ //Return the number of bytes copied
+ return dataToCopy;
+ }
+
+ /// <summary>
+ /// Fills the remaining window space of the current accumulator with
+ /// data from the specified stream asynchronously.
+ /// </summary>
+ /// <param name="accumulator"></param>
+ /// <param name="input">The stream to read data from</param>
+ /// <param name="cancellationToken">A token to cancel the operation</param>
+ /// <returns>A value task representing the operation</returns>
+ public static async ValueTask AccumulateDataAsync(this ISlindingWindowBuffer<byte> accumulator, Stream input, CancellationToken cancellationToken)
+ {
+ //Get a buffer from the end of the current window to the end of the buffer
+ Memory<byte> bufWindow = accumulator.RemainingBuffer;
+ //Read from stream async
+ int read = await input.ReadAsync(bufWindow, cancellationToken);
+ //Update the end of the buffer window to the end of the read data
+ accumulator.Advance(read);
+ }
+ /// <summary>
+ /// Fills the remaining window space of the current accumulator with
+ /// data from the specified stream.
+ /// </summary>
+ /// <param name="accumulator"></param>
+ /// <param name="input">The stream to read data from</param>
+ public static void AccumulateData(this IDataAccumulator<byte> accumulator, Stream input)
+ {
+ //Get a buffer from the end of the current window to the end of the buffer
+ Span<byte> bufWindow = accumulator.Remaining;
+ //Read from stream async
+ int read = input.Read(bufWindow);
+ //Update the end of the buffer window to the end of the read data
+ accumulator.Advance(read);
+ }
+ }
+} \ No newline at end of file
diff --git a/lib/Utils/src/IO/TemporayIsolatedFile.cs b/lib/Utils/src/IO/TemporayIsolatedFile.cs
new file mode 100644
index 0000000..3bee92b
--- /dev/null
+++ b/lib/Utils/src/IO/TemporayIsolatedFile.cs
@@ -0,0 +1,57 @@
+/*
+* Copyright (c) 2022 Vaughn Nugent
+*
+* Library: VNLib
+* Package: VNLib.Utils
+* File: TemporayIsolatedFile.cs
+*
+* TemporayIsolatedFile.cs is part of VNLib.Utils which is part of the larger
+* VNLib collection of libraries and utilities.
+*
+* VNLib.Utils is free software: you can redistribute it and/or modify
+* it under the terms of the GNU General Public License as published
+* by the Free Software Foundation, either version 2 of the License,
+* or (at your option) any later version.
+*
+* VNLib.Utils is distributed in the hope that it will be useful,
+* but WITHOUT ANY WARRANTY; without even the implied warranty of
+* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+* General Public License for more details.
+*
+* You should have received a copy of the GNU General Public License
+* along with VNLib.Utils. If not, see http://www.gnu.org/licenses/.
+*/
+
+using System;
+using System.IO;
+using System.IO.IsolatedStorage;
+
+namespace VNLib.Utils.IO
+{
+ /// <summary>
+ /// Allows for temporary files to be generated, used, then removed from an <see cref="IsolatedStorageFile"/>
+ /// </summary>
+ public sealed class TemporayIsolatedFile : BackingStream<IsolatedStorageFileStream>
+ {
+ private readonly IsolatedStorageDirectory Storage;
+ private readonly string Filename;
+ /// <summary>
+ /// Creates a new temporary filestream within the specified <see cref="IsolatedStorageFile"/>
+ /// </summary>
+ /// <param name="storage">The file store to genreate temporary files within</param>
+ public TemporayIsolatedFile(IsolatedStorageDirectory storage)
+ {
+ //Store ref
+ this.Storage = storage;
+ //Creaet a new random filename
+ this.Filename = Path.GetRandomFileName();
+ //try to created a new file within the isolaged storage
+ this.BaseStream = storage.CreateFile(this.Filename);
+ }
+ protected override void OnClose()
+ {
+ //Remove the file from the storage
+ Storage.DeleteFile(this.Filename);
+ }
+ }
+} \ No newline at end of file
diff --git a/lib/Utils/src/IO/VnMemoryStream.cs b/lib/Utils/src/IO/VnMemoryStream.cs
new file mode 100644
index 0000000..4e8a2b3
--- /dev/null
+++ b/lib/Utils/src/IO/VnMemoryStream.cs
@@ -0,0 +1,469 @@
+/*
+* Copyright (c) 2022 Vaughn Nugent
+*
+* Library: VNLib
+* Package: VNLib.Utils
+* File: VnMemoryStream.cs
+*
+* VnMemoryStream.cs is part of VNLib.Utils which is part of the larger
+* VNLib collection of libraries and utilities.
+*
+* VNLib.Utils is free software: you can redistribute it and/or modify
+* it under the terms of the GNU General Public License as published
+* by the Free Software Foundation, either version 2 of the License,
+* or (at your option) any later version.
+*
+* VNLib.Utils is distributed in the hope that it will be useful,
+* but WITHOUT ANY WARRANTY; without even the implied warranty of
+* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+* General Public License for more details.
+*
+* You should have received a copy of the GNU General Public License
+* along with VNLib.Utils. If not, see http://www.gnu.org/licenses/.
+*/
+
+using System;
+using System.IO;
+using System.Threading;
+using System.Threading.Tasks;
+using System.Runtime.InteropServices;
+
+using VNLib.Utils.Extensions;
+
+namespace VNLib.Utils.IO
+{
+
+ using Utils.Memory;
+
+ /// <summary>
+ /// Provides an unmanaged memory stream. Desigend to help reduce garbage collector load for
+ /// high frequency memory operations. Similar to <see cref="UnmanagedMemoryStream"/>
+ /// </summary>
+ public sealed class VnMemoryStream : Stream, ICloneable
+ {
+ private long _position;
+ private long _length;
+ //Memory
+ private readonly MemoryHandle<byte> _buffer;
+ private bool IsReadonly;
+ //Default owns handle
+ private readonly bool OwnsHandle = true;
+
+ /// <summary>
+ /// Creates a new <see cref="VnMemoryStream"/> pointing to the begining of memory, and consumes the handle.
+ /// </summary>
+ /// <param name="handle"><see cref="MemoryHandle{T}"/> to consume</param>
+ /// <param name="length">Length of the stream</param>
+ /// <param name="readOnly">Should the stream be readonly?</param>
+ /// <exception cref="ArgumentException"></exception>
+ /// <returns>A <see cref="VnMemoryStream"/> wrapper to access the handle data</returns>
+ public static VnMemoryStream ConsumeHandle(MemoryHandle<byte> handle, Int64 length, bool readOnly)
+ {
+ handle.ThrowIfClosed();
+ return new VnMemoryStream(handle, length, readOnly, true);
+ }
+
+ /// <summary>
+ /// Converts a writable <see cref="VnMemoryStream"/> to readonly to allow shallow copies
+ /// </summary>
+ /// <param name="stream">The stream to make readonly</param>
+ /// <returns>The readonly stream</returns>
+ public static VnMemoryStream CreateReadonly(VnMemoryStream stream)
+ {
+ //Set the readonly flag
+ stream.IsReadonly = true;
+ //Return the stream
+ return stream;
+ }
+
+ /// <summary>
+ /// Creates a new memory stream
+ /// </summary>
+ public VnMemoryStream() : this(Memory.Shared) { }
+ /// <summary>
+ /// Create a new memory stream where buffers will be allocated from the specified heap
+ /// </summary>
+ /// <param name="heap"><see cref="PrivateHeap"/> to allocate memory from</param>
+ /// <exception cref="OutOfMemoryException"></exception>
+ /// <exception cref="ArgumentNullException"></exception>
+ public VnMemoryStream(IUnmangedHeap heap) : this(heap, 0, false) { }
+
+ /// <summary>
+ /// Creates a new memory stream and pre-allocates the internal
+ /// buffer of the specified size on the specified heap to avoid resizing.
+ /// </summary>
+ /// <param name="heap"><see cref="PrivateHeap"/> to allocate memory from</param>
+ /// <param name="bufferSize">Number of bytes (length) of the stream if known</param>
+ /// <param name="zero">Zero memory allocations during buffer expansions</param>
+ /// <exception cref="OutOfMemoryException"></exception>
+ /// <exception cref="ArgumentNullException"></exception>
+ /// <exception cref="ArgumentOutOfRangeException"></exception>
+ public VnMemoryStream(IUnmangedHeap heap, long bufferSize, bool zero)
+ {
+ _ = heap ?? throw new ArgumentNullException(nameof(heap));
+ _buffer = heap.Alloc<byte>(bufferSize, zero);
+ }
+
+ /// <summary>
+ /// Creates a new memory stream from the data provided
+ /// </summary>
+ /// <param name="heap"><see cref="PrivateHeap"/> to allocate memory from</param>
+ /// <param name="data">Initial data</param>
+ public VnMemoryStream(IUnmangedHeap heap, ReadOnlySpan<byte> data)
+ {
+ _ = heap ?? throw new ArgumentNullException(nameof(heap));
+ //Alloc the internal buffer to match the data stream
+ _buffer = heap.AllocAndCopy(data);
+ //Set length
+ _length = data.Length;
+ //Position will default to 0 cuz its dotnet :P
+ return;
+ }
+
+ /// <summary>
+ /// WARNING: Dangerous constructor, make sure read-only and owns hanlde are set accordingly
+ /// </summary>
+ /// <param name="buffer">The buffer to referrence directly</param>
+ /// <param name="length">The length property of the stream</param>
+ /// <param name="readOnly">Is the stream readonly (should mostly be true!)</param>
+ /// <param name="ownsHandle">Does the new stream own the memory -> <paramref name="buffer"/></param>
+ private VnMemoryStream(MemoryHandle<byte> buffer, long length, bool readOnly, bool ownsHandle)
+ {
+ OwnsHandle = ownsHandle;
+ _buffer = buffer; //Consume the handle
+ _length = length; //Store length of the buffer
+ IsReadonly = readOnly;
+ }
+
+ /// <summary>
+ /// UNSAFE Number of bytes between position and length. Never negative
+ /// </summary>
+ private long LenToPosDiff => Math.Max(_length - _position, 0);
+
+ /// <summary>
+ /// If the current stream is a readonly stream, creates an unsafe shallow copy for reading only.
+ /// </summary>
+ /// <returns>New stream shallow copy of the internal stream</returns>
+ /// <exception cref="NotSupportedException"></exception>
+ public VnMemoryStream GetReadonlyShallowCopy()
+ {
+ //Create a new readonly copy (stream does not own the handle)
+ return !IsReadonly
+ ? throw new NotSupportedException("This stream is not readonly. Cannot create shallow copy on a mutable stream")
+ : new VnMemoryStream(_buffer, _length, true, false);
+ }
+
+ /// <summary>
+ /// Writes data directly to the destination stream from the internal buffer
+ /// without allocating or copying any data.
+ /// </summary>
+ /// <param name="destination">The stream to write data to</param>
+ /// <param name="bufferSize">The size of the chunks to write to the destination stream</param>
+ /// <exception cref="IOException"></exception>
+ public override void CopyTo(Stream destination, int bufferSize)
+ {
+ _ = destination ?? throw new ArgumentNullException(nameof(destination));
+
+ if (!destination.CanWrite)
+ {
+ throw new IOException("The destinaion stream is not writeable");
+ }
+
+ do
+ {
+ //Calc the remaining bytes to read no larger than the buffer size
+ int bytesToRead = (int)Math.Min(LenToPosDiff, bufferSize);
+
+ //Create a span wrapper by using the offet function to support memory handles larger than 2gb
+ ReadOnlySpan<byte> span = _buffer.GetOffsetSpan(_position, bytesToRead);
+
+ destination.Write(span);
+
+ //Update position
+ _position += bytesToRead;
+
+ } while (LenToPosDiff > 0);
+ }
+
+ /// <summary>
+ /// Allocates a temporary buffer of the desired size, copies data from the internal
+ /// buffer and writes it to the destination buffer asynchronously.
+ /// </summary>
+ /// <param name="destination">The stream to write output data to</param>
+ /// <param name="bufferSize">The size of the buffer to use when copying data</param>
+ /// <param name="cancellationToken">A token to cancel the opreation</param>
+ /// <returns>A task that resolves when the remaining data in the stream has been written to the destination</returns>
+ /// <exception cref="IOException"></exception>
+ /// <exception cref="ObjectDisposedException"></exception>
+ /// <exception cref="ArgumentOutOfRangeException"></exception>
+ public override async Task CopyToAsync(Stream destination, int bufferSize, CancellationToken cancellationToken)
+ {
+ _ = destination ?? throw new ArgumentNullException(nameof(destination));
+
+ if (!destination.CanWrite)
+ {
+ throw new IOException("The destinaion stream is not writeable");
+ }
+
+ cancellationToken.ThrowIfCancellationRequested();
+
+ /*
+ * Alloc temp copy buffer. This is a requirement because
+ * the stream may be larger than an int32 so it must be
+ * copied by segment
+ */
+
+ using VnTempBuffer<byte> copyBuffer = new(bufferSize);
+
+ do
+ {
+ //read from internal stream
+ int read = Read(copyBuffer);
+
+ if(read <= 0)
+ {
+ break;
+ }
+
+ //write async
+ await destination.WriteAsync(copyBuffer.AsMemory(0, read), cancellationToken);
+
+ } while (true);
+
+ }
+
+ /// <summary>
+ /// <inheritdoc/>
+ /// <para>
+ /// This property is always true
+ /// </para>
+ /// </summary>
+ public override bool CanRead => true;
+ /// <summary>
+ /// <inheritdoc/>
+ /// <para>
+ /// This propery is always true
+ /// </para>
+ /// </summary>
+ public override bool CanSeek => true;
+ /// <summary>
+ /// True unless the stream is (or has been converted to) a readonly
+ /// stream.
+ /// </summary>
+ public override bool CanWrite => !IsReadonly;
+ ///<inheritdoc/>
+ public override long Length => _length;
+ ///<inheritdoc/>
+ public override bool CanTimeout => false;
+
+ ///<inheritdoc/>
+ public override long Position
+ {
+ get => _position;
+ set => Seek(value, SeekOrigin.Begin);
+ }
+ /// <summary>
+ /// Closes the stream and frees the internal allocated memory blocks
+ /// </summary>
+ public override void Close()
+ {
+ //Only dispose buffer if we own it
+ if (OwnsHandle)
+ {
+ _buffer.Dispose();
+ }
+ }
+ ///<inheritdoc/>
+ public override void Flush() { }
+ // Override to reduce base class overhead
+ ///<inheritdoc/>
+ public override Task FlushAsync(CancellationToken cancellationToken) => Task.CompletedTask;
+ ///<inheritdoc/>
+ public override int Read(byte[] buffer, int offset, int count) => Read(new Span<byte>(buffer, offset, count));
+ ///<inheritdoc/>
+ public override int Read(Span<byte> buffer)
+ {
+ if (buffer.Length == 0)
+ {
+ return 0;
+ }
+ //Number of bytes to read from memory buffer
+ int bytesToRead = checked((int)Math.Min(LenToPosDiff, buffer.Length));
+ //Copy bytes to buffer
+ Memory.Copy(_buffer, _position, buffer, 0, bytesToRead);
+ //Increment buffer position
+ _position += bytesToRead;
+ //Bytestoread should never be larger than int.max because span length is an integer
+ return bytesToRead;
+ }
+
+ /*
+ * Async reading will always run synchronously in a memory stream,
+ * so overrides are just so avoid base class overhead
+ */
+ ///<inheritdoc/>
+ public override ValueTask<int> ReadAsync(Memory<byte> buffer, CancellationToken cancellationToken = default)
+ {
+ //Read synchronously and return a completed task
+ int read = Read(buffer.Span);
+ return ValueTask.FromResult(read);
+ }
+ ///<inheritdoc/>
+ public override Task<int> ReadAsync(byte[] buffer, int offset, int count, CancellationToken cancellationToken)
+ {
+ //Read synchronously and return a completed task
+ int read = Read(buffer.AsSpan(offset, count));
+ return Task.FromResult(read);
+ }
+ ///<inheritdoc/>
+ public override long Seek(long offset, SeekOrigin origin)
+ {
+ if (offset < 0)
+ {
+ throw new ArgumentOutOfRangeException(nameof(offset), "Offset cannot be less than 0");
+ }
+ switch (origin)
+ {
+ case SeekOrigin.Begin:
+ //Length will never be greater than int.Max so output will never exceed int.max
+ _position = Math.Min(_length, offset);
+ return _position;
+ case SeekOrigin.Current:
+ long newPos = _position + offset;
+ //Length will never be greater than int.Max so output will never exceed length
+ _position = Math.Min(_length, newPos);
+ return newPos;
+ case SeekOrigin.End:
+ long real_index = _length - offset;
+ //If offset moves the position negative, just set the position to 0 and continue
+ _position = Math.Min(real_index, 0);
+ return real_index;
+ default:
+ throw new ArgumentException("Stream operation is not supported on current stream");
+ }
+ }
+
+
+ /// <summary>
+ /// Resizes the internal buffer to the exact size (in bytes) of the
+ /// value argument. A value of 0 will free the entire buffer. A value
+ /// greater than zero will resize the buffer (and/or alloc)
+ /// </summary>
+ /// <param name="value">The size of the stream (and internal buffer)</param>
+ /// <exception cref="OutOfMemoryException"></exception>
+ /// <exception cref="NotSupportedException"></exception>
+ /// <exception cref="ObjectDisposedException"></exception>
+ /// <exception cref="ArgumentOutOfRangeException"></exception>
+ public override void SetLength(long value)
+ {
+ if (IsReadonly)
+ {
+ throw new NotSupportedException("This stream is readonly");
+ }
+ if (value < 0)
+ {
+ throw new ArgumentOutOfRangeException(nameof(value), "Value cannot be less than 0");
+ }
+ //Resize the buffer to the specified length
+ _buffer.Resize(value);
+ //Set length
+ _length = value;
+ //Make sure the position is not pointing outside of the buffer
+ _position = Math.Min(_position, _length);
+ return;
+ }
+ ///<inheritdoc/>
+ public override void Write(byte[] buffer, int offset, int count) => Write(new ReadOnlySpan<byte>(buffer, offset, count));
+ ///<inheritdoc/>
+ public override void Write(ReadOnlySpan<byte> buffer)
+ {
+ if (IsReadonly)
+ {
+ throw new NotSupportedException("Write operation is not allowed on readonly stream!");
+ }
+ //Calculate the new final position
+ long newPos = (_position + buffer.Length);
+ //Determine if the buffer needs to be expanded
+ if (buffer.Length > LenToPosDiff)
+ {
+ //Expand buffer if required
+ _buffer.ResizeIfSmaller(newPos);
+ //Update length
+ _length = newPos;
+ }
+ //Copy the input buffer to the internal buffer
+ Memory.Copy(buffer, _buffer, _position);
+ //Update the position
+ _position = newPos;
+ return;
+ }
+ ///<inheritdoc/>
+ public override Task WriteAsync(byte[] buffer, int offset, int count, CancellationToken cancellationToken)
+ {
+ //Write synchronously and return a completed task
+ Write(buffer, offset, count);
+ return Task.CompletedTask;
+ }
+ ///<inheritdoc/>
+ public override ValueTask WriteAsync(ReadOnlyMemory<byte> buffer, CancellationToken cancellationToken = default)
+ {
+ //Write synchronously and return a completed task
+ Write(buffer.Span);
+ return ValueTask.CompletedTask;
+ }
+ ///<inheritdoc/>
+ public override void WriteByte(byte value)
+ {
+ Span<byte> buf = MemoryMarshal.CreateSpan(ref value, 1);
+ Write(buf);
+ }
+
+ /// <summary>
+ /// Allocates and copies internal buffer to new managed byte[]
+ /// </summary>
+ /// <returns>Copy of internal buffer</returns>
+ /// <exception cref="OutOfMemoryException"></exception>
+ /// <exception cref="OutOfMemoryException"></exception>
+ public byte[] ToArray()
+ {
+ //Alloc a new array of the size of the internal buffer
+ byte[] data = new byte[_length];
+ //Copy data from the internal buffer to the output buffer
+ _buffer.Span.CopyTo(data);
+ return data;
+
+ }
+ /// <summary>
+ /// Returns a <see cref="ReadOnlySpan{T}"/> window over the data within the entire stream
+ /// </summary>
+ /// <returns>A <see cref="ReadOnlySpan{T}"/> of the data within the entire stream</returns>
+ /// <exception cref="OverflowException"></exception>
+ public ReadOnlySpan<byte> AsSpan()
+ {
+ ReadOnlySpan<byte> output = _buffer.Span;
+ return output[..(int)_length];
+ }
+
+ /// <summary>
+ /// If the current stream is a readonly stream, creates a shallow copy for reading only.
+ /// </summary>
+ /// <returns>New stream shallow copy of the internal stream</returns>
+ /// <exception cref="InvalidOperationException"></exception>
+ public object Clone() => GetReadonlyShallowCopy();
+
+ /*
+ * Override the Dispose async method to avoid the base class overhead
+ * and task allocation since this will always be a syncrhonous
+ * operation (freeing memory)
+ */
+
+ ///<inheritdoc/>
+ public override ValueTask DisposeAsync()
+ {
+ //Dispose and return completed task
+ base.Dispose(true);
+ GC.SuppressFinalize(this);
+ return ValueTask.CompletedTask;
+ }
+ }
+} \ No newline at end of file
diff --git a/lib/Utils/src/IO/VnStreamReader.cs b/lib/Utils/src/IO/VnStreamReader.cs
new file mode 100644
index 0000000..70b9734
--- /dev/null
+++ b/lib/Utils/src/IO/VnStreamReader.cs
@@ -0,0 +1,180 @@
+/*
+* Copyright (c) 2022 Vaughn Nugent
+*
+* Library: VNLib
+* Package: VNLib.Utils
+* File: VnStreamReader.cs
+*
+* VnStreamReader.cs is part of VNLib.Utils which is part of the larger
+* VNLib collection of libraries and utilities.
+*
+* VNLib.Utils is free software: you can redistribute it and/or modify
+* it under the terms of the GNU General Public License as published
+* by the Free Software Foundation, either version 2 of the License,
+* or (at your option) any later version.
+*
+* VNLib.Utils is distributed in the hope that it will be useful,
+* but WITHOUT ANY WARRANTY; without even the implied warranty of
+* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+* General Public License for more details.
+*
+* You should have received a copy of the GNU General Public License
+* along with VNLib.Utils. If not, see http://www.gnu.org/licenses/.
+*/
+
+using System;
+using System.IO;
+using System.Text;
+using System.Buffers;
+using System.Threading;
+using System.Threading.Tasks;
+
+using VNLib.Utils.Extensions;
+
+namespace VNLib.Utils.IO
+{
+ /// <summary>
+ /// Binary based buffered text reader, optimized for reading network streams
+ /// </summary>
+ public class VnStreamReader : TextReader, IVnTextReader
+ {
+ private bool disposedValue;
+
+ private readonly ISlindingWindowBuffer<byte> _buffer;
+ ///<inheritdoc/>
+ public virtual Stream BaseStream { get; }
+ ///<inheritdoc/>
+ public Encoding Encoding { get; }
+
+ /// <summary>
+ /// Number of available bytes of buffered data within the current buffer window
+ /// </summary>
+ public int Available => _buffer.AccumulatedSize;
+ /// <summary>
+ /// Gets or sets the line termination used to deliminate a line of data
+ /// </summary>
+ public ReadOnlyMemory<byte> LineTermination { get; set; }
+ Span<byte> IVnTextReader.BufferedDataWindow => _buffer.Accumulated;
+
+ /// <summary>
+ /// Creates a new <see cref="TextReader"/> that reads encoded data from the base.
+ /// Internal buffers will be alloced from <see cref="ArrayPool{T}.Shared"/>
+ /// </summary>
+ /// <param name="baseStream">The underlying stream to read data from</param>
+ /// <param name="enc">The <see cref="Encoding"/> to use when reading from the stream</param>
+ /// <param name="bufferSize">The size of the internal binary buffer</param>
+ public VnStreamReader(Stream baseStream, Encoding enc, int bufferSize)
+ {
+ BaseStream = baseStream;
+ Encoding = enc;
+ //Init a new buffer
+ _buffer = InitializeBuffer(bufferSize);
+ }
+
+ /// <summary>
+ /// Invoked by the constuctor method to allocte the internal buffer with the specified buffer size.
+ /// </summary>
+ /// <param name="bufferSize">The requested size of the buffer to alloc</param>
+ /// <remarks>By default requests the buffer from the <see cref="ArrayPool{T}.Shared"/> instance</remarks>
+ protected virtual ISlindingWindowBuffer<byte> InitializeBuffer(int bufferSize) => new ArrayPoolStreamBuffer<byte>(ArrayPool<byte>.Shared, bufferSize);
+
+ ///<inheritdoc/>
+ public override async Task<string?> ReadLineAsync()
+ {
+ //If buffered data is available, check for line termination
+ if (Available > 0)
+ {
+ //Get current buffer window
+ Memory<byte> buffered = _buffer.AccumulatedBuffer;
+ //search for line termination in current buffer
+ int term = buffered.IndexOf(LineTermination);
+ //Termination found in buffer window
+ if (term > -1)
+ {
+ //Capture the line from the begining of the window to the termination
+ Memory<byte> line = buffered[..term];
+ //Shift the window to the end of the line (excluding the termination)
+ _buffer.AdvanceStart(term + LineTermination.Length);
+ //Decode the line to a string
+ return Encoding.GetString(line.Span);
+ }
+ //Termination not found
+ }
+ //Compact the buffer window and see if space is avialble to buffer more data
+ if (_buffer.CompactBufferWindow())
+ {
+ //There is room, so buffer more data
+ await _buffer.AccumulateDataAsync(BaseStream, CancellationToken.None);
+ //Check again to see if more data is buffered
+ if (Available <= 0)
+ {
+ //No string found
+ return null;
+ }
+ //Get current buffer window
+ Memory<byte> buffered = _buffer.AccumulatedBuffer;
+ //search for line termination in current buffer
+ int term = buffered.IndexOf(LineTermination);
+ //Termination found in buffer window
+ if (term > -1)
+ {
+ //Capture the line from the begining of the window to the termination
+ Memory<byte> line = buffered[..term];
+ //Shift the window to the end of the line (excluding the termination)
+ _buffer.AdvanceStart(term + LineTermination.Length);
+ //Decode the line to a string
+ return Encoding.GetString(line.Span);
+ }
+ }
+ //Termination not found within the entire buffer, so buffer space has been exhausted
+
+ //OOM is raised in the TextReader base class, the standard is preserved
+#pragma warning disable CA2201 // Do not raise reserved exception types
+ throw new OutOfMemoryException("A line termination was not found within the buffer");
+#pragma warning restore CA2201 // Do not raise reserved exception types
+ }
+
+ ///<inheritdoc/>
+ public override int Read(char[] buffer, int index, int count) => Read(buffer.AsSpan(index, count));
+ ///<inheritdoc/>
+ public override int Read(Span<char> buffer)
+ {
+ if (Available <= 0)
+ {
+ return 0;
+ }
+ //Get current buffer window
+ Span<byte> buffered = _buffer.Accumulated;
+ //Convert all avialable data
+ int encoded = Encoding.GetChars(buffered, buffer);
+ //Shift buffer window to the end of the converted data
+ _buffer.AdvanceStart(encoded);
+ //return the number of chars written
+ return Encoding.GetCharCount(buffered);
+ }
+ ///<inheritdoc/>
+ public override void Close() => _buffer.Close();
+ ///<inheritdoc/>
+ protected override void Dispose(bool disposing)
+ {
+ if (!disposedValue)
+ {
+ Close();
+ disposedValue = true;
+ }
+ base.Dispose(disposing);
+ }
+
+ /// <summary>
+ /// Resets the internal buffer window
+ /// </summary>
+ protected void ClearBuffer()
+ {
+ _buffer.Reset();
+ }
+
+ void IVnTextReader.Advance(int count) => _buffer.AdvanceStart(count);
+ void IVnTextReader.FillBuffer() => _buffer.AccumulateData(BaseStream);
+ ERRNO IVnTextReader.CompactBufferWindow() => _buffer.CompactBufferWindow();
+ }
+} \ No newline at end of file
diff --git a/lib/Utils/src/IO/VnStreamWriter.cs b/lib/Utils/src/IO/VnStreamWriter.cs
new file mode 100644
index 0000000..f875932
--- /dev/null
+++ b/lib/Utils/src/IO/VnStreamWriter.cs
@@ -0,0 +1,292 @@
+/*
+* Copyright (c) 2022 Vaughn Nugent
+*
+* Library: VNLib
+* Package: VNLib.Utils
+* File: VnStreamWriter.cs
+*
+* VnStreamWriter.cs is part of VNLib.Utils which is part of the larger
+* VNLib collection of libraries and utilities.
+*
+* VNLib.Utils is free software: you can redistribute it and/or modify
+* it under the terms of the GNU General Public License as published
+* by the Free Software Foundation, either version 2 of the License,
+* or (at your option) any later version.
+*
+* VNLib.Utils is distributed in the hope that it will be useful,
+* but WITHOUT ANY WARRANTY; without even the implied warranty of
+* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+* General Public License for more details.
+*
+* You should have received a copy of the GNU General Public License
+* along with VNLib.Utils. If not, see http://www.gnu.org/licenses/.
+*/
+
+using System;
+using System.IO;
+using System.Text;
+using System.Buffers;
+using System.Threading;
+using System.Threading.Tasks;
+using System.Runtime.InteropServices;
+using System.Runtime.CompilerServices;
+
+using VNLib.Utils.Memory;
+
+namespace VNLib.Utils.IO
+{
+ /// <summary>
+ /// Provides a memory optimized <see cref="TextWriter"/> implementation. Optimized for writing
+ /// to network streams
+ /// </summary>
+ public class VnStreamWriter : TextWriter
+ {
+ private readonly Encoder Enc;
+
+ private readonly ISlindingWindowBuffer<byte> _buffer;
+
+ private bool closed;
+
+ /// <summary>
+ /// Gets the underlying stream that interfaces with the backing store
+ /// </summary>
+ public virtual Stream BaseStream { get; }
+ ///<inheritdoc/>
+ public override Encoding Encoding { get; }
+
+ /// <summary>
+ /// Line termination to use when writing lines to the output
+ /// </summary>
+ public ReadOnlyMemory<byte> LineTermination { get; set; }
+ ///<inheritdoc/>
+ public override string NewLine
+ {
+ get => Encoding.GetString(LineTermination.Span);
+ set => LineTermination = Encoding.GetBytes(value);
+ }
+
+ /// <summary>
+ /// Creates a new <see cref="VnStreamWriter"/> that writes formatted data
+ /// to the specified base stream
+ /// </summary>
+ /// <param name="baseStream">The stream to write data to</param>
+ /// <param name="encoding">The <see cref="Encoding"/> to use when writing data</param>
+ /// <param name="bufferSize">The size of the internal buffer used to buffer binary data before writing to the base stream</param>
+ public VnStreamWriter(Stream baseStream, Encoding encoding, int bufferSize = 1024)
+ {
+ //Store base stream
+ BaseStream = baseStream ?? throw new ArgumentNullException(nameof(baseStream));
+ Encoding = encoding ?? throw new ArgumentNullException(nameof(encoding));
+ //Get an encoder
+ Enc = encoding.GetEncoder();
+ _buffer = InitializeBuffer(bufferSize);
+ }
+
+ /// <summary>
+ /// Invoked by the constuctor method to allocte the internal buffer with the specified buffer size.
+ /// </summary>
+ /// <param name="bufferSize">The requested size of the buffer to alloc</param>
+ /// <remarks>By default requests the buffer from the <see cref="MemoryPool{T}.Shared"/> instance</remarks>
+ protected virtual ISlindingWindowBuffer<byte> InitializeBuffer(int bufferSize) => new ArrayPoolStreamBuffer<byte>(ArrayPool<byte>.Shared, bufferSize);
+ ///<inheritdoc/>
+ public void Write(byte value)
+ {
+ //See if there is room in the binary buffer
+ if (_buffer.AccumulatedSize == 0)
+ {
+ //There is not enough room to store the single byte
+ Flush();
+ }
+ //Store at the end of the window
+ _buffer.Append(value);
+ }
+ ///<inheritdoc/>
+ public override void Write(char value)
+ {
+ ReadOnlySpan<char> tbuf = MemoryMarshal.CreateSpan(ref value, 0x01);
+ Write(tbuf);
+ }
+ ///<inheritdoc/>
+ public override void Write(object? value) => Write(value?.ToString());
+ ///<inheritdoc/>
+ public override void Write(string? value) => Write(value.AsSpan());
+ ///<inheritdoc/>
+ public override void Write(ReadOnlySpan<char> buffer)
+ {
+ Check();
+
+ ForwardOnlyReader<char> reader = new(buffer);
+
+ //Create a variable for a character buffer window
+ bool completed;
+ do
+ {
+ //Get an available buffer window to store characters in and convert the characters to binary
+ Enc.Convert(reader.Window, _buffer.Remaining, true, out int charsUsed, out int bytesUsed, out completed);
+ //Update byte position
+ _buffer.Advance(bytesUsed);
+ //Update char position
+ reader.Advance(charsUsed);
+
+ //Converting did not complete because the buffer was too small
+ if (!completed || reader.WindowSize == 0)
+ {
+ //Flush the buffer and continue
+ Flush();
+ }
+
+ } while (!completed);
+ //Reset the encoder
+ Enc.Reset();
+ }
+ ///<inheritdoc/>
+ public override async Task WriteAsync(ReadOnlyMemory<char> buffer, CancellationToken cancellationToken = default)
+ {
+ Check();
+ //Create a variable for a character buffer window
+ bool completed;
+ ForwardOnlyMemoryReader<char> reader = new(buffer);
+ do
+ {
+ //Get an available buffer window to store characters in and convert the characters to binary
+ Enc.Convert(reader.Window.Span, _buffer.Remaining, true, out int charsUsed, out int bytesUsed, out completed);
+ //Update byte position
+ _buffer.Advance(bytesUsed);
+ //Update char position
+ reader.Advance(charsUsed);
+ //Converting did not complete because the buffer was too small
+ if (!completed || reader.WindowSize == 0)
+ {
+ //Flush the buffer and continue
+ await FlushWriterAsync(cancellationToken);
+ }
+ } while (!completed);
+ //Reset the encoder
+ Enc.Reset();
+ }
+
+ ///<inheritdoc/>
+ public override void WriteLine()
+ {
+ Check();
+ //See if there is room in the binary buffer
+ if (_buffer.RemainingSize < LineTermination.Length)
+ {
+ //There is not enough room to store the termination, so we need to flush the buffer
+ Flush();
+ }
+ _buffer.Append(LineTermination.Span);
+ }
+ ///<inheritdoc/>
+ public override void WriteLine(object? value) => WriteLine(value?.ToString());
+ ///<inheritdoc/>
+ public override void WriteLine(string? value) => WriteLine(value.AsSpan());
+ ///<inheritdoc/>
+ public override void WriteLine(ReadOnlySpan<char> buffer)
+ {
+ //Write the value itself
+ Write(buffer);
+ //Write the line termination
+ WriteLine();
+ }
+
+ ///<inheritdoc/>
+ ///<exception cref="ObjectDisposedException"></exception>
+ public override void Flush()
+ {
+ Check();
+ //If data is available to be written, write it to the base stream
+ if (_buffer.AccumulatedSize > 0)
+ {
+ //Write all buffered data to stream
+ BaseStream.Write(_buffer.Accumulated);
+ //Reset the buffer
+ _buffer.Reset();
+ }
+ }
+ /// <summary>
+ /// Asynchronously flushes the internal buffers to the <see cref="BaseStream"/>, and resets the internal buffer state
+ /// </summary>
+ /// <returns>A <see cref="ValueTask"/> that represents the asynchronous flush operation</returns>
+ /// <exception cref="ObjectDisposedException"></exception>
+ public async ValueTask FlushWriterAsync(CancellationToken cancellationToken = default)
+ {
+ Check();
+ if (_buffer.AccumulatedSize > 0)
+ {
+ //Flush current window to the stream
+ await BaseStream.WriteAsync(_buffer.AccumulatedBuffer, cancellationToken);
+ //Reset the buffer
+ _buffer.Reset();
+ }
+ }
+
+ ///<inheritdoc/>
+ public override Task FlushAsync() => FlushWriterAsync().AsTask();
+
+ /// <summary>
+ /// Resets internal properies for resuse
+ /// </summary>
+ protected void Reset()
+ {
+ _buffer.Reset();
+ Enc.Reset();
+ }
+ ///<inheritdoc/>
+ public override void Close()
+ {
+ //Only invoke close once
+ if (closed)
+ {
+ return;
+ }
+ try
+ {
+ Flush();
+ }
+ finally
+ {
+ //Release the memory handle if its set
+ _buffer.Close();
+ //Set closed flag
+ closed = true;
+ }
+ }
+ ///<inheritdoc/>
+ protected override void Dispose(bool disposing)
+ {
+ Close();
+ base.Dispose(disposing);
+ }
+
+ [MethodImpl(MethodImplOptions.AggressiveInlining)]
+ private void Check()
+ {
+ if (closed)
+ {
+ throw new ObjectDisposedException("The stream is closed");
+ }
+ }
+ ///<inheritdoc/>
+ public override async ValueTask DisposeAsync()
+ {
+ //Only invoke close once
+ if (closed)
+ {
+ return;
+ }
+ try
+ {
+ await FlushWriterAsync();
+ }
+ finally
+ {
+ //Set closed flag
+ closed = true;
+ //Release the memory handle if its set
+ _buffer.Close();
+ }
+ GC.SuppressFinalize(this);
+ }
+ }
+} \ No newline at end of file
diff --git a/lib/Utils/src/IO/VnTextReaderExtensions.cs b/lib/Utils/src/IO/VnTextReaderExtensions.cs
new file mode 100644
index 0000000..119461b
--- /dev/null
+++ b/lib/Utils/src/IO/VnTextReaderExtensions.cs
@@ -0,0 +1,223 @@
+/*
+* Copyright (c) 2022 Vaughn Nugent
+*
+* Library: VNLib
+* Package: VNLib.Utils
+* File: VnTextReaderExtensions.cs
+*
+* VnTextReaderExtensions.cs is part of VNLib.Utils which is part of the larger
+* VNLib collection of libraries and utilities.
+*
+* VNLib.Utils is free software: you can redistribute it and/or modify
+* it under the terms of the GNU General Public License as published
+* by the Free Software Foundation, either version 2 of the License,
+* or (at your option) any later version.
+*
+* VNLib.Utils is distributed in the hope that it will be useful,
+* but WITHOUT ANY WARRANTY; without even the implied warranty of
+* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+* General Public License for more details.
+*
+* You should have received a copy of the GNU General Public License
+* along with VNLib.Utils. If not, see http://www.gnu.org/licenses/.
+*/
+
+using System;
+
+using VNLib.Utils.Extensions;
+
+namespace VNLib.Utils.IO
+{
+ /// <summary>
+ /// Extension methods to help reuse code for used TextReader implementations
+ /// </summary>
+ public static class VnTextReaderExtensions
+ {
+ public const int E_BUFFER_TOO_SMALL = -1;
+
+
+ /*
+ * Generic extensions provide constained compiler method invocation
+ * for structs the implement the IVNtextReader
+ */
+
+ /// <summary>
+ /// Attempts to read a line from the stream and store it in the specified buffer
+ /// </summary>
+ /// <param name="reader"></param>
+ /// <param name="charBuffer">The character buffer to write data to</param>
+ /// <returns>Returns the number of bytes read, <see cref="E_BUFFER_TOO_SMALL"/>
+ /// if the buffer was not large enough, 0 if no data was available</returns>
+ /// <exception cref="OutOfMemoryException"></exception>
+ /// <remarks>Allows reading lines of data from the stream without allocations</remarks>
+ public static ERRNO ReadLine<T>(this ref T reader, Span<char> charBuffer) where T:struct, IVnTextReader
+ {
+ return readLine(ref reader, charBuffer);
+ }
+ /// <summary>
+ /// Attempts to read a line from the stream and store it in the specified buffer
+ /// </summary>
+ /// <param name="reader"></param>
+ /// <param name="charBuffer">The character buffer to write data to</param>
+ /// <returns>Returns the number of bytes read, <see cref="E_BUFFER_TOO_SMALL"/>
+ /// if the buffer was not large enough, 0 if no data was available</returns>
+ /// <exception cref="OutOfMemoryException"></exception>
+ /// <remarks>Allows reading lines of data from the stream without allocations</remarks>
+ public static ERRNO ReadLine<T>(this T reader, Span<char> charBuffer) where T : class, IVnTextReader
+ {
+ return readLine(ref reader, charBuffer);
+ }
+
+ /// <summary>
+ /// Fill a buffer with reamining buffered data
+ /// </summary>
+ /// <param name="reader"></param>
+ /// <param name="buffer">Buffer to copy data to</param>
+ /// <param name="offset">Offset in buffer to begin writing</param>
+ /// <param name="count">Number of bytes to read</param>
+ /// <returns>The number of bytes copied to the input buffer</returns>
+ public static int ReadRemaining<T>(this ref T reader, byte[] buffer, int offset, int count) where T : struct, IVnTextReader
+ {
+ return reader.ReadRemaining(buffer.AsSpan(offset, count));
+ }
+ /// <summary>
+ /// Fill a buffer with reamining buffered data
+ /// </summary>
+ /// <param name="reader"></param>
+ /// <param name="buffer">Buffer to copy data to</param>
+ /// <param name="offset">Offset in buffer to begin writing</param>
+ /// <param name="count">Number of bytes to read</param>
+ /// <returns>The number of bytes copied to the input buffer</returns>
+ public static int ReadRemaining<T>(this T reader, byte[] buffer, int offset, int count) where T : class, IVnTextReader
+ {
+ return reader.ReadRemaining(buffer.AsSpan(offset, count));
+ }
+
+ /// <summary>
+ /// Fill a buffer with reamining buffered data, up to
+ /// the size of the supplied buffer
+ /// </summary>
+ /// <param name="reader"></param>
+ /// <param name="buffer">Buffer to copy data to</param>
+ /// <returns>The number of bytes copied to the input buffer</returns>
+ /// <remarks>You should use the <see cref="IVnTextReader.Available"/> property to know how much remaining data is buffered</remarks>
+ public static int ReadRemaining<T>(this ref T reader, Span<byte> buffer) where T : struct, IVnTextReader
+ {
+ return readRemaining(ref reader, buffer);
+ }
+ /// <summary>
+ /// Fill a buffer with reamining buffered data, up to
+ /// the size of the supplied buffer
+ /// </summary>
+ /// <param name="reader"></param>
+ /// <param name="buffer">Buffer to copy data to</param>
+ /// <returns>The number of bytes copied to the input buffer</returns>
+ /// <remarks>You should use the <see cref="IVnTextReader.Available"/> property to know how much remaining data is buffered</remarks>
+ public static int ReadRemaining<T>(this T reader, Span<byte> buffer) where T : class, IVnTextReader
+ {
+ return readRemaining(ref reader, buffer);
+ }
+
+ private static ERRNO readLine<T>(ref T reader, Span<char> chars) where T: IVnTextReader
+ {
+ /*
+ * I am aware of a potential bug, the line decoding process
+ * shifts the interal buffer by the exact number of bytes to
+ * the end of the line, without considering if the decoder failed
+ * to properly decode the entire line.
+ *
+ * I dont expect this to be an issue unless there is a bug within the specified
+ * encoder implementation
+ */
+ ReadOnlySpan<byte> LineTermination = reader.LineTermination.Span;
+ //If buffered data is available, check for line termination
+ if (reader.Available > 0)
+ {
+ //Get current buffer window
+ ReadOnlySpan<byte> bytes = reader.BufferedDataWindow;
+ //search for line termination in current buffer
+ int term = bytes.IndexOf(LineTermination);
+ //Termination found in buffer window
+ if (term > -1)
+ {
+ //Capture the line from the begining of the window to the termination
+ ReadOnlySpan<byte> line = bytes[..term];
+ //Get the number ot chars
+ int charCount = reader.Encoding.GetCharCount(line);
+ //See if the buffer is large enough
+ if (bytes.Length < charCount)
+ {
+ return E_BUFFER_TOO_SMALL;
+ }
+ //Use the decoder to convert the data
+ _ = reader.Encoding.GetChars(line, chars);
+ //Shift the window to the end of the line (excluding the termination, regardless of the conversion result)
+ reader.Advance(term + LineTermination.Length);
+ //Return the number of characters
+ return charCount;
+ }
+ //Termination not found but there may be more data waiting
+ }
+ //Compact the buffer window and make sure it was compacted so there is room to fill the buffer
+ if (reader.CompactBufferWindow())
+ {
+ //There is room, so buffer more data
+ reader.FillBuffer();
+ //Check again to see if more data is buffered
+ if (reader.Available <= 0)
+ {
+ //No data avialable
+ return 0;
+ }
+ //Get current buffer window
+ ReadOnlySpan<byte> bytes = reader.BufferedDataWindow;
+ //search for line termination in current buffer
+ int term = bytes.IndexOf(LineTermination);
+ //Termination found in buffer window
+ if (term > -1)
+ {
+ //Capture the line from the begining of the window to the termination
+ ReadOnlySpan<byte> line = bytes[..term];
+ //Get the number ot chars
+ int charCount = reader.Encoding.GetCharCount(line);
+ //See if the buffer is large enough
+ if (bytes.Length < charCount)
+ {
+ return E_BUFFER_TOO_SMALL;
+ }
+ //Use the decoder to convert the data
+ _ = reader.Encoding.GetChars(line, chars);
+ //Shift the window to the end of the line (excluding the termination, regardless of the conversion result)
+ reader.Advance(term + LineTermination.Length);
+ //Return the number of characters
+ return charCount;
+ }
+ }
+
+ //Termination not found within the entire buffer, so buffer space has been exhausted
+
+ //Supress as this response is expected when the buffer is exhausted,
+#pragma warning disable CA2201 // Do not raise reserved exception types
+ throw new OutOfMemoryException("The line was not found within the current buffer, cannot continue");
+#pragma warning restore CA2201 // Do not raise reserved exception types
+ }
+
+ private static int readRemaining<T>(ref T reader, Span<byte> buffer) where T: IVnTextReader
+ {
+ //guard for empty buffer
+ if (buffer.Length == 0 || reader.Available == 0)
+ {
+ return 0;
+ }
+ //get the remaining bytes in the reader
+ Span<byte> remaining = reader.BufferedDataWindow;
+ //Calculate the number of bytes to copy
+ int canCopy = Math.Min(remaining.Length, buffer.Length);
+ //Copy remaining bytes to buffer
+ remaining[..canCopy].CopyTo(buffer);
+ //Shift the window by the number of bytes copied
+ reader.Advance(canCopy);
+ return canCopy;
+ }
+ }
+} \ No newline at end of file
diff --git a/lib/Utils/src/IO/WriteOnlyBufferedStream.cs b/lib/Utils/src/IO/WriteOnlyBufferedStream.cs
new file mode 100644
index 0000000..5e7faa1
--- /dev/null
+++ b/lib/Utils/src/IO/WriteOnlyBufferedStream.cs
@@ -0,0 +1,255 @@
+/*
+* Copyright (c) 2022 Vaughn Nugent
+*
+* Library: VNLib
+* Package: VNLib.Utils
+* File: WriteOnlyBufferedStream.cs
+*
+* WriteOnlyBufferedStream.cs is part of VNLib.Utils which is part of the larger
+* VNLib collection of libraries and utilities.
+*
+* VNLib.Utils is free software: you can redistribute it and/or modify
+* it under the terms of the GNU General Public License as published
+* by the Free Software Foundation, either version 2 of the License,
+* or (at your option) any later version.
+*
+* VNLib.Utils is distributed in the hope that it will be useful,
+* but WITHOUT ANY WARRANTY; without even the implied warranty of
+* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+* General Public License for more details.
+*
+* You should have received a copy of the GNU General Public License
+* along with VNLib.Utils. If not, see http://www.gnu.org/licenses/.
+*/
+
+using System;
+using System.IO;
+using System.Buffers;
+using System.Threading;
+using System.Threading.Tasks;
+
+using VNLib.Utils.Memory;
+
+namespace VNLib.Utils.IO
+{
+ /// <summary>
+ /// A basic accumulator style write buffered stream
+ /// </summary>
+ public class WriteOnlyBufferedStream : Stream
+ {
+ private readonly ISlindingWindowBuffer<byte> _buffer;
+ private readonly bool LeaveOpen;
+
+ /// <summary>
+ /// Gets the underlying stream that interfaces with the backing store
+ /// </summary>
+ public Stream BaseStream { get; init; }
+
+ /// <summary>
+ /// Initalizes a new <see cref="WriteOnlyBufferedStream"/> using the
+ /// specified backing stream, using the specified buffer size, and
+ /// optionally leaves the stream open
+ /// </summary>
+ /// <param name="baseStream">The backing stream to write buffered data to</param>
+ /// <param name="bufferSize">The size of the internal buffer</param>
+ /// <param name="leaveOpen">A value indicating of the stream should be left open when the buffered stream is closed</param>
+ public WriteOnlyBufferedStream(Stream baseStream, int bufferSize, bool leaveOpen = false)
+ {
+ BaseStream = baseStream;
+ //Create buffer
+ _buffer = InitializeBuffer(bufferSize);
+ LeaveOpen = leaveOpen;
+ }
+ /// <summary>
+ /// Invoked by the constuctor method to allocte the internal buffer with the specified buffer size.
+ /// </summary>
+ /// <param name="bufferSize">The requested size of the buffer to alloc</param>
+ /// <remarks>By default requests the buffer from the <see cref="ArrayPool{T}.Shared"/> instance</remarks>
+ protected virtual ISlindingWindowBuffer<byte> InitializeBuffer(int bufferSize)
+ {
+ return new ArrayPoolStreamBuffer<byte>(ArrayPool<byte>.Shared, bufferSize);
+ }
+
+ ///<inheritdoc/>
+ public override void Close()
+ {
+ try
+ {
+ //Make sure the buffer is empty
+ WriteBuffer();
+
+ if (!LeaveOpen)
+ {
+ //Dispose stream
+ BaseStream.Dispose();
+ }
+ }
+ finally
+ {
+ _buffer.Close();
+ }
+ }
+ ///<inheritdoc/>
+ public override async ValueTask DisposeAsync()
+ {
+ try
+ {
+ if (_buffer.AccumulatedSize > 0)
+ {
+ await WriteBufferAsync(CancellationToken.None);
+ }
+
+ if (!LeaveOpen)
+ {
+ //Dispose stream
+ await BaseStream.DisposeAsync();
+ }
+
+ GC.SuppressFinalize(this);
+ }
+ finally
+ {
+ _buffer.Close();
+ }
+ }
+
+ ///<inheritdoc/>
+ public override void Flush() => WriteBuffer();
+ ///<inheritdoc/>
+ public override Task FlushAsync(CancellationToken cancellationToken) => WriteBufferAsync(cancellationToken).AsTask();
+
+ private void WriteBuffer()
+ {
+ //Only if data is available to write
+ if (_buffer.AccumulatedSize > 0)
+ {
+ //Write data to stream
+ BaseStream.Write(_buffer.Accumulated);
+ //Reset position
+ _buffer.Reset();
+ }
+ }
+
+ private async ValueTask WriteBufferAsync(CancellationToken token = default)
+ {
+ if(_buffer.AccumulatedSize > 0)
+ {
+ await BaseStream.WriteAsync(_buffer.AccumulatedBuffer, token);
+ _buffer.Reset();
+ }
+ }
+ ///<inheritdoc/>
+ public override void Write(byte[] buffer, int offset, int count) => Write(buffer.AsSpan(offset, count));
+
+ public override void Write(ReadOnlySpan<byte> buffer)
+ {
+ ForwardOnlyReader<byte> reader = new(buffer);
+ //Attempt to buffer/flush data until all data is sent
+ do
+ {
+ //Try to buffer as much as possible
+ ERRNO buffered = _buffer.TryAccumulate(reader.Window);
+
+ if(buffered < reader.WindowSize)
+ {
+ //Buffer is full and needs to be flushed
+ WriteBuffer();
+ //Advance reader and continue to buffer
+ reader.Advance(buffered);
+ continue;
+ }
+
+ break;
+ }
+ while (true);
+ }
+
+ public override Task WriteAsync(byte[] buffer, int offset, int count, CancellationToken cancellationToken)
+ {
+ return WriteAsync(buffer.AsMemory(offset, count), cancellationToken).AsTask();
+ }
+
+ public async override ValueTask WriteAsync(ReadOnlyMemory<byte> buffer, CancellationToken cancellationToken = default)
+ {
+ ForwardOnlyMemoryReader<byte> reader = new(buffer);
+ //Attempt to buffer/flush data until all data is sent
+ do
+ {
+ //Try to buffer as much as possible
+ ERRNO buffered = _buffer.TryAccumulate(reader.Window.Span);
+
+ if (buffered < reader.WindowSize)
+ {
+ //Buffer is full and needs to be flushed
+ await WriteBufferAsync(cancellationToken);
+ //Advance reader and continue to buffer
+ reader.Advance(buffered);
+ continue;
+ }
+
+ break;
+ }
+ while (true);
+ }
+
+
+ /// <summary>
+ /// Always false
+ /// </summary>
+ public override bool CanRead => false;
+ /// <summary>
+ /// Always returns false
+ /// </summary>
+ public override bool CanSeek => false;
+ /// <summary>
+ /// Always true
+ /// </summary>
+ public override bool CanWrite => true;
+ /// <summary>
+ /// Returns the size of the underlying buffer
+ /// </summary>
+ public override long Length => _buffer.AccumulatedSize;
+ /// <summary>
+ /// Always throws <see cref="NotSupportedException"/>
+ /// </summary>
+ /// <exception cref="NotSupportedException"></exception>
+ public override long Position { get => throw new NotSupportedException(); set => throw new NotSupportedException(); }
+ /// <summary>
+ /// Always throws <see cref="NotSupportedException"/>
+ /// </summary>
+ /// <exception cref="NotSupportedException"></exception>
+ /// <returns></returns>
+ public override int Read(byte[] buffer, int offset, int count)
+ {
+ throw new NotSupportedException("This stream is not readable");
+ }
+
+ /// <summary>
+ /// Always throws <see cref="NotSupportedException"/>
+ /// </summary>
+ /// <exception cref="NotSupportedException"></exception>
+ public override long Seek(long offset, SeekOrigin origin)
+ {
+ throw new NotSupportedException();
+ }
+
+ /// <summary>
+ /// Always throws <see cref="NotSupportedException"/>
+ /// </summary>
+ /// <exception cref="NotSupportedException"></exception>
+ public override void SetLength(long value)
+ {
+ throw new NotSupportedException();
+ }
+
+ public override Task<int> ReadAsync(byte[] buffer, int offset, int count, CancellationToken cancellationToken)
+ {
+ throw new NotImplementedException();
+ }
+
+ public override ValueTask<int> ReadAsync(Memory<byte> buffer, CancellationToken cancellationToken = default)
+ {
+ throw new NotImplementedException();
+ }
+ }
+}