BinaryStream.cs 14.8 KB
Newer Older
Ray Koopa's avatar
Ray Koopa committed
1
using System;
2
using System.Collections.Generic;
Ray Koopa's avatar
Ray Koopa committed
3 4 5
using System.Diagnostics;
using System.IO;
using System.Text;
Ray Koopa's avatar
Ray Koopa committed
6
using System.Threading;
7
using System.Threading.Tasks;
Ray Koopa's avatar
Ray Koopa committed
8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63

namespace Syroot.BinaryData
{
    /// <summary>
    /// Represents a wrapper around a <see cref="Stream"/> to read from and write to it with default data format
    /// configurations.
    /// </summary>
    [DebuggerDisplay(nameof(BinaryStream) + ", " + nameof(Position) + "={" + nameof(Position) + "}")]
    public partial class BinaryStream : Stream
    {
        // ---- FIELDS -------------------------------------------------------------------------------------------------

        private ByteConverter _byteConverter;
        private Encoding _encoding;
        private bool _leaveOpen;
        private bool _disposed;

        // ---- CONSTRUCTORS -------------------------------------------------------------------------------------------

        /// <summary>
        /// Initializes a new instance of the <see cref="BinaryStream"/> class with the given default configuration.
        /// </summary>
        /// <param name="baseStream">The output stream.</param>
        /// <param name="converter">The <see cref="ByteConverter"/> to use. Defaults to
        /// <see cref="ByteConverter.System"/>.</param>
        /// <param name="encoding">The character encoding to use. Defaults to <see cref="Encoding.UTF8"/>.</param>
        /// <param name="booleanCoding">The <see cref="BinaryData.BooleanCoding"/> data format to use  for
        /// <see cref="Boolean"/> values.</param>
        /// <param name="dateTimeCoding">The <see cref="BinaryData.DateTimeCoding"/> data format to use for
        /// <see cref="DateTime"/> values.</param>
        /// <param name="stringCoding">The <see cref="BinaryData.StringCoding"/> data format to use for
        /// <see cref="String"/> values.</param>
        /// <param name="leaveOpen"><c>true</c> to leave the base stream open after the <see cref="BinaryStream"/>
        /// object is disposed; otherwise <c>false</c>.</param>
        /// <exception cref="ArgumentException">The stream does not support writing or is already closed.</exception>
        /// <exception cref="ArgumentNullException">output is null.</exception>
        public BinaryStream(Stream baseStream, ByteConverter converter = null, Encoding encoding = null,
            BooleanCoding booleanCoding = BooleanCoding.Byte, DateTimeCoding dateTimeCoding = DateTimeCoding.NetTicks,
            StringCoding stringCoding = StringCoding.VariableByteCount, bool leaveOpen = false)
        {
            BaseStream = baseStream;
            ByteConverter = converter;
            Encoding = encoding;
            BooleanCoding = booleanCoding;
            DateTimeCoding = dateTimeCoding;
            StringCoding = stringCoding;
            _leaveOpen = leaveOpen;
        }

        // ---- PROPERTIES ---------------------------------------------------------------------------------------------

        /// <summary>
        /// Gets the underlying <see cref="Stream"/> the instance works on.
        /// </summary>
        public Stream BaseStream { get; }

64 65 66 67 68 69 70 71 72 73
        /// <summary>
        /// Gets a value indicating whether the end of the stream has been reached.
        /// </summary>
        public bool EndOfStream
            => BaseStream.IsEndOfStream();

        // ---- Configuration ----

        /// <summary>
        /// Gets or sets the <see cref="ByteConverter"/> instance used to parse multibyte binary data with.
74
        /// Setting this value to <c>null</c> will restore the default <see cref="ByteConverter.System"/>.
75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108
        /// </summary>
        public ByteConverter ByteConverter
        {
            get => _byteConverter;
            set => _byteConverter = value ?? ByteConverter.System;
        }

        /// <summary>
        /// Gets or sets the encoding used for string related operations where no other encoding has been provided.
        /// Setting this value to <c>null</c> will restore the default <see cref="Encoding.UTF8"/>.
        /// </summary>
        public Encoding Encoding
        {
            get => _encoding;
            set => _encoding = value ?? Encoding.UTF8;
        }

        /// <summary>
        /// Gets the <see cref="BinaryData.BooleanCoding"/> to use for <see cref="Boolean"/> values.
        /// </summary>
        public BooleanCoding BooleanCoding { get; set; }

        /// <summary>
        /// Gets the <see cref="BinaryData.DateTimeCoding"/> to use for <see cref="DateTime"/> values.
        /// </summary>
        public DateTimeCoding DateTimeCoding { get; set; }

        /// <summary>
        /// Gets the <see cref="BinaryData.StringCoding"/> to use for <see cref="String"/> values.
        /// </summary>
        public StringCoding StringCoding { get; set; }

        // ---- Stream implementation ----

Ray Koopa's avatar
Ray Koopa committed
109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142
        /// <summary>
        /// Gets a value indicating whether the underlying stream supports reading.
        /// </summary>
        public override bool CanRead
            => BaseStream.CanRead;

