diff --git a/Aaru.Filters/Aaru.Filters.csproj b/Aaru.Filters/Aaru.Filters.csproj index 76acf51bf..ea6bfb821 100644 --- a/Aaru.Filters/Aaru.Filters.csproj +++ b/Aaru.Filters/Aaru.Filters.csproj @@ -58,6 +58,7 @@ + @@ -87,7 +88,7 @@ - + diff --git a/Aaru.Filters/SplitJoinStream.cs b/Aaru.Filters/SplitJoinStream.cs new file mode 100644 index 000000000..2ec783d54 --- /dev/null +++ b/Aaru.Filters/SplitJoinStream.cs @@ -0,0 +1,242 @@ +using System; +using System.Collections.Generic; +using System.Data; +using System.IO; +using System.Linq; +using Aaru.CommonTypes.Interfaces; +using Microsoft.Win32.SafeHandles; + +namespace Aaru.Filters +{ + public class SplitJoinStream : Stream + { + readonly Dictionary _baseStreams; + long _position; + long _streamLength; + + public SplitJoinStream() + { + _baseStreams = new Dictionary(); + _streamLength = 0; + _position = 0; + Filter = new ZZZNoFilter(); + Filter.Open(this); + } + + 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("Cannot set position past stream end."); + + _position = value; + } + } + + public IFilter Filter { get; } + + public void Add(Stream stream) + { + if(!stream.CanSeek) + throw new ArgumentException("Non-seekable streams are not supported"); + + if(!stream.CanRead) + throw new ArgumentException("Non-readable streams are not supported"); + + _baseStreams[_streamLength] = stream; + _streamLength += stream.Length; + } + + public void Add(string path, FileMode mode, FileAccess access, FileShare share, int bufferSize, + FileOptions options) => Add(new FileStream(path, mode, access, share, bufferSize, options)); + + public void Add(SafeFileHandle handle, FileAccess access) => Add(new FileStream(handle, access)); + + public void Add(SafeFileHandle handle, FileAccess access, int bufferSize) => + Add(new FileStream(handle, access, bufferSize)); + + public void Add(SafeFileHandle handle, FileAccess access, int bufferSize, bool isAsync) => + Add(new FileStream(handle, access, bufferSize, isAsync)); + + public void Add(string path, FileMode mode, FileAccess access, FileShare share, int bufferSize, + bool useAsync) => Add(new FileStream(path, mode, access, share, bufferSize, useAsync)); + + public void Add(string path, FileMode mode, FileAccess access, FileShare share, int bufferSize) => + Add(new FileStream(path, mode, access, share, bufferSize)); + + public void Add(string path, FileMode mode, FileAccess access, FileShare share) => + Add(new FileStream(path, mode, access, share)); + + public void Add(string path, FileMode mode, FileAccess access) => Add(new FileStream(path, mode, access)); + + public void Add(string path, FileMode mode) => Add(new FileStream(path, mode)); + + public void Add(byte[] buffer, int index, int count, bool writable, bool publiclyVisible) => + Add(new MemoryStream(buffer, index, count, writable, publiclyVisible)); + + public void Add(byte[] buffer, int index, int count, bool writable, long start, long end) => + Add(new MemoryStream(buffer, index, count, writable)); + + public void Add(byte[] buffer, int index, int count) => Add(new MemoryStream(buffer, index, count)); + + public void Add(byte[] buffer, bool writable) => Add(new MemoryStream(buffer, writable)); + + public void Add(byte[] buffer) => Add(new MemoryStream(buffer)); + + public void Add(IFilter filter) => Add(filter.GetDataForkStream()); + + public void AddRange(string basePath, string counterFormat = "{0:D3}", int counterStart = 0) + { + while(true) + { + string filePath = Path.Combine(basePath, string.Format(counterFormat, counterStart)); + + if(!File.Exists(filePath)) + break; + + Add(filePath, FileMode.Open); + + 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("Asynchronous I/O is not supported."); + + public override IAsyncResult BeginWrite(byte[] buffer, int offset, int count, AsyncCallback callback, + object state) => + throw new NotSupportedException("Asynchronous I/O is not supported."); + + public override void Close() + { + foreach(Stream stream in _baseStreams.Values) + stream.Close(); + + _baseStreams.Clear(); + _position = 0; + } + + new void Dispose() + { + foreach(Stream stream in _baseStreams.Values) + stream.Dispose(); + + _baseStreams.Clear(); + _position = 0; + + base.Dispose(); + } + + public override int EndRead(IAsyncResult asyncResult) => + throw new NotSupportedException("Asynchronous I/O is not supported."); + + public override void EndWrite(IAsyncResult asyncResult) => + throw new NotSupportedException("Asynchronous I/O 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("This stream is read-only"); + + public override void Flush() {} + + public override int Read(byte[] buffer, int offset, int count) + { + int 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("Cannot seek past stream end."); + + _position = offset; + + break; + case SeekOrigin.End: + if(_position - offset < 0) + throw new IOException("Cannot seek before stream start."); + + _position -= offset; + + break; + default: + if(_position + offset >= _streamLength) + throw new IOException("Cannot seek past stream end."); + + _position += offset; + + break; + } + + return _position; + } + + public override void SetLength(long value) => throw new ReadOnlyException("This stream is read-only"); + + public override void Write(byte[] buffer, int offset, int count) => + throw new ReadOnlyException("This stream is read-only"); + } +} \ No newline at end of file