The System.IO.Compression namespace includes the GZipStream class that can be used to compress files and memory. The examples on MSDN show the straight forward way of compressing a single file, however in my case I wanted to do several files. Not wanting to use any 3rd party libraries, I came up with my own way of doing this.
GZipStream compresses a MemoryStream. The trick is to create a memory stream that contains the files to compress, and write it out in a single block. However the issue then becomes, how do you know what the files are and how big are they?
I blocked out the memory like this.
[FileNameLength 4bytes]
[FileName (FileNameLength bytes)]
[FileLength 8bytes] [File (FileLength bytes)]
.. repeat
using (MemoryStream stream = new MemoryStream()) { foreach (FileInfo file in files) { byte[] filenameBytes = Encoding.Unicode.GetBytes(file.Name); byte[] filenameLength = BitConverter.GetBytes(filenameBytes.Length); var fstream = file.OpenRead(); long size = fstream.Length; byte[] fileBytesLength = BitConverter.GetBytes(size); stream.Write(filenameLength, 0, filenameLength.Length); stream.Write(filenameBytes, 0, filenameBytes.Length); stream.Write(fileBytesLength, 0, fileBytesLength.Length); fstream.CopyTo(stream); } stream.Position = 0;<span style="color: #008800; font-weight: bold">using</span> (FileStream compressTo = File.Create(filepath)) { <span style="color: #008800; font-weight: bold">using</span> (GZipStream compression = <span style="color: #008800; font-weight: bold">new</span> GZipStream(compressTo, CompressionMode.Compress)) { stream.CopyTo(compression); } }
}
I was having issues at first when trying to write out the MemoryStream, and finally realized the issue was because the stream position was at the end of the memory stream. Be sure to stream.Position = 0 the stream before calling the CopyTo.
Then when Decompressing the package it is straight forward how to get the files.
using (FileStream decompressFrom = File.OpenRead(filepath)) { using (GZipStream compression = new GZipStream(decompressFrom, CompressionMode.Decompress)) { using (MemoryStream decomp = new MemoryStream()) { compression.CopyTo(decomp); decomp.Position = 0; byte[] filenameLength = new byte[4]; byte[] fileBytesLength = new byte[8]; while (decomp.Position != decomp.Length) { decomp.Read(filenameLength, 0, filenameLength.Length); int filenameInt = BitConverter.ToInt32(filenameLength, 0);<span style="color: #333399; font-weight: bold">byte</span>[] filenameBytes = <span style="color: #008800; font-weight: bold">new</span> <span style="color: #333399; font-weight: bold">byte</span>[filenameInt]; decomp.Read(filenameBytes, <span style="color: #6600EE; font-weight: bold">0</span>, filenameBytes.Length); <span style="color: #333399; font-weight: bold">string</span> filename = Encoding.Unicode.GetString(filenameBytes); decomp.Read(fileBytesLength, <span style="color: #6600EE; font-weight: bold">0</span>, fileBytesLength.Length); <span style="color: #333399; font-weight: bold">long</span> filesizeInt = BitConverter.ToInt64(fileBytesLength, <span style="color: #6600EE; font-weight: bold">0</span>); <span style="color: #333399; font-weight: bold">byte</span>[] fileBytes = <span style="color: #008800; font-weight: bold">new</span> <span style="color: #333399; font-weight: bold">byte</span>[filesizeInt]; decomp.Read(fileBytes, <span style="color: #6600EE; font-weight: bold">0</span>, fileBytes.Length); File.WriteAllBytes(outputfolder + filename, fileBytes); } } }
Calling the CopyTo on the GZipStream decompresses the files into the MemoryStream, and then it traverses through the memory parsing out each file.
And with that, you can package and unpackage multiple files! No Thirdparty libs required! This could be extended to include nested directories fairly easily. Just include the directories in the file name and when unpacking, create them. In my case I just need one level of files.