        /// <summary>
        /// Gets a value indicating whether the underlying stream supports seeking.
        /// </summary>
        public override bool CanSeek
            => BaseStream.CanSeek;

        /// <summary>
        /// Gets a value indicating whether the underlying stream supports writing.
        /// </summary>
        public override bool CanWrite
            => BaseStream.CanWrite;

        /// <summary>
        /// Gets or sets the length in bytes of the stream in bytes.
        /// </summary>
        public override long Length
            => BaseStream.Length;

        /// <summary>
        /// Gets or sets the position within the current stream. This is a shortcut to the base stream Position
        /// property.
        /// </summary>
        public override long Position
        {
            get => BaseStream.Position;
            set => BaseStream.Position = value;
        }

143
        // ---- METHODS (PUBLIC) ---------------------------------------------------------------------------------------
Ray Koopa's avatar
Ray Koopa committed
144 145

        /// <summary>
146
        /// Aligns the underlying stream to the given byte multiple.
Ray Koopa's avatar
Ray Koopa committed
147
        /// </summary>
148 149 150 151 152 153 154
        /// <param name="alignment">The byte multiple to align to. If negative, the position is decreased to the
        /// previous multiple rather than the next one.</param>
        /// <param name="grow"><c>true</c> to enlarge the stream size to include the final position in case it is larger
        /// than the current stream length.</param>
        /// <returns>The new position within the current stream.</returns>
        public long Align(long alignment, bool grow = false)
            => BaseStream.Align(alignment, grow);
Ray Koopa's avatar
Ray Koopa committed
155 156

        /// <summary>
157 158
        /// Sets the position within the underlying stream relative to the current position. If the stream is
        /// not seekable, it tries to simulates advancing the position by reading or writing 0-bytes.
Ray Koopa's avatar
Ray Koopa committed
159
        /// </summary>
160 161 162
        /// <param name="offset">A byte offset relative to the origin parameter.</param>
        public void Move(long offset)
            => BaseStream.Move(offset);
Ray Koopa's avatar
Ray Koopa committed
163

Ray Koopa's avatar
Ray Koopa committed
164 165 166 167 168 169 170 171 172 173
        /// <summary>
        /// Sets the position within the underlying stream relative to the current position. If the stream is not
        /// seekable, it tries to simulates advancing the position by reading or writing 0-bytes asynchronously.
        /// </summary>
        /// <param name="offset">A byte offset relative to the origin parameter.</param>
        /// <param name="cancellationToken">The token to monitor for cancellation requests.</param>
        public async Task MoveAsync(long offset,
            CancellationToken cancellationToken = default)
            => await BaseStream.MoveAsync(offset, cancellationToken);

Ray Koopa's avatar
Ray Koopa committed
174
        /// <summary>
175
        /// Sets the position within the underlying stream relative to the current position.
Ray Koopa's avatar
Ray Koopa committed
176
        /// </summary>
177 178 179 180
        /// <param name="offset">A byte offset relative to the current position in the stream.</param>
        /// <returns>The new position within the current stream.</returns>
        public long Seek(long offset)
            => BaseStream.Seek(offset);
Ray Koopa's avatar
Ray Koopa committed
181 182

        /// <summary>
183 184
        /// Creates a <see cref="BinaryData.Seek"/> with the given parameters. As soon as the returned
        /// <see cref="BinaryData.Seek"/> is disposed, the previous stream position will be restored.
Ray Koopa's avatar
Ray Koopa committed
185
        /// </summary>
186 187 188 189 190 191 192 193
        /// <param name="offset">A byte offset relative to the origin parameter.</param>
        /// <param name="origin">A value of type <see cref="SeekOrigin"/> indicating the reference point used to obtain
        /// the new position.</param>
        /// <returns>The <see cref="BinaryData.Seek"/> to be disposed to undo the seek.</returns>
        public Seek TemporarySeek(long offset = 0, SeekOrigin origin = SeekOrigin.Current)
            => BaseStream.TemporarySeek(offset, origin);

        // ---- Read ----
Ray Koopa's avatar
Ray Koopa committed
194 195

        /// <summary>
196 197
        /// Returns <paramref name="count"/> instances of type <typeparamref name="T"/> continually read from the
        /// underlying stream by calling the <paramref name="readCallback"/>.
Ray Koopa's avatar
Ray Koopa committed
198
        /// </summary>
199 200 201 202 203 204
        /// <typeparam name="T">The type of the instances to read.</typeparam>
        /// <param name="count">The number of instances to read.</param>
        /// <param name="readCallback">The read callback function invoked for each instance read.</param>
        /// <returns>The array of read instances.</returns>
        public T[] ReadMany<T>(int count, Func<T> readCallback)
            => BaseStream.ReadMany(count, readCallback);
Ray Koopa's avatar
Ray Koopa committed
205

206 207 208 209 210 211 212 213 214 215 216 217 218 219 220 221 222 223 224 225 226 227 228 229 230 231 232 233 234 235 236 237
        /// <summary>
        /// Returns <paramref name="count"/> instances of type <typeparamref name="T"/> continually read asynchronously
        /// from the underlying stream by calling the <paramref name="readCallback"/>.
        /// </summary>
        /// <typeparam name="T">The type of the instances to read.</typeparam>
        /// <param name="count">The number of instances to read.</param>
        /// <param name="readCallback">The read callback function invoked for each instance read.</param>
        /// <returns>The array of read instances.</returns>
        public async Task<T[]> ReadManyAsync<T>(int count, Func<Task<T>> readCallback)
            => await BaseStream.ReadManyAsync(count, readCallback);

