PageRenderTime 30ms CodeModel.GetById 17ms app.highlight 8ms RepoModel.GetById 1ms app.codeStats 1ms

/Utilities/Compression/Zip.cs

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