/Utilities/Compression/Zip.cs
C# | 283 lines | 173 code | 27 blank | 83 comment | 14 complexity | 308274301f3e39c306e53a458e94f908 MD5 | raw file
Possible License(s): Apache-2.0
- using System;
- using System.IO;
- using Delta.Utilities.Compression.Streams;
- using Delta.Utilities.Helpers;
- using NUnit.Framework;
-
- namespace Delta.Utilities.Compression
- {
- /// <summary>
- /// Zip class, which is the main entry point for this namespace. It allows
- /// you to access compression and decompression features without having
- /// to create ZipFile or buffers yourself. While this makes things easy for
- /// simple operations like zipping or decompressing a single file and it is
- /// also useful for network operations, this class won't help you if you need
- /// more advanced functionality like saving or loading multiple files. Use
- /// the ZipFile class instead if you need this kind of access.
- /// </summary>
- public static class Zip
- {
- #region Constants
- /// <summary>
- /// Represents the minimum size in bytes that data have to reach. Every
- /// data that is smaller don't need to pack because the packed size
- /// wouldn't be notable smaller than the original data. The border where
- /// the packed data start to be smaller than the original one (in a normal
- /// use case) is around 500 bytes, but there is also overhead in
- /// compressing and decompressing data, so it only makes sense for at
- /// least 1kb of data (e.g. a network message sending a big file).
- /// </summary>
- public const int MinimumByteDataLengthToZip = 1024;
- #endregion
-
- #region Compress (Static)
- /// <summary>
- /// Compresses a source file to the target file (by the given paths).
- /// </summary>
- /// <param name="pathOfSourceFile">Path of source file</param>
- /// <param name="pathOfTargetFile">Path of target file</param>
- /// <param name="overrideIfExistsTarget">Override target file if it exists?
- /// </param>
- public static bool Compress(string pathOfSourceFile,
- string pathOfTargetFile, bool overrideIfExistsTarget = false)
- {
- if (FileHelper.Exists(pathOfSourceFile) == false)
- {
- Log.Warning("Can't compress the non-existing file '" +
- pathOfSourceFile + "'.");
- return false;
- } // if
-
- if (FileHelper.Exists(pathOfTargetFile))
- {
- if (overrideIfExistsTarget == false)
- {
- Log.Warning("Can't create zip file '" + pathOfTargetFile +
- "' because it already exists");
- return false;
- } // if
- } // if
-
- try
- {
- // Load the original data
- byte[] sourceData = File.ReadAllBytes(pathOfSourceFile);
- // compress them
- byte[] packedData = Compress(sourceData);
- // and save it to the target file path
- File.WriteAllBytes(pathOfTargetFile, packedData);
-
- return true;
- } // try
- catch (Exception ex)
- {
- Log.Warning("Couldn't create the zip file '" + pathOfTargetFile +
- "' because of reason: '" + ex.Message + "'");
- return false;
- } // catch
- }
-
- /// <summary>
- /// Compresses the given data.
- /// <para />
- /// Note: The data should be unpacked by the "Decompress" method.
- /// </summary>
- /// <param name="dataToPack">Data to compress</param>
- /// <returns></returns>
- public static byte[] Compress(byte[] dataToPack)
- {
- #region Validation
- if (dataToPack == null)
- {
- Log.Warning("Can't compress data, because no data was given!");
- return new byte[0];
- } // if
- if (dataToPack.Length < MinimumByteDataLengthToZip)
- {
- Log.Warning(
- "It only makes sense to compress data with at least " +
- MinimumByteDataLengthToZip + " bytes, but the current data has " +
- "only '" + dataToPack.Length + "' bytes. The data will be " +
- "compressed now, but next time the data should be saved directly.");
- } // if
- #endregion
-
- // Create the output stream which will contain the packed data
- MemoryStream baseStream = new MemoryStream(dataToPack.Length);
- // and the packer stream which attaches to it
- ZipOutputStream packerStream = new ZipOutputStream(baseStream);
-
- // Now put the user data which should be packed by the zip stream
- ZipEntry newZipEntry = new ZipEntry( //obs:"CompressedData " +
- (entryId++).ToString());
- packerStream.PutNextEntry(newZipEntry);
- packerStream.Write(dataToPack, 0, dataToPack.Length);
-
- // and after that tell the zip stream everything is so it can "push" all
- // data now into our underlying data stream
- packerStream.Finish();
- // so we can extract the packed data out
- byte[] packedData = baseStream.ToArray();
-
- // Last we just dispose the packer stream again
- packerStream.Dispose();
- baseStream.Dispose();
-
- // and return the packed user data
- return packedData;
- }
- #endregion
-
- #region Decompress (Static)
- /// <summary>
- /// Decompress a zipped file (by the given file path).
- /// </summary>
- /// <param name="pathOfZipFile">Path of zip file</param>
- public static byte[] Decompress(string pathOfZipFile)
- {
- if (FileHelper.Exists(pathOfZipFile) == false)
- {
- Log.Warning("Can't decompress the non-existing file '" +
- pathOfZipFile + "'.");
- return new byte[0];
- } // if
-
- try
- {
- byte[] compressedData = File.ReadAllBytes(pathOfZipFile);
- return Decompress(compressedData);
- } // try
- catch (Exception ex)
- {
- Log.Warning("Couldn't decompress the zip file '" + pathOfZipFile +
- "' because of reason: '" + ex.Message + "'");
- return new byte[0];
- } // catch
- }
-
- /// <summary>
- /// Decompresses the given data.
- /// <para />
- /// Note: The packed data should be the result of the "Compress" method.
- /// </summary>
- /// <param name="dataToUnpack">Data to decompress</param>
- public static byte[] Decompress(byte[] dataToUnpack)
- {
- #region Validation
- if (dataToUnpack == null)
- {
- Log.Warning("Can't decompress data, because no data was given!");
- return new byte[0];
- } // if
- #endregion
-
- // Create the output stream which will contain the compressed data
- MemoryStream baseStream = new MemoryStream(dataToUnpack);
- // and the compressor stream which attaches to it
- ZipInputStream unpackerStream = new ZipInputStream(baseStream);
-
- // Now read the containing zip entry
- // Note: We assume that in this case only 1 entry exists
- ZipEntry foundEntry = unpackerStream.GetNextEntry();
- if (foundEntry == null)
- {
- Log.Warning("The given data seems to be wrong or corrupted, can't " +
- "decompress.");
- return new byte[0];
- } // if
-
- // to be able to extract the compressed data
- MemoryStream unpackedBytesStream = unpackerStream.ExtractZipEntry(
- foundEntry);
- byte[] unpackedBytes = unpackedBytesStream.ToArray();
-
- // After extraction of the data we can dispose the streams again
- unpackerStream.Dispose();
- baseStream.Dispose();
-
- // and return the decompressed user data
- return unpackedBytes;
- }
- #endregion
-
- #region Private
-
- #region entryId (Private)
- /// <summary>
- /// Self-counting entry id which is used to identify the compressed data.
- /// </summary>
- private static int entryId;
- #endregion
-
- #endregion
-
- /// <summary>
- /// Tests
- /// </summary>
- public class ZipTests
- {
- #region PackAndUnpackTextStream
- /// <summary>
- /// Pack and unpack text stream
- /// </summary>
- [Test]
- public void PackAndUnpackTextStream()
- {
- // Define an arbitrary text we want to compress and uncompress again
- string textMessage =
- // A text with 788 bytes will be compressed to 598 bytes which
- // is nearly 32% smaller. in this case we have data of '1239' bytes,
- // which is compressed down to '576' bytes (less than half).
- @"This is just a simple test message for the
- 'PackAndUnpackTextStream' unit test of Zip class in the '
- Delta.Utilities.Compressions' assembly. Adding some spaces to go
- over the 256 bytes limit (else compression will be disabled).
- Now it's just some dummy data following to simply increase the
- amount of data to compress for the current test because we need
- to the minimum limit of at least 256 bytes (for the moment).
- Otherwise if we would be below that border we would get a
- warning in the log. 1. Forsaking monastic tradition, twelve
- jovial friars gave up their vocation for a questionable
- existence on the flying trapeze. 2. No kidding -- Lorenzo called
- off his trip to visit Mexico City just because they told him the
- conquistadores were extinct. 3. Waltz, nymph, for quick jigs vex
- Bud.
-
-
-
-
-
- ";
- // Note: This compresses nicely to 153 bytes total:
- //textMessage = new string('a', 5000);
-
- // First split the text into byte packages
- byte[] originalData = StringHelper.ToByteArray(textMessage);
- // so we can compress them
- byte[] compressedData = Compress(originalData);
- Log.Test("The original data of '" + originalData.Length +
- "' bytes is compressed down to '" + compressedData.Length +
- "' bytes.");
-
- // The compressed data should not be invalid
- Assert.NotNull(compressedData);
- Assert.NotEqual(compressedData.Length, 0);
- // and shouldn't be the same data as the original
- Assert.False(ArrayHelper.Compare(originalData, compressedData));
-
- // Now start decompressing the data
- byte[] uncompressedData = Decompress(compressedData);
- // which be valid
- Assert.NotNull(uncompressedData);
- Assert.NotEqual(uncompressedData.Length, 0);
-
- // and after converting to a string again
- string uncompressedMessage = ZipConstants.ConvertToString(
- uncompressedData);
- // it should be the same message as the original one
- Assert.Equal(uncompressedMessage, textMessage);
- }
- #endregion
- }
- }
- }