PageRenderTime 361ms CodeModel.GetById 82ms RepoModel.GetById 11ms app.codeStats 0ms

/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
  1. using System;
  2. using System.IO;
  3. using Delta.Utilities.Compression.Streams;
  4. using Delta.Utilities.Helpers;
  5. using NUnit.Framework;
  6. namespace Delta.Utilities.Compression
  7. {
  8. /// <summary>
  9. /// Zip class, which is the main entry point for this namespace. It allows
  10. /// you to access compression and decompression features without having
  11. /// to create ZipFile or buffers yourself. While this makes things easy for
  12. /// simple operations like zipping or decompressing a single file and it is
  13. /// also useful for network operations, this class won't help you if you need
  14. /// more advanced functionality like saving or loading multiple files. Use
  15. /// the ZipFile class instead if you need this kind of access.
  16. /// </summary>
  17. public static class Zip
  18. {
  19. #region Constants
  20. /// <summary>
  21. /// Represents the minimum size in bytes that data have to reach. Every
  22. /// data that is smaller don't need to pack because the packed size
  23. /// wouldn't be notable smaller than the original data. The border where
  24. /// the packed data start to be smaller than the original one (in a normal
  25. /// use case) is around 500 bytes, but there is also overhead in
  26. /// compressing and decompressing data, so it only makes sense for at
  27. /// least 1kb of data (e.g. a network message sending a big file).
  28. /// </summary>
  29. public const int MinimumByteDataLengthToZip = 1024;
  30. #endregion
  31. #region Compress (Static)
  32. /// <summary>
  33. /// Compresses a source file to the target file (by the given paths).
  34. /// </summary>
  35. /// <param name="pathOfSourceFile">Path of source file</param>
  36. /// <param name="pathOfTargetFile">Path of target file</param>
  37. /// <param name="overrideIfExistsTarget">Override target file if it exists?
  38. /// </param>
  39. public static bool Compress(string pathOfSourceFile,
  40. string pathOfTargetFile, bool overrideIfExistsTarget = false)
  41. {
  42. if (FileHelper.Exists(pathOfSourceFile) == false)
  43. {
  44. Log.Warning("Can't compress the non-existing file '" +
  45. pathOfSourceFile + "'.");
  46. return false;
  47. } // if
  48. if (FileHelper.Exists(pathOfTargetFile))
  49. {
  50. if (overrideIfExistsTarget == false)
  51. {
  52. Log.Warning("Can't create zip file '" + pathOfTargetFile +
  53. "' because it already exists");
  54. return false;
  55. } // if
  56. } // if
  57. try
  58. {
  59. // Load the original data
  60. byte[] sourceData = File.ReadAllBytes(pathOfSourceFile);
  61. // compress them
  62. byte[] packedData = Compress(sourceData);
  63. // and save it to the target file path
  64. File.WriteAllBytes(pathOfTargetFile, packedData);
  65. return true;
  66. } // try
  67. catch (Exception ex)
  68. {
  69. Log.Warning("Couldn't create the zip file '" + pathOfTargetFile +
  70. "' because of reason: '" + ex.Message + "'");
  71. return false;
  72. } // catch
  73. }
  74. /// <summary>
  75. /// Compresses the given data.
  76. /// <para />
  77. /// Note: The data should be unpacked by the "Decompress" method.
  78. /// </summary>
  79. /// <param name="dataToPack">Data to compress</param>
  80. /// <returns></returns>
  81. public static byte[] Compress(byte[] dataToPack)
  82. {
  83. #region Validation
  84. if (dataToPack == null)
  85. {
  86. Log.Warning("Can't compress data, because no data was given!");
  87. return new byte[0];
  88. } // if
  89. if (dataToPack.Length < MinimumByteDataLengthToZip)
  90. {
  91. Log.Warning(
  92. "It only makes sense to compress data with at least " +
  93. MinimumByteDataLengthToZip + " bytes, but the current data has " +
  94. "only '" + dataToPack.Length + "' bytes. The data will be " +
  95. "compressed now, but next time the data should be saved directly.");
  96. } // if
  97. #endregion
  98. // Create the output stream which will contain the packed data
  99. MemoryStream baseStream = new MemoryStream(dataToPack.Length);
  100. // and the packer stream which attaches to it
  101. ZipOutputStream packerStream = new ZipOutputStream(baseStream);
  102. // Now put the user data which should be packed by the zip stream
  103. ZipEntry newZipEntry = new ZipEntry( //obs:"CompressedData " +
  104. (entryId++).ToString());
  105. packerStream.PutNextEntry(newZipEntry);
  106. packerStream.Write(dataToPack, 0, dataToPack.Length);
  107. // and after that tell the zip stream everything is so it can "push" all
  108. // data now into our underlying data stream
  109. packerStream.Finish();
  110. // so we can extract the packed data out
  111. byte[] packedData = baseStream.ToArray();
  112. // Last we just dispose the packer stream again
  113. packerStream.Dispose();
  114. baseStream.Dispose();
  115. // and return the packed user data
  116. return packedData;
  117. }
  118. #endregion
  119. #region Decompress (Static)
  120. /// <summary>
  121. /// Decompress a zipped file (by the given file path).
  122. /// </summary>
  123. /// <param name="pathOfZipFile">Path of zip file</param>
  124. public static byte[] Decompress(string pathOfZipFile)
  125. {
  126. if (FileHelper.Exists(pathOfZipFile) == false)
  127. {
  128. Log.Warning("Can't decompress the non-existing file '" +
  129. pathOfZipFile + "'.");
  130. return new byte[0];
  131. } // if
  132. try
  133. {
  134. byte[] compressedData = File.ReadAllBytes(pathOfZipFile);
  135. return Decompress(compressedData);
  136. } // try
  137. catch (Exception ex)
  138. {
  139. Log.Warning("Couldn't decompress the zip file '" + pathOfZipFile +
  140. "' because of reason: '" + ex.Message + "'");
  141. return new byte[0];
  142. } // catch
  143. }
  144. /// <summary>
  145. /// Decompresses the given data.
  146. /// <para />
  147. /// Note: The packed data should be the result of the "Compress" method.
  148. /// </summary>
  149. /// <param name="dataToUnpack">Data to decompress</param>
  150. public static byte[] Decompress(byte[] dataToUnpack)
  151. {
  152. #region Validation
  153. if (dataToUnpack == null)
  154. {
  155. Log.Warning("Can't decompress data, because no data was given!");
  156. return new byte[0];
  157. } // if
  158. #endregion
  159. // Create the output stream which will contain the compressed data
  160. MemoryStream baseStream = new MemoryStream(dataToUnpack);
  161. // and the compressor stream which attaches to it
  162. ZipInputStream unpackerStream = new ZipInputStream(baseStream);
  163. // Now read the containing zip entry
  164. // Note: We assume that in this case only 1 entry exists
  165. ZipEntry foundEntry = unpackerStream.GetNextEntry();
  166. if (foundEntry == null)
  167. {
  168. Log.Warning("The given data seems to be wrong or corrupted, can't " +
  169. "decompress.");
  170. return new byte[0];
  171. } // if
  172. // to be able to extract the compressed data
  173. MemoryStream unpackedBytesStream = unpackerStream.ExtractZipEntry(
  174. foundEntry);
  175. byte[] unpackedBytes = unpackedBytesStream.ToArray();
  176. // After extraction of the data we can dispose the streams again
  177. unpackerStream.Dispose();
  178. baseStream.Dispose();
  179. // and return the decompressed user data
  180. return unpackedBytes;
  181. }
  182. #endregion
  183. #region Private
  184. #region entryId (Private)
  185. /// <summary>
  186. /// Self-counting entry id which is used to identify the compressed data.
  187. /// </summary>
  188. private static int entryId;
  189. #endregion
  190. #endregion
  191. /// <summary>
  192. /// Tests
  193. /// </summary>
  194. public class ZipTests
  195. {
  196. #region PackAndUnpackTextStream
  197. /// <summary>
  198. /// Pack and unpack text stream
  199. /// </summary>
  200. [Test]
  201. public void PackAndUnpackTextStream()
  202. {
  203. // Define an arbitrary text we want to compress and uncompress again
  204. string textMessage =
  205. // A text with 788 bytes will be compressed to 598 bytes which
  206. // is nearly 32% smaller. in this case we have data of '1239' bytes,
  207. // which is compressed down to '576' bytes (less than half).
  208. @"This is just a simple test message for the
  209. 'PackAndUnpackTextStream' unit test of Zip class in the '
  210. Delta.Utilities.Compressions' assembly. Adding some spaces to go
  211. over the 256 bytes limit (else compression will be disabled).
  212. Now it's just some dummy data following to simply increase the
  213. amount of data to compress for the current test because we need
  214. to the minimum limit of at least 256 bytes (for the moment).
  215. Otherwise if we would be below that border we would get a
  216. warning in the log. 1. Forsaking monastic tradition, twelve
  217. jovial friars gave up their vocation for a questionable
  218. existence on the flying trapeze. 2. No kidding -- Lorenzo called
  219. off his trip to visit Mexico City just because they told him the
  220. conquistadores were extinct. 3. Waltz, nymph, for quick jigs vex
  221. Bud.
  222. ";
  223. // Note: This compresses nicely to 153 bytes total:
  224. //textMessage = new string('a', 5000);
  225. // First split the text into byte packages
  226. byte[] originalData = StringHelper.ToByteArray(textMessage);
  227. // so we can compress them
  228. byte[] compressedData = Compress(originalData);
  229. Log.Test("The original data of '" + originalData.Length +
  230. "' bytes is compressed down to '" + compressedData.Length +
  231. "' bytes.");
  232. // The compressed data should not be invalid
  233. Assert.NotNull(compressedData);
  234. Assert.NotEqual(compressedData.Length, 0);
  235. // and shouldn't be the same data as the original
  236. Assert.False(ArrayHelper.Compare(originalData, compressedData));
  237. // Now start decompressing the data
  238. byte[] uncompressedData = Decompress(compressedData);
  239. // which be valid
  240. Assert.NotNull(uncompressedData);
  241. Assert.NotEqual(uncompressedData.Length, 0);
  242. // and after converting to a string again
  243. string uncompressedMessage = ZipConstants.ConvertToString(
  244. uncompressedData);
  245. // it should be the same message as the original one
  246. Assert.Equal(uncompressedMessage, textMessage);
  247. }
  248. #endregion
  249. }
  250. }
  251. }