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");
}
}