        // ---- Write ----

        /// <summary>
        /// Writes the <paramref name="values"/> to the underlying stream through the <paramref name="writeCallback"/>
        /// invoked for each value.
        /// </summary>
        /// <param name="values">The values to write.</param>
        /// <param name="writeCallback">The callback invoked to write each value.</param>
        public void WriteMany<T>(IEnumerable<T> values, Action<T> writeCallback)
            => BaseStream.WriteMany(values, writeCallback);

        /// <summary>
        /// Writes the <paramref name="values"/> to the underlying stream asynchronously through the
        /// <paramref name="writeCallback"/> invoked for each value.
        /// </summary>
        /// <param name="values">The values to write.</param>
        /// <param name="writeCallback">The callback invoked to write each value.</param>
        public async Task WriteManyAsync<T>(IEnumerable<T> values, Func<T, Task> writeCallback)
            => await BaseStream.WriteManyAsync(values, writeCallback);

        // ---- Stream implementation ----
Ray Koopa's avatar
Ray Koopa committed
238 239 240 241 242 243 244 245 246 247 248 249 250 251 252 253 254 255 256 257 258 259 260 261 262 263 264 265 266 267 268 269 270 271 272 273 274 275 276 277 278 279 280 281 282 283 284 285 286 287 288 289 290 291 292 293 294 295 296 297 298 299 300 301 302 303 304 305 306 307 308 309

        /// <summary>
        /// Clears all buffers for this stream and causes any buffered data to be written to the underlying stream.
        /// </summary>
        public override void Flush()
            => BaseStream.Flush();

        /// <summary>
        /// Reads a sequence of bytes from the underlying stream and advances the position within the stream by the
        /// number of bytes read.
        /// </summary>
        /// <param name="buffer">An array of bytes. When this method returns, the buffer contains the specified byte
        /// array with the values between offset and (offset + count - 1) replaced by the bytes read from the underlying
        /// stream.</param>
        /// <param name="offset">The zero-based byte offset in <paramref name="buffer"/> at which to begin storing the
        /// data read from the underlying stream.</param>
        /// <param name="count">The maximum number of bytes to be read from the underlying stream.</param>
        /// <returns>The total number of bytes read into the buffer. This can be less than the number of bytes requested
        /// if that many bytes are not currently available, or zero (0) if the end of the stream has been reached.
        /// </returns>
        public override int Read(byte[] buffer, int offset, int count)
            => BaseStream.Read(buffer, offset, count);

        /// <summary>
        /// Sets the position within the underlying stream.
        /// </summary>
        /// <param name="offset">A byte offset relative to the <paramref name="origin"/> parameter.</param>
        /// <param name="origin">A value of type <see cref="SeekOrigin"/> indicating the reference point used to obtain
        /// the new position.</param>
        /// <returns>The new position within the underlying stream.</returns>
        public override long Seek(long offset, SeekOrigin origin)
            => BaseStream.Seek(offset, origin);

        /// <summary>
        /// Sets the length of the underlying stream.
        /// </summary>
        /// <param name="value">The desired length of the underlying stream in bytes.</param>
        public override void SetLength(long value)
            => BaseStream.SetLength(value);

        /// <summary>
        /// Writes a sequence of bytes to the underlying stream and advances the current position within this stream by
        /// the number of bytes written.
        /// </summary>
        /// <param name="buffer">An array of bytes. This method copies count bytes from buffer to the underlying stream.
        /// </param>
        /// <param name="offset">The zero-based byte offset in buffer at which to begin copying bytes to the underlying
        /// stream.</param>
        /// <param name="count">The number of bytes to be written to the underlying stream.</param>
        public override void Write(byte[] buffer, int offset, int count)
            => BaseStream.Write(buffer, offset, count);

        // ---- METHODS (PROTECTED) ------------------------------------------------------------------------------------

        /// <summary>
        /// Optionally releases the underlying stream.
        /// </summary>
        /// <param name="disposing"><c>true</c> to release both managed and unmanaged resources; <c>false</c> to release
        /// only unmanaged resources.</param>
        protected override void Dispose(bool disposing)
        {
            if (_disposed)
                return;

            if (disposing && !_leaveOpen)
                BaseStream.Dispose();

            _disposed = true;
            base.Dispose(disposing);
        }
    }
}