using System;
using System.Collections.Generic;
using System.Text;
using System.IO;
namespace DSDecmp.Formats
{
///
/// A format that is composed of multiple formats.
/// When compressing, the input is compressed using the best contained format.
/// When decompressing, all contained formats will try to decompress the file, until one succeeds.
///
public abstract class CompositeFormat : CompressionFormat
{
///
/// The actual list of formats this format is somposed of.
///
private List formats;
#region Constructors
///
/// Creates a new composite format based on the given sequence of formats.
///
protected CompositeFormat(IEnumerable formats)
{
this.formats = new List(formats);
}
///
/// Creates a new composite format based on the given formats.
///
protected CompositeFormat(params CompressionFormat[] formats)
{
this.formats = new List(formats);
}
#endregion
#region Method: Supports
///
/// Checks if any of the contained formats supports the given input.
///
public override bool Supports(System.IO.Stream stream, long inLength)
{
foreach (CompositeFormat fmt in this.formats)
{
if (fmt.Supports(stream, inLength))
return true;
}
return false;
}
#endregion
#region Method: Decompress
///
/// Attempts to decompress the given input by letting all contained formats
/// try to decompress the input.
///
public override long Decompress(System.IO.Stream instream, long inLength, System.IO.Stream outstream)
{
byte[] inputData = new byte[instream.Length];
instream.Read(inputData, 0, inputData.Length);
foreach (CompressionFormat format in this.formats)
{
if (!format.SupportsDecompression)
continue;
using (MemoryStream input = new MemoryStream(inputData))
{
if (!format.Supports(input, inputData.Length))
continue;
MemoryStream output = new MemoryStream();
try
{
long decLength = format.Decompress(input, inputData.Length, output);
if (decLength > 0)
{
output.WriteTo(outstream);
return decLength;
}
}
catch (Exception) { continue; }
}
}
throw new InvalidDataException("Input cannot be decompressed using the " + this.ShortFormatString + " formats.");
}
#endregion
#region Method: Compress & Field: LastUsedCompressFormatString
///
/// Gets the ShortFormatString of the last CompressionFormat that was used to compress input.
///
public string LastUsedCompressFormatString { get; private set; }
///
/// Compresses the given input using the contained format that yields the best results in terms of
/// size reduction.
///
public override int Compress(System.IO.Stream instream, long inLength, System.IO.Stream outstream)
{
// only read the input data once from the file.
byte[] inputData = new byte[instream.Length];
instream.Read(inputData, 0, inputData.Length);
MemoryStream bestOutput = null;
string bestFormatString = "";
int minCompSize = int.MaxValue;
foreach (CompressionFormat format in formats)
{
if (!format.SupportsCompression)
continue;
#region compress the file in each format, and save the best one
MemoryStream currentOutput = new MemoryStream();
int currentOutSize;
try
{
using (MemoryStream input = new MemoryStream(inputData))
{
currentOutSize = format.Compress(input, input.Length, currentOutput);
}
}
catch (InputTooLargeException i)
{
Console.WriteLine(i.Message);
bestFormatString = format.ShortFormatString;
return -1;
}
catch (Exception)
{
continue;
}
if (currentOutSize < minCompSize)
{
bestOutput = currentOutput;
minCompSize = currentOutSize;
bestFormatString = format.ShortFormatString;
}
#endregion
}
if (bestOutput == null)
return -1;
bestOutput.WriteTo(outstream);
this.LastUsedCompressFormatString = bestFormatString;
return minCompSize;
}
#endregion
#region Method: ParseCompressionOptions(args)
///
/// Handles the compression options for each of the contained compression formats.
///
public override int ParseCompressionOptions(string[] args)
{
// try each option on each of the formats.
// each pass over the formats lets them try to consume the options.
// if one or more formats consume at least one option, the maximum number
// of consumed options is treated as 'handled'; they are ignored in the
// next pass. This continues until none of the formats consume the next
// value in the options.
int totalOptionCount = 0;
bool usedOption = true;
while (usedOption)
{
usedOption = false;
if (args.Length <= totalOptionCount)
break;
int maxOptionCount = 0;
string[] subArray = new string[args.Length - totalOptionCount];
Array.Copy(args, totalOptionCount, subArray, 0, subArray.Length);
foreach (CompressionFormat format in this.formats)
{
int optCount = format.ParseCompressionOptions(subArray);
maxOptionCount = Math.Max(optCount, maxOptionCount);
}
if (maxOptionCount > 0)
{
totalOptionCount += maxOptionCount;
usedOption = true;
}
}
return totalOptionCount;
}
#endregion
}
}