//****************************************************************************************************** // CompressionExtensions.cs - Gbtc // // Copyright © 2012, Grid Protection Alliance. All Rights Reserved. // // Licensed to the Grid Protection Alliance (GPA) under one or more contributor license agreements. See // the NOTICE file distributed with this work for additional information regarding copyright ownership. // The GPA licenses this file to you under the Eclipse Public License -v 1.0 (the "License"); you may // not use this file except in compliance with the License. You may obtain a copy of the License at: // // http://www.opensource.org/licenses/eclipse-1.0.php // // Unless agreed to in writing, the subject software distributed under the License is distributed on an // "AS-IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. Refer to the // License for the specific language governing permissions and limitations. // // Code Modification History: // ---------------------------------------------------------------------------------------------------- // 09/26/2008 - J. Ritchie Carroll // Generated original version of source code. // 08/05/2009 - Josh L. Patterson // Edited Comments. // 09/14/2009 - Stephen C. Wills // Added new header and license agreement. // 12/14/2012 - Starlynn Danyelle Gilliam // Modified Header. // //****************************************************************************************************** using System; using System.IO; using System.IO.Compression; namespace GSF.IO.Compression { #region [ Enumerations ] /// /// Level of compression enumeration. /// /// /// Compression strengths represent tradeoffs on speed of compression vs. effectiveness of compression. /// public enum CompressionStrength { /// Disables compression. NoCompression = 0, /// Enables standard compression. Standard = 1, /// Enables multi-pass compression to continue recompressing buffer as long as size continues to shrink. MultiPass = 2 } #endregion /// /// Defines extension functions related to compression. /// public static class CompressionExtensions { /// /// Default compression buffer size. /// public const int BufferSize = 262144; /// /// Needed version of this library to uncompress stream (1.0.0 stored as byte 100). /// public const byte CompressionVersion = 100; /// /// Compress a byte array using standard compression method. /// /// The array to compress. /// A compressed version of the source array. public static byte[] Compress(this byte[] source) { return source.Compress(CompressionStrength.Standard); } /// /// Compress a byte array using specified compression method. /// /// The array to compress. /// The specified . /// A compressed version of the source array. public static byte[] Compress(this byte[] source, CompressionStrength strength) { return source.Compress(0, source.Length, strength, 0); } /// /// Compress a byte array using specified compression method. /// /// The array to compress. /// The number of bytes to read into the byte array for compression. /// An representing the start index of the byte array. /// The specified . /// A compressed version of the source array. public static byte[] Compress(this byte[] source, int startIndex, int length, CompressionStrength strength) { return source.Compress(startIndex, length, strength, 0); } // When user requests multi-pass compression, we allow multiple compression passes on a buffer because // this can often produce better compression results private static byte[] Compress(this byte[] source, int startIndex, int length, CompressionStrength strength, int compressionDepth) { if (strength == CompressionStrength.NoCompression) { // No compression requested, return specified portion of the buffer byte[] outBuffer = new byte[++length]; outBuffer[0] = (byte)strength; for (int x = 1; x < length; x++) outBuffer[x] = source[startIndex + x - 1]; return outBuffer; } // Create a new compression deflater using (BlockAllocatedMemoryStream compressedData = new BlockAllocatedMemoryStream()) { using (DeflateStream deflater = new DeflateStream(compressedData, CompressionMode.Compress, true)) { // Provide data for compression deflater.Write(source, startIndex, length); } byte[] destination = compressedData.ToArray(); int destinationLength = destination.Length; // Prepend compression depth and extract only used part of compressed buffer byte[] outBuffer = new byte[++destinationLength]; // First two bits are reserved for compression strength - this leaves 6 bits for a maximum of 64 compressions outBuffer[0] = (byte)((compressionDepth << 2) | (int)strength); for (int x = 1; x < destinationLength; x++) outBuffer[x] = destination[x - 1]; if (strength == CompressionStrength.MultiPass && destinationLength < length && compressionDepth < 64) { // See if another pass would help the compression... byte[] testBuffer = outBuffer.Compress(0, outBuffer.Length, strength, compressionDepth + 1); if (testBuffer.Length < outBuffer.Length) return testBuffer; return outBuffer; } return outBuffer; } } /// /// Compress a stream using specified compression strength. /// /// /// This returns a memory stream of the compressed results, if the incoming stream is /// very large this will consume a large amount memory. In this case use the overload /// that takes the destination stream as a parameter instead. /// /// The to compress. /// The of the compression. /// Returns a of the compressed . public static MemoryStream Compress(this Stream source, CompressionStrength strength) { MemoryStream destination = new MemoryStream(); source.Compress(destination, strength, null); return destination; } /// /// Compress a stream onto given output stream using specified compression strength. /// /// The to compress. /// The of the compression. /// The destination. /// The progress handler to check progress. public static void Compress(this Stream source, Stream destination, CompressionStrength strength, Action> progressHandler) { ProcessProgressHandler progress = null; byte[] inBuffer = new byte[BufferSize]; byte[] outBuffer; byte[] lengthBuffer; int read; long total = 0, length = -1; // Send initial progress event if ((object)progressHandler != null) { try { if (source.CanSeek) length = source.Length; } catch { length = -1; } // Create a new progress handler to track compression progress progress = new ProcessProgressHandler(progressHandler, "Compress", length); progress.Complete = 0; } // Read initial buffer read = source.Read(inBuffer, 0, BufferSize); // Write compression version into stream byte[] version = new byte[1]; version[0] = CompressionVersion; destination.Write(version, 0, 1); while (read > 0) { // Compress buffer - note that we are only going to compress used part of buffer, // we don't want any left over garbage to end up in compressed stream... outBuffer = inBuffer.Compress(0, read, strength); // The output stream is hopefully smaller than the input stream, so we prepend the final size of // each compressed buffer into the destination output stream so that we can safely uncompress // the stream in a "chunked" fashion later... lengthBuffer = BitConverter.GetBytes(outBuffer.Length); destination.Write(lengthBuffer, 0, lengthBuffer.Length); destination.Write(outBuffer, 0, outBuffer.Length); // Update compression progress if ((object)progressHandler != null) { total += read; progress.Complete = total; } // Read next buffer read = source.Read(inBuffer, 0, BufferSize); } } /// /// Decompress a byte array. /// /// The array to decompress. /// A decompressed version of the source array. public static byte[] Decompress(this byte[] source) { return source.Decompress(0, source.Length); } /// /// Decompress a byte array. /// /// The array to decompress. /// The number of bytes to read into the byte array for compression. /// An representing the start index of the byte array. /// A decompressed array. public static byte[] Decompress(this byte[] source, int startIndex, int length) { const byte CompressionStrengthMask = (byte)(Bits.Bit00 | Bits.Bit01); // Unmask compression strength from first buffer byte CompressionStrength strength = (CompressionStrength)(source[startIndex] & CompressionStrengthMask); if (strength == CompressionStrength.NoCompression) { // No compression was applied to original buffer, return specified portion of the buffer return source.BlockCopy(startIndex + 1, length - 1); } // Create a new decompression deflater using (BlockAllocatedMemoryStream compressedData = new BlockAllocatedMemoryStream(source, startIndex + 1, length - 1)) { byte[] destination; int compressionDepth = (source[startIndex] & ~CompressionStrengthMask) >> 2; using (DeflateStream inflater = new DeflateStream(compressedData, CompressionMode.Decompress)) { // Read uncompressed data destination = inflater.ReadStream(); } // When user requests multi-pass compression, there may be multiple compression passes on a buffer, // so we cycle through the needed uncompressions to get back to the original data if (strength == CompressionStrength.MultiPass && compressionDepth > 0) return destination.Decompress(); return destination; } } /// /// Decompress a stream. /// /// /// This returns a memory stream of the uncompressed results, if the incoming stream is /// very large this will consume a large amount memory. In this case use the overload /// that takes the destination stream as a parameter instead. /// /// A source to decompress. /// A representing the decompressed source. public static MemoryStream Decompress(this Stream source) { MemoryStream destination = new MemoryStream(); source.Decompress(destination, null); return destination; } /// /// Decompress a stream onto given output stream. /// /// A source to decompress. /// The destination to decompress to. /// A handler to monitor the action's progress. public static void Decompress(this Stream source, Stream destination, Action> progressHandler) { ProcessProgressHandler progress = null; byte[] inBuffer; byte[] outBuffer; byte[] lengthBuffer = BitConverter.GetBytes((int)0); int read, size; long total = 0, length = -1; // Send initial progress event if ((object)progressHandler != null) { try { if (source.CanSeek) length = source.Length; } catch { length = -1; } // Create a new progress handler to track compression progress progress = new ProcessProgressHandler(progressHandler, "Uncompress", length); progress.Complete = 0; } // Read compression version from stream byte[] versionBuffer = new byte[1]; if (source.Read(versionBuffer, 0, 1) > 0) { if (versionBuffer[0] != CompressionVersion) throw new InvalidOperationException("Invalid compression version encountered in compressed stream - decompression aborted"); // Read initial buffer read = source.Read(lengthBuffer, 0, lengthBuffer.Length); while (read > 0) { // Convert the byte array containing the buffer size into an integer size = BitConverter.ToInt32(lengthBuffer, 0); if (size > 0) { // Create and read the next buffer inBuffer = new byte[size]; read = source.Read(inBuffer, 0, size); if (read > 0) { // Uncompress buffer outBuffer = inBuffer.Decompress(); destination.Write(outBuffer, 0, outBuffer.Length); } // Update decompression progress if ((object)progressHandler != null) { total += (read + lengthBuffer.Length); progress.Complete = total; } } // Read the size of the next buffer from the stream read = source.Read(lengthBuffer, 0, lengthBuffer.Length); } } } } }