From 0a5ccd5a9a3a4963589e2cc9e444a0a0ac01cba2 Mon Sep 17 00:00:00 2001 From: Natalia Portillo Date: Sat, 7 Oct 2023 21:29:42 +0100 Subject: [PATCH] [Aaru.Helpers] Move all stream extensions from around the application to this project. --- IO/ForcedSeekStream.cs | 257 ++++++++++ IO/NonClosableStream.cs | 78 +++ IO/OffsetStream.cs | 680 ++++++++++++++++++++++++++ IO/SplitJoinStream.cs | 372 ++++++++++++++ Localization/Localization.Designer.cs | 117 +++++ Localization/Localization.es.resx | 39 ++ Localization/Localization.resx | 39 ++ 7 files changed, 1582 insertions(+) create mode 100644 IO/ForcedSeekStream.cs create mode 100644 IO/NonClosableStream.cs create mode 100644 IO/OffsetStream.cs create mode 100644 IO/SplitJoinStream.cs diff --git a/IO/ForcedSeekStream.cs b/IO/ForcedSeekStream.cs new file mode 100644 index 0000000..fd48c2f --- /dev/null +++ b/IO/ForcedSeekStream.cs @@ -0,0 +1,257 @@ +// /*************************************************************************** +// Aaru Data Preservation Suite +// ---------------------------------------------------------------------------- +// +// Filename : ForcedSeekStream.cs +// Author(s) : Natalia Portillo +// +// Component : Filters. +// +// --[ Description ] ---------------------------------------------------------- +// +// Provides a seekable stream from a forward-readable stream. +// +// --[ License ] -------------------------------------------------------------- +// +// This library is free software; you can redistribute it and/or modify +// it under the terms of the GNU Lesser General Public License as +// published by the Free Software Foundation; either version 2.1 of the +// License, or (at your option) any later version. +// +// This library 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 +// Lesser General Public License for more details. +// +// You should have received a copy of the GNU Lesser General Public +// License along with this library; if not, see . +// +// ---------------------------------------------------------------------------- +// Copyright © 2011-2023 Natalia Portillo +// ****************************************************************************/ + +using System; +using System.IO; + +namespace Aaru.Helpers.IO; + +/// +/// ForcedSeekStream allows to seek a forward-readable stream (like System.IO.Compression streams) by doing the +/// slow and known trick of rewinding and forward reading until arriving the desired position. +/// +/// +public sealed class ForcedSeekStream : Stream where T : Stream +{ + const int BUFFER_LEN = 1048576; + readonly string _backFile; + readonly FileStream _backStream; + readonly T _baseStream; + long _streamLength; + + /// Initializes a new instance of the class. + /// The real (uncompressed) length of the stream. + /// Parameters that are used to create the base stream. + /// + public ForcedSeekStream(long length, params object[] args) + { + _streamLength = length; + _baseStream = (T)Activator.CreateInstance(typeof(T), args); + _backFile = Path.GetTempFileName(); + _backStream = new FileStream(_backFile, FileMode.Open, FileAccess.ReadWrite, FileShare.None); + + if(length == 0) + CalculateLength(); + } + + /// Initializes a new instance of the class. + /// Parameters that are used to create the base stream. + /// + public ForcedSeekStream(params object[] args) + { + _baseStream = (T)Activator.CreateInstance(typeof(T), args); + _backFile = Path.GetTempFileName(); + _backStream = new FileStream(_backFile, FileMode.Open, FileAccess.ReadWrite, FileShare.None); + CalculateLength(); + } + + /// + public override bool CanRead => _baseStream.CanRead; + + /// + public override bool CanSeek => true; + + /// + public override bool CanWrite => false; + + /// + public override long Length => _streamLength; + + /// + public override long Position + { + get => _backStream.Position; + + set => SetPosition(value); + } + + /// + /// Calculates the real (uncompressed) length of the stream. It basically reads (uncompresses) the whole stream to + /// memory discarding its contents, so it should be used as a last resort. + /// + /// The length. + public void CalculateLength() + { + int read; + + do + { + var buffer = new byte[BUFFER_LEN]; + read = _baseStream.EnsureRead(buffer, 0, BUFFER_LEN); + _backStream.Write(buffer, 0, read); + } while(read == BUFFER_LEN); + + _streamLength = _backStream.Length; + _backStream.Position = 0; + } + + void SetPosition(long position) + { + if(position == _backStream.Position) + return; + + if(position < _backStream.Length) + { + _backStream.Position = position; + + return; + } + + if(position > _streamLength) + position = _streamLength; + + _backStream.Position = _backStream.Length; + long toPosition = position - _backStream.Position; + var fullBufferReads = (int)(toPosition / BUFFER_LEN); + var restToRead = (int)(toPosition % BUFFER_LEN); + byte[] buffer; + int bufPos; + int left; + + for(var i = 0; i < fullBufferReads; i++) + { + buffer = new byte[BUFFER_LEN]; + bufPos = 0; + left = BUFFER_LEN; + + while(left > 0) + { + int done = _baseStream.EnsureRead(buffer, bufPos, left); + left -= done; + bufPos += done; + } + + _backStream.Write(buffer, 0, BUFFER_LEN); + } + + buffer = new byte[restToRead]; + bufPos = 0; + left = restToRead; + + while(left > 0) + { + int done = _baseStream.EnsureRead(buffer, bufPos, left); + left -= done; + bufPos += done; + } + + _backStream.Write(buffer, 0, restToRead); + } + + /// + public override void Flush() + { + _baseStream.Flush(); + _backStream.Flush(); + } + + /// + public override int Read(byte[] buffer, int offset, int count) + { + if(_backStream.Position + count > _streamLength) + count = (int)(_streamLength - _backStream.Position); + + if(_backStream.Position + count <= _backStream.Length) + return _backStream.EnsureRead(buffer, offset, count); + + long oldPosition = _backStream.Position; + SetPosition(_backStream.Position + count); + SetPosition(oldPosition); + + return _backStream.EnsureRead(buffer, offset, count); + } + + /// + public override int ReadByte() + { + if(_backStream.Position + 1 > _streamLength) + return -1; + + if(_backStream.Position + 1 <= _backStream.Length) + return _backStream.ReadByte(); + + SetPosition(_backStream.Position + 1); + SetPosition(_backStream.Position - 1); + + return _backStream.ReadByte(); + } + + /// + public override long Seek(long offset, SeekOrigin origin) + { + switch(origin) + { + case SeekOrigin.Begin: + if(offset < 0) + throw new IOException(Localization.Cannot_seek_before_stream_start); + + SetPosition(offset); + + break; + case SeekOrigin.End: + if(offset > 0) + throw new IOException(Localization.Cannot_seek_after_stream_end); + + if(_streamLength == 0) + CalculateLength(); + + SetPosition(_streamLength + offset); + + break; + default: + SetPosition(_backStream.Position + offset); + + break; + } + + return _backStream.Position; + } + + /// + public override void SetLength(long value) => throw new NotSupportedException(); + + /// + public override void Write(byte[] buffer, int offset, int count) => throw new NotSupportedException(); + + /// + public override void Close() + { + _backStream?.Close(); + File.Delete(_backFile); + } + + ~ForcedSeekStream() + { + _backStream?.Close(); + File.Delete(_backFile); + } +} \ No newline at end of file diff --git a/IO/NonClosableStream.cs b/IO/NonClosableStream.cs new file mode 100644 index 0000000..72c100d --- /dev/null +++ b/IO/NonClosableStream.cs @@ -0,0 +1,78 @@ +// /*************************************************************************** +// Aaru Data Preservation Suite +// ---------------------------------------------------------------------------- +// +// Filename : NonClosableStream.cs +// Author(s) : Natalia Portillo +// +// Component : Compression. +// +// --[ Description ] ---------------------------------------------------------- +// +// Overrides MemoryStream to ignore standard close requests. +// +// --[ License ] -------------------------------------------------------------- +// +// This library is free software; you can redistribute it and/or modify +// it under the terms of the GNU Lesser General Public License as +// published by the Free Software Foundation; either version 2.1 of the +// License, or (at your option) any later version. +// +// This library 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 +// Lesser General Public License for more details. +// +// You should have received a copy of the GNU Lesser General Public +// License along with this library; if not, see . +// +// ---------------------------------------------------------------------------- +// Copyright © 2011-2023 Natalia Portillo +// ****************************************************************************/ + +using System.Diagnostics.CodeAnalysis; +using System.IO; + +namespace Aaru.Helpers.IO; + +/// +/// Creates a MemoryStream that ignores close commands +[SuppressMessage("ReSharper", "UnusedMember.Global")] +public sealed class NonClosableStream : Stream +{ + readonly Stream _baseStream; + + public NonClosableStream(byte[] buffer) => _baseStream = new MemoryStream(buffer); + + public NonClosableStream() => _baseStream = new MemoryStream(); + + public NonClosableStream(Stream stream) => _baseStream = stream; + + public override bool CanRead => _baseStream.CanRead; + public override bool CanSeek => _baseStream.CanSeek; + public override bool CanWrite => _baseStream.CanWrite; + public override long Length => _baseStream.Length; + + public override long Position + { + get => _baseStream.Position; + set => _baseStream.Position = value; + } + + public override void Flush() => _baseStream.Flush(); + + public override int Read(byte[] buffer, int offset, int count) => _baseStream.EnsureRead(buffer, offset, count); + + public override long Seek(long offset, SeekOrigin origin) => _baseStream.Seek(offset, origin); + + public override void SetLength(long value) => _baseStream.SetLength(value); + + public override void Write(byte[] buffer, int offset, int count) => _baseStream.Write(buffer, offset, count); + + public override void Close() + { + // Do nothing + } + + public void ReallyClose() => _baseStream.Close(); +} \ No newline at end of file diff --git a/IO/OffsetStream.cs b/IO/OffsetStream.cs new file mode 100644 index 0000000..809b894 --- /dev/null +++ b/IO/OffsetStream.cs @@ -0,0 +1,680 @@ +// /*************************************************************************** +// Aaru Data Preservation Suite +// ---------------------------------------------------------------------------- +// +// Filename : OffsetStream.cs +// Author(s) : Natalia Portillo +// +// Component : Filters. +// +// --[ Description ] ---------------------------------------------------------- +// +// Provides a stream that's a subset of another stream. +// +// --[ License ] -------------------------------------------------------------- +// +// This library is free software; you can redistribute it and/or modify +// it under the terms of the GNU Lesser General Public License as +// published by the Free Software Foundation; either version 2.1 of the +// License, or (at your option) any later version. +// +// This library 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 +// Lesser General Public License for more details. +// +// You should have received a copy of the GNU Lesser General Public +// License along with this library; if not, see . +// +// ---------------------------------------------------------------------------- +// Copyright © 2011-2023 Natalia Portillo +// ****************************************************************************/ + +using System; +using System.Diagnostics.CodeAnalysis; +using System.IO; +using Microsoft.Win32.SafeHandles; +#if !NETSTANDARD2_0 + +#endif + +namespace Aaru.Helpers.IO; + +/// Creates a stream that is a subset of another stream. +/// +[SuppressMessage("ReSharper", "UnusedMember.Global")] +public sealed class OffsetStream : Stream +{ + readonly Stream _baseStream; + readonly long _streamEnd; + readonly long _streamStart; + + /// + /// + /// Initializes a stream that only allows reading from to of the + /// specified stream, both inclusive. + /// + /// Base stream + /// Start position + /// Last readable position + /// Invalid range + public OffsetStream(Stream stream, long start, long end) + { + if(start < 0) + throw new ArgumentOutOfRangeException(nameof(start), Localization.Start_cant_be_a_negative_number); + + if(end < 0) + throw new ArgumentOutOfRangeException(nameof(end), Localization.End_cant_be_a_negative_number); + + _streamStart = start; + _streamEnd = end; + + _baseStream = stream; + + if(end > _baseStream.Length) + throw new ArgumentOutOfRangeException(nameof(end), Localization.End_is_after_stream_end); + + _baseStream.Position = start; + } + + /// + /// + /// Initializes a stream that only allows reading from to of the + /// specified file, both inclusive. + /// + /// A relative or absolute path for the file that the stream will encapsulate. + /// One of the enumeration values that determines how to open or create the file. + /// + /// A bitwise combination of the enumeration values that determines how the file can be accessed by a + /// object. + /// + /// + /// A bitwise combination of the enumeration values that determines how the file will be shared by + /// processes. + /// + /// + /// A positive Int32 value greater than 0 indicating the buffer size. The default buffer size is + /// 4096. + /// + /// A bitwise combination of the enumeration values that specifies additional file options. + /// Start position + /// Last readable position + /// Invalid range + public OffsetStream(string path, FileMode mode, FileAccess access, FileShare share, int bufferSize, + FileOptions options, long start, long end) + { + if(start < 0) + throw new ArgumentOutOfRangeException(nameof(start), Localization.Start_cant_be_a_negative_number); + + if(end < 0) + throw new ArgumentOutOfRangeException(nameof(end), Localization.End_cant_be_a_negative_number); + + _streamStart = start; + _streamEnd = end; + + _baseStream = new FileStream(path, mode, access, share, bufferSize, options); + + if(end > _baseStream.Length) + throw new ArgumentOutOfRangeException(nameof(end), Localization.End_is_after_stream_end); + + _baseStream.Position = start; + } + + /// + /// + /// Initializes a stream that only allows reading from to of the + /// specified file, both inclusive. + /// + /// A file handle for the file that the stream will encapsulate. + /// + /// A bitwise combination of the enumeration values that determines how the file can be accessed by a + /// object. + /// + /// Start position + /// Last readable position + /// Invalid range + public OffsetStream(SafeFileHandle handle, FileAccess access, long start, long end) + { + if(start < 0) + throw new ArgumentOutOfRangeException(nameof(start), Localization.Start_cant_be_a_negative_number); + + if(end < 0) + throw new ArgumentOutOfRangeException(nameof(end), Localization.End_cant_be_a_negative_number); + + _streamStart = start; + _streamEnd = end; + + _baseStream = new FileStream(handle, access); + + if(end > _baseStream.Length) + throw new ArgumentOutOfRangeException(nameof(end), Localization.End_is_after_stream_end); + + _baseStream.Position = start; + } + + /// + /// + /// Initializes a stream that only allows reading from to of the + /// specified file, both inclusive. + /// + /// A file handle for the file that the stream will encapsulate. + /// + /// A bitwise combination of the enumeration values that determines how the file can be accessed by a + /// object. + /// + /// + /// A positive Int32 value greater than 0 indicating the buffer size. The default buffer size is + /// 4096. + /// + /// Start position + /// Last readable position + /// Invalid range + public OffsetStream(SafeFileHandle handle, FileAccess access, int bufferSize, long start, long end) + { + if(start < 0) + throw new ArgumentOutOfRangeException(nameof(start), Localization.Start_cant_be_a_negative_number); + + if(end < 0) + throw new ArgumentOutOfRangeException(nameof(end), Localization.End_cant_be_a_negative_number); + + _streamStart = start; + _streamEnd = end; + + _baseStream = new FileStream(handle, access, bufferSize); + + if(end > _baseStream.Length) + throw new ArgumentOutOfRangeException(nameof(end), Localization.End_is_after_stream_end); + + _baseStream.Position = start; + } + + /// + /// + /// Initializes a stream that only allows reading from to of the + /// specified file, both inclusive. + /// + /// A file handle for the file that the stream will encapsulate. + /// + /// A bitwise combination of the enumeration values that determines how the file can be accessed by a + /// object. + /// + /// + /// A positive Int32 value greater than 0 indicating the buffer size. The default buffer size is + /// 4096. + /// + /// Specifies whether to use asynchronous I/O or synchronous I/O. + /// Start position + /// Last readable position + /// Invalid range + public OffsetStream(SafeFileHandle handle, FileAccess access, int bufferSize, bool isAsync, long start, long end) + { + if(start < 0) + throw new ArgumentOutOfRangeException(nameof(start), Localization.Start_cant_be_a_negative_number); + + if(end < 0) + throw new ArgumentOutOfRangeException(nameof(end), Localization.End_cant_be_a_negative_number); + + _streamStart = start; + _streamEnd = end; + + _baseStream = new FileStream(handle, access, bufferSize, isAsync); + + if(end > _baseStream.Length) + throw new ArgumentOutOfRangeException(nameof(end), Localization.End_is_after_stream_end); + + _baseStream.Position = start; + } + + /// + /// + /// Initializes a stream that only allows reading from to of the + /// specified file, both inclusive. + /// + /// A relative or absolute path for the file that the stream will encapsulate. + /// One of the enumeration values that determines how to open or create the file. + /// + /// A bitwise combination of the enumeration values that determines how the file can be accessed by a + /// object. + /// + /// + /// A bitwise combination of the enumeration values that determines how the file will be shared by + /// processes. + /// + /// + /// A positive Int32 value greater than 0 indicating the buffer size. The default buffer size is + /// 4096. + /// + /// Specifies whether to use asynchronous I/O or synchronous I/O. + /// Start position + /// Last readable position + /// Invalid range + public OffsetStream(string path, FileMode mode, FileAccess access, FileShare share, int bufferSize, bool useAsync, + long start, long end) + { + if(start < 0) + throw new ArgumentOutOfRangeException(nameof(start), Localization.Start_cant_be_a_negative_number); + + if(end < 0) + throw new ArgumentOutOfRangeException(nameof(end), Localization.End_cant_be_a_negative_number); + + _streamStart = start; + _streamEnd = end; + + _baseStream = new FileStream(path, mode, access, share, bufferSize, useAsync); + + if(end > _baseStream.Length) + throw new ArgumentOutOfRangeException(nameof(end), Localization.End_is_after_stream_end); + + _baseStream.Position = start; + } + + /// + /// + /// Initializes a stream that only allows reading from to of the + /// specified file, both inclusive. + /// + /// A relative or absolute path for the file that the stream will encapsulate. + /// One of the enumeration values that determines how to open or create the file. + /// + /// A bitwise combination of the enumeration values that determines how the file can be accessed by a + /// object. + /// + /// + /// A bitwise combination of the enumeration values that determines how the file will be shared by + /// processes. + /// + /// + /// A positive Int32 value greater than 0 indicating the buffer size. The default buffer size is + /// 4096. + /// + /// Start position + /// Last readable position + /// Invalid range + public OffsetStream(string path, FileMode mode, FileAccess access, FileShare share, int bufferSize, long start, + long end) + { + if(start < 0) + throw new ArgumentOutOfRangeException(nameof(start), Localization.Start_cant_be_a_negative_number); + + if(end < 0) + throw new ArgumentOutOfRangeException(nameof(end), Localization.End_cant_be_a_negative_number); + + _streamStart = start; + _streamEnd = end; + + _baseStream = new FileStream(path, mode, access, share, bufferSize); + + if(end > _baseStream.Length) + throw new ArgumentOutOfRangeException(nameof(end), Localization.End_is_after_stream_end); + + _baseStream.Position = start; + } + + /// + /// + /// Initializes a stream that only allows reading from to of the + /// specified file, both inclusive. + /// + /// A relative or absolute path for the file that the stream will encapsulate. + /// One of the enumeration values that determines how to open or create the file. + /// + /// A bitwise combination of the enumeration values that determines how the file can be accessed by a + /// object. + /// + /// + /// A bitwise combination of the enumeration values that determines how the file will be shared by + /// processes. + /// + /// Start position + /// Last readable position + /// Invalid range + public OffsetStream(string path, FileMode mode, FileAccess access, FileShare share, long start, long end) + { + if(start < 0) + throw new ArgumentOutOfRangeException(nameof(start), Localization.Start_cant_be_a_negative_number); + + if(end < 0) + throw new ArgumentOutOfRangeException(nameof(end), Localization.End_cant_be_a_negative_number); + + _streamStart = start; + _streamEnd = end; + + _baseStream = new FileStream(path, mode, access, share); + + if(end > _baseStream.Length) + throw new ArgumentOutOfRangeException(nameof(end), Localization.End_is_after_stream_end); + + _baseStream.Position = start; + } + + /// + /// + /// Initializes a stream that only allows reading from to of the + /// specified file, both inclusive. + /// + /// A relative or absolute path for the file that the stream will encapsulate. + /// One of the enumeration values that determines how to open or create the file. + /// + /// A bitwise combination of the enumeration values that determines how the file can be accessed by a + /// object. + /// + /// Start position + /// Last readable position + /// Invalid range + public OffsetStream(string path, FileMode mode, FileAccess access, long start, long end) + { + if(start < 0) + throw new ArgumentOutOfRangeException(nameof(start), Localization.Start_cant_be_a_negative_number); + + if(end < 0) + throw new ArgumentOutOfRangeException(nameof(end), Localization.End_cant_be_a_negative_number); + + _streamStart = start; + _streamEnd = end; + + _baseStream = new FileStream(path, mode, access); + + if(end > _baseStream.Length) + throw new ArgumentOutOfRangeException(nameof(end), Localization.End_is_after_stream_end); + + _baseStream.Position = start; + } + + /// + /// + /// Initializes a stream that only allows reading from to of the + /// specified file, both inclusive. + /// + /// A relative or absolute path for the file that the stream will encapsulate. + /// One of the enumeration values that determines how to open or create the file. + /// Start position + /// Last readable position + /// Invalid range + public OffsetStream(string path, FileMode mode, long start, long end) + { + if(start < 0) + throw new ArgumentOutOfRangeException(nameof(start), Localization.Start_cant_be_a_negative_number); + + if(end < 0) + throw new ArgumentOutOfRangeException(nameof(end), Localization.End_cant_be_a_negative_number); + + _streamStart = start; + _streamEnd = end; + + _baseStream = new FileStream(path, mode); + + if(end > _baseStream.Length) + throw new ArgumentOutOfRangeException(nameof(end), Localization.End_is_after_stream_end); + + _baseStream.Position = start; + } + + /// + /// + /// Initializes a stream that only allows reading from to of the + /// specified byte array, both inclusive. + /// + /// The array of unsigned bytes to add at the end of this stream. + /// The index into at which the stream begins. + /// The length in bytes to add to the end of the current stream. + /// The setting of the CanWrite property, currently ignored. + /// Currently ignored. + /// Start position + /// Last readable position + /// Invalid range + public OffsetStream(byte[] buffer, int index, int count, bool writable, bool publiclyVisible, long start, long end) + { + if(start < 0) + throw new ArgumentOutOfRangeException(nameof(start), Localization.Start_cant_be_a_negative_number); + + if(end < 0) + throw new ArgumentOutOfRangeException(nameof(end), Localization.End_cant_be_a_negative_number); + + _streamStart = start; + _streamEnd = end; + + _baseStream = new MemoryStream(buffer, index, count, writable, publiclyVisible); + + if(end > _baseStream.Length) + throw new ArgumentOutOfRangeException(nameof(end), Localization.End_is_after_stream_end); + + _baseStream.Position = start; + } + + /// + /// + /// Initializes a stream that only allows reading from to of the + /// specified byte array, both inclusive. + /// + /// The array of unsigned bytes to add at the end of this stream. + /// The index into at which the stream begins. + /// The length in bytes to add to the end of the current stream. + /// The setting of the CanWrite property, currently ignored. + /// Start position + /// Last readable position + /// Invalid range + public OffsetStream(byte[] buffer, int index, int count, bool writable, long start, long end) + { + if(start < 0) + throw new ArgumentOutOfRangeException(nameof(start), Localization.Start_cant_be_a_negative_number); + + if(end < 0) + throw new ArgumentOutOfRangeException(nameof(end), Localization.End_cant_be_a_negative_number); + + _streamStart = start; + _streamEnd = end; + + _baseStream = new MemoryStream(buffer, index, count, writable); + + if(end > _baseStream.Length) + throw new ArgumentOutOfRangeException(nameof(end), Localization.End_is_after_stream_end); + + _baseStream.Position = start; + } + + /// + /// + /// Initializes a stream that only allows reading from to of the + /// specified byte array, both inclusive. + /// + /// The array of unsigned bytes to add at the end of this stream. + /// The index into at which the stream begins. + /// The length in bytes to add to the end of the current stream. + /// Start position + /// Last readable position + /// Invalid range + public OffsetStream(byte[] buffer, int index, int count, long start, long end) + { + if(start < 0) + throw new ArgumentOutOfRangeException(nameof(start), Localization.Start_cant_be_a_negative_number); + + if(end < 0) + throw new ArgumentOutOfRangeException(nameof(end), Localization.End_cant_be_a_negative_number); + + _streamStart = start; + _streamEnd = end; + + _baseStream = new MemoryStream(buffer, index, count); + + if(end > _baseStream.Length) + throw new ArgumentOutOfRangeException(nameof(end), Localization.End_is_after_stream_end); + + _baseStream.Position = start; + } + + /// + /// + /// Initializes a stream that only allows reading from to of the + /// specified byte array, both inclusive. + /// + /// The array of unsigned bytes to add at the end of this stream. + /// The setting of the CanWrite property, currently ignored. + /// Start position + /// Last readable position + /// Invalid range + public OffsetStream(byte[] buffer, bool writable, long start, long end) + { + if(start < 0) + throw new ArgumentOutOfRangeException(nameof(start), Localization.Start_cant_be_a_negative_number); + + if(end < 0) + throw new ArgumentOutOfRangeException(nameof(end), Localization.End_cant_be_a_negative_number); + + _streamStart = start; + _streamEnd = end; + + _baseStream = new MemoryStream(buffer, writable); + + if(end > _baseStream.Length) + throw new ArgumentOutOfRangeException(nameof(end), Localization.End_is_after_stream_end); + + _baseStream.Position = start; + } + + /// + /// + /// Initializes a stream that only allows reading from to of the + /// specified byte array, both inclusive. + /// + /// The array of unsigned bytes to add at the end of this stream. + /// Start position + /// Last readable position + /// Invalid range + public OffsetStream(byte[] buffer, long start, long end) + { + if(start < 0) + throw new ArgumentOutOfRangeException(nameof(start), Localization.Start_cant_be_a_negative_number); + + if(end < 0) + throw new ArgumentOutOfRangeException(nameof(end), Localization.End_cant_be_a_negative_number); + + _streamStart = start; + _streamEnd = end; + + _baseStream = new MemoryStream(buffer); + + if(end > _baseStream.Length) + throw new ArgumentOutOfRangeException(nameof(end), Localization.End_is_after_stream_end); + + _baseStream.Position = start; + } + + /// + public override bool CanRead => _baseStream.CanRead; + + /// + public override bool CanSeek => _baseStream.CanSeek; + + /// + public override bool CanWrite => _baseStream.CanWrite; + + /// + public override long Length => _streamEnd - _streamStart + 1; + + /// + public override long Position + { + get => _baseStream.Position - _streamStart; + + set + { + if(value + _streamStart > _streamEnd) + throw new IOException(Localization.Cannot_set_position_past_stream_end); + + _baseStream.Position = value + _streamStart; + } + } + + ~OffsetStream() + { + _baseStream.Close(); + _baseStream.Dispose(); + } + + /// + public override IAsyncResult BeginRead(byte[] buffer, int offset, int count, AsyncCallback callback, object state) + { + if(_baseStream.Position + count > _streamEnd) + throw new IOException(Localization.Cannot_read_past_stream_end); + + return _baseStream.BeginRead(buffer, offset, count, callback, state); + } + + /// + public override IAsyncResult BeginWrite(byte[] buffer, int offset, int count, AsyncCallback callback, object state) + { + if(_baseStream.Position + count > _streamEnd) + throw new IOException(Localization.Cannot_write_past_stream_end); + + return _baseStream.BeginWrite(buffer, offset, count, callback, state); + } + + /// + public override void Close() => _baseStream.Close(); + + /// + public override int EndRead(IAsyncResult asyncResult) => _baseStream.EndRead(asyncResult); + + /// + public override void EndWrite(IAsyncResult asyncResult) => _baseStream.EndWrite(asyncResult); + + /// + public override int ReadByte() => _baseStream.Position == _streamEnd + 1 ? -1 : _baseStream.ReadByte(); + + /// + public override void WriteByte(byte value) + { + if(_baseStream.Position + 1 > _streamEnd) + throw new IOException(Localization.Cannot_write_past_stream_end); + + _baseStream.WriteByte(value); + } + + /// + public override void Flush() => _baseStream.Flush(); + + /// + public override int Read(byte[] buffer, int offset, int count) + { + if(_baseStream.Position + count > _streamEnd + 1) + throw new IOException(Localization.Cannot_read_past_stream_end); + + return _baseStream.EnsureRead(buffer, offset, count); + } + + /// + public override long Seek(long offset, SeekOrigin origin) + { + switch(origin) + { + case SeekOrigin.Begin: + if(offset + _streamStart > _streamEnd) + throw new IOException(Localization.Cannot_seek_after_stream_end); + + return _baseStream.Seek(offset + _streamStart, SeekOrigin.Begin) - _streamStart; + case SeekOrigin.End: + if(offset - (_baseStream.Length - _streamEnd) < _streamStart) + throw new IOException(Localization.Cannot_seek_before_stream_start); + + return _baseStream.Seek(offset - (_baseStream.Length - _streamEnd), SeekOrigin.End) - _streamStart; + default: + if(offset + _baseStream.Position > _streamEnd) + throw new IOException(Localization.Cannot_seek_after_stream_end); + + return _baseStream.Seek(offset, SeekOrigin.Current) - _streamStart; + } + } + + /// + public override void SetLength(long value) => + throw new NotSupportedException(Localization.Growing_OffsetStream_is_not_supported); + + /// + public override void Write(byte[] buffer, int offset, int count) + { + if(_baseStream.Position + count > _streamEnd) + throw new IOException(Localization.Cannot_write_past_stream_end); + + _baseStream.Write(buffer, offset, count); + } +} \ No newline at end of file diff --git a/IO/SplitJoinStream.cs b/IO/SplitJoinStream.cs new file mode 100644 index 0000000..34981ec --- /dev/null +++ b/IO/SplitJoinStream.cs @@ -0,0 +1,372 @@ +using System; +using System.Collections.Generic; +using System.Data; +using System.Diagnostics.CodeAnalysis; +using System.IO; +using System.Linq; +using Microsoft.Win32.SafeHandles; + +namespace Aaru.Helpers.IO; + +/// +/// Implements a stream that joins two or more files (sequentially) as a single stream +[SuppressMessage("ReSharper", "UnusedMember.Global")] +public class SplitJoinStream : Stream +{ + readonly Dictionary _baseStreams; + long _position; + long _streamLength; + + /// + public SplitJoinStream() + { + _baseStreams = new Dictionary(); + _streamLength = 0; + _position = 0; + } + + /// + public override bool CanRead => true; + + /// + public override bool CanSeek => true; + + /// + public override bool CanWrite => false; + + /// + public override long Length => _streamLength; + + /// + public override long Position + { + get => _position; + + set + { + if(value >= _streamLength) + throw new IOException(Localization.Cannot_set_position_past_stream_end); + + _position = value; + } + } + + /// Adds a stream at the end of the current stream + /// Stream to add + /// The specified stream is non-readable or non-seekable + public void Add(Stream stream) + { + if(!stream.CanSeek) + throw new ArgumentException(Localization.Non_seekable_streams_are_not_supported); + + if(!stream.CanRead) + throw new ArgumentException(Localization.Non_readable_streams_are_not_supported); + + _baseStreams[_streamLength] = stream; + _streamLength += stream.Length; + } + + /// Adds the specified file to the end of the current stream + /// A relative or absolute path for the file that the stream will encapsulate. + /// One of the enumeration values that determines how to open or create the file. + /// + /// A bitwise combination of the enumeration values that determines how the file can be accessed by a + /// object. + /// + /// + /// A bitwise combination of the enumeration values that determines how the file will be shared by + /// processes. + /// + /// + /// A positive Int32 value greater than 0 indicating the buffer size. The default buffer size is + /// 4096. + /// + /// A bitwise combination of the enumeration values that specifies additional file options. + public void Add(string path, FileMode mode, FileAccess access, FileShare share, int bufferSize, + FileOptions options) => Add(new FileStream(path, mode, access, share, bufferSize, options)); + + /// Adds the specified file to the end of the current stream + /// A file handle for the file that the stream will encapsulate. + /// + /// A bitwise combination of the enumeration values that determines how the file can be accessed by a + /// object. + /// + public void Add(SafeFileHandle handle, FileAccess access) => Add(new FileStream(handle, access)); + + /// Adds the specified file to the end of the current stream + /// A file handle for the file that the stream will encapsulate. + /// + /// A bitwise combination of the enumeration values that determines how the file can be accessed by a + /// object. + /// + /// + /// A positive Int32 value greater than 0 indicating the buffer size. The default buffer size is + /// 4096. + /// + public void Add(SafeFileHandle handle, FileAccess access, int bufferSize) => + Add(new FileStream(handle, access, bufferSize)); + + /// Adds the specified file to the end of the current stream + /// A file handle for the file that the stream will encapsulate. + /// + /// A bitwise combination of the enumeration values that determines how the file can be accessed by a + /// object. + /// + /// + /// A positive Int32 value greater than 0 indicating the buffer size. The default buffer size is + /// 4096. + /// + /// Specifies whether to use asynchronous I/O or synchronous I/O. + public void Add(SafeFileHandle handle, FileAccess access, int bufferSize, bool isAsync) => + Add(new FileStream(handle, access, bufferSize, isAsync)); + + /// Adds the specified file to the end of the current stream + /// A relative or absolute path for the file that the stream will encapsulate. + /// One of the enumeration values that determines how to open or create the file. + /// + /// A bitwise combination of the enumeration values that determines how the file can be accessed by a + /// object. + /// + /// + /// A bitwise combination of the enumeration values that determines how the file will be shared by + /// processes. + /// + /// + /// A positive Int32 value greater than 0 indicating the buffer size. The default buffer size is + /// 4096. + /// + /// Specifies whether to use asynchronous I/O or synchronous I/O. + public void Add(string path, FileMode mode, FileAccess access, FileShare share, int bufferSize, bool useAsync) => + Add(new FileStream(path, mode, access, share, bufferSize, useAsync)); + + /// Adds the specified file to the end of the current stream + /// A relative or absolute path for the file that the stream will encapsulate. + /// One of the enumeration values that determines how to open or create the file. + /// + /// A bitwise combination of the enumeration values that determines how the file can be accessed by a + /// object. + /// + /// + /// A bitwise combination of the enumeration values that determines how the file will be shared by + /// processes. + /// + /// + /// A positive Int32 value greater than 0 indicating the buffer size. The default buffer size is + /// 4096. + /// + public void Add(string path, FileMode mode, FileAccess access, FileShare share, int bufferSize) => + Add(new FileStream(path, mode, access, share, bufferSize)); + + /// Adds the specified file to the end of the current stream + /// A relative or absolute path for the file that the stream will encapsulate. + /// One of the enumeration values that determines how to open or create the file. + /// + /// A bitwise combination of the enumeration values that determines how the file can be accessed by a + /// object. + /// + /// + /// A bitwise combination of the enumeration values that determines how the file will be shared by + /// processes. + /// + public void Add(string path, FileMode mode, FileAccess access, FileShare share) => + Add(new FileStream(path, mode, access, share)); + + /// Adds the specified file to the end of the current stream + /// A relative or absolute path for the file that the stream will encapsulate. + /// One of the enumeration values that determines how to open or create the file. + /// + /// A bitwise combination of the enumeration values that determines how the file can be accessed by a + /// object. + /// + public void Add(string path, FileMode mode, FileAccess access) => Add(new FileStream(path, mode, access)); + + /// Adds the specified file to the end of the current stream + /// A relative or absolute path for the file that the stream will encapsulate. + /// One of the enumeration values that determines how to open or create the file. + public void Add(string path, FileMode mode) => Add(new FileStream(path, mode)); + + /// Adds the specified byte array to the end of the current stream + /// The array of unsigned bytes to add at the end of this stream. + /// The index into at which the stream begins. + /// The length in bytes to add to the end of the current stream. + /// The setting of the CanWrite property, currently ignored. + /// Currently ignored. + public void Add(byte[] buffer, int index, int count, bool writable, bool publiclyVisible) => + Add(new MemoryStream(buffer, index, count, writable, publiclyVisible)); + + /// Adds the specified byte array to the end of the current stream + /// The array of unsigned bytes to add at the end of this stream. + /// The index into at which the stream begins. + /// The length in bytes to add to the end of the current stream. + /// The setting of the CanWrite property, currently ignored. + public void Add(byte[] buffer, int index, int count, bool writable) => + Add(new MemoryStream(buffer, index, count, writable)); + + /// Adds the specified byte array to the end of the current stream + /// The array of unsigned bytes to add at the end of this stream. + /// The index into at which the stream begins. + /// The length in bytes to add to the end of the current stream. + public void Add(byte[] buffer, int index, int count) => Add(new MemoryStream(buffer, index, count)); + + /// Adds the specified byte array to the end of the current stream + /// The array of unsigned bytes to add at the end of this stream. + /// The setting of the CanWrite property, currently ignored. + public void Add(byte[] buffer, bool writable) => Add(new MemoryStream(buffer, writable)); + + /// Adds the specified byte array to the end of the current stream + /// The array of unsigned bytes to add at the end of this stream. + public void Add(byte[] buffer) => Add(new MemoryStream(buffer)); + + /// Adds a range of files to the end of the current stream, alphabetically sorted + /// Base file path, directory path only + /// Counter format, includes filename and a formatting string + /// Counter start, defaults to 0 + /// + /// A bitwise combination of the enumeration values that determines how the file can be accessed by a + /// object. + /// + public void AddRange(string basePath, string counterFormat = "{0:D3}", int counterStart = 0, + FileAccess access = FileAccess.Read) + { + while(true) + { + string filePath = Path.Combine(basePath, string.Format(counterFormat, counterStart)); + + if(!File.Exists(filePath)) + break; + + Add(filePath, FileMode.Open, access); + + counterStart++; + } + } + + ~SplitJoinStream() + { + foreach(Stream stream in _baseStreams.Values) + { + stream.Close(); + stream.Dispose(); + } + + _baseStreams.Clear(); + _position = 0; + } + + /// + public override IAsyncResult + BeginRead(byte[] buffer, int offset, int count, AsyncCallback callback, object state) => + throw new NotSupportedException(Localization.Asynchronous_IO_is_not_supported); + + /// + public override IAsyncResult + BeginWrite(byte[] buffer, int offset, int count, AsyncCallback callback, object state) => + throw new NotSupportedException(Localization.Asynchronous_IO_is_not_supported); + + /// + public override void Close() + { + foreach(Stream stream in _baseStreams.Values) + stream.Close(); + + _baseStreams.Clear(); + _position = 0; + } + + /// + public override int EndRead(IAsyncResult asyncResult) => + throw new NotSupportedException(Localization.Asynchronous_IO_is_not_supported); + + /// + public override void EndWrite(IAsyncResult asyncResult) => + throw new NotSupportedException(Localization.Asynchronous_IO_is_not_supported); + + /// + public override int ReadByte() + { + if(_position >= _streamLength) + return -1; + + KeyValuePair baseStream = _baseStreams.FirstOrDefault(s => s.Key >= _position); + + if(baseStream.Value == null) + return -1; + + baseStream.Value.Position = _position - baseStream.Key; + _position++; + + return baseStream.Value.ReadByte(); + } + + /// + public override void WriteByte(byte value) => throw new ReadOnlyException(Localization.This_stream_is_read_only); + + /// + public override void Flush() {} + + /// + public override int Read(byte[] buffer, int offset, int count) + { + var read = 0; + + while(count > 0) + { + KeyValuePair baseStream = _baseStreams.LastOrDefault(s => s.Key <= _position); + + if(baseStream.Value == null) + break; + + baseStream.Value.Position = _position - baseStream.Key; + + int currentCount = count; + + if(baseStream.Value.Position + currentCount > baseStream.Value.Length) + currentCount = (int)(baseStream.Value.Length - baseStream.Value.Position); + + read += baseStream.Value.Read(buffer, offset, currentCount); + + count -= currentCount; + offset += currentCount; + } + + return read; + } + + /// + public override long Seek(long offset, SeekOrigin origin) + { + switch(origin) + { + case SeekOrigin.Begin: + if(offset >= _streamLength) + throw new IOException(Localization.Cannot_seek_after_stream_end); + + _position = offset; + + break; + case SeekOrigin.End: + if(_position - offset < 0) + throw new IOException(Localization.Cannot_seek_before_stream_start); + + _position -= offset; + + break; + default: + if(_position + offset >= _streamLength) + throw new IOException(Localization.Cannot_seek_after_stream_end); + + _position += offset; + + break; + } + + return _position; + } + + /// + public override void SetLength(long value) => throw new ReadOnlyException(Localization.This_stream_is_read_only); + + /// + public override void Write(byte[] buffer, int offset, int count) => + throw new ReadOnlyException(Localization.This_stream_is_read_only); +} \ No newline at end of file diff --git a/Localization/Localization.Designer.cs b/Localization/Localization.Designer.cs index a07e3a8..36a0e61 100644 --- a/Localization/Localization.Designer.cs +++ b/Localization/Localization.Designer.cs @@ -59,6 +59,87 @@ namespace Aaru.Helpers { } } + /// + /// Looks up a localized string similar to Asynchronous I/O is not supported.. + /// + internal static string Asynchronous_IO_is_not_supported { + get { + return ResourceManager.GetString("Asynchronous_IO_is_not_supported", resourceCulture); + } + } + + /// + /// Looks up a localized string similar to Cannot read past stream end.. + /// + internal static string Cannot_read_past_stream_end { + get { + return ResourceManager.GetString("Cannot_read_past_stream_end", resourceCulture); + } + } + + /// + /// Looks up a localized string similar to Cannot seek after stream end.. + /// + internal static string Cannot_seek_after_stream_end { + get { + return ResourceManager.GetString("Cannot_seek_after_stream_end", resourceCulture); + } + } + + /// + /// Looks up a localized string similar to Cannot seek before stream start.. + /// + internal static string Cannot_seek_before_stream_start { + get { + return ResourceManager.GetString("Cannot_seek_before_stream_start", resourceCulture); + } + } + + /// + /// Looks up a localized string similar to Cannot set position past stream end.. + /// + internal static string Cannot_set_position_past_stream_end { + get { + return ResourceManager.GetString("Cannot_set_position_past_stream_end", resourceCulture); + } + } + + /// + /// Looks up a localized string similar to Cannot write past stream end.. + /// + internal static string Cannot_write_past_stream_end { + get { + return ResourceManager.GetString("Cannot_write_past_stream_end", resourceCulture); + } + } + + /// + /// Looks up a localized string similar to End can't be a negative number.. + /// + internal static string End_cant_be_a_negative_number { + get { + return ResourceManager.GetString("End_cant_be_a_negative_number", resourceCulture); + } + } + + /// + /// Looks up a localized string similar to End is after stream end.. + /// + internal static string End_is_after_stream_end { + get { + return ResourceManager.GetString("End_is_after_stream_end", resourceCulture); + } + } + + /// + /// Looks up a localized string similar to Growing OffsetStream is not supported.. + /// + internal static string Growing_OffsetStream_is_not_supported { + get { + return ResourceManager.GetString("Growing_OffsetStream_is_not_supported", resourceCulture); + } + } + /// /// Looks up a localized string similar to Length of value array must not be more than length of destination. /// @@ -68,6 +149,24 @@ namespace Aaru.Helpers { } } + /// + /// Looks up a localized string similar to Non-readable streams are not supported. + /// + internal static string Non_readable_streams_are_not_supported { + get { + return ResourceManager.GetString("Non_readable_streams_are_not_supported", resourceCulture); + } + } + + /// + /// Looks up a localized string similar to Non-seekable streams are not supported. + /// + internal static string Non_seekable_streams_are_not_supported { + get { + return ResourceManager.GetString("Non_seekable_streams_are_not_supported", resourceCulture); + } + } + /// /// Looks up a localized string similar to Offset. /// @@ -76,5 +175,23 @@ namespace Aaru.Helpers { return ResourceManager.GetString("Offset", resourceCulture); } } + + /// + /// Looks up a localized string similar to Start can't be a negative number.. + /// + internal static string Start_cant_be_a_negative_number { + get { + return ResourceManager.GetString("Start_cant_be_a_negative_number", resourceCulture); + } + } + + /// + /// Looks up a localized string similar to This stream is read-only. + /// + internal static string This_stream_is_read_only { + get { + return ResourceManager.GetString("This_stream_is_read_only", resourceCulture); + } + } } } diff --git a/Localization/Localization.es.resx b/Localization/Localization.es.resx index 15b6f7e..9ed14ff 100644 --- a/Localization/Localization.es.resx +++ b/Localization/Localization.es.resx @@ -22,4 +22,43 @@ La longitud de una colección no puede ser mayor que la longitud del destino + + Las secuencias no legíbles no están soportadas. + + + Las secuencias no posicionables no están soportadas. + + + No se puede leer más allá del final de la secuencia. + + + No se puede posicionar después del final de la secuencia. + + + No se puede posicionar antes del comienzo de la secuencia. + + + No se puede establecer la posición más allá del final de la secuencia. + + + No se puede escribir después del final de la secuencia. + + + El final no puede ser un número negativo. + + + El final está después del final de la secuencia. + + + No se puede agrandar un OffsetStream. + + + E/S asíncrona no soportada. + + + El comienzo no puede ser un número negativo. + + + Esta secuencia es de sólo lectura. + \ No newline at end of file diff --git a/Localization/Localization.resx b/Localization/Localization.resx index 7ec00ab..6f9a16a 100644 --- a/Localization/Localization.resx +++ b/Localization/Localization.resx @@ -29,4 +29,43 @@ Offset + + Start can't be a negative number. + + + End can't be a negative number. + + + End is after stream end. + + + Cannot set position past stream end. + + + Cannot read past stream end. + + + Cannot write past stream end. + + + Growing OffsetStream is not supported. + + + Cannot seek before stream start. + + + Cannot seek after stream end. + + + Non-seekable streams are not supported + + + Non-readable streams are not supported + + + Asynchronous I/O is not supported. + + + This stream is read-only + \ No newline at end of file