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 { /// /// Implements a stream that joins two or more files (sequentially) as a single stream 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; } } /// Gets a filter from this stream public IFilter Filter { get; } /// 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("Non-seekable streams are not supported"); if(!stream.CanRead) throw new ArgumentException("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 the data fork of the specified filter to the end of the current stream /// Filter public void Add(IFilter filter) => Add(filter.GetDataForkStream()); /// 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 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; } /// 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"); } }