How to compress a file before saving on the disk? - c#

I want to compress a file before saving physically on the disk.
I tried using compress and decompress methods (MSDN sample code) but all methods require a file which is already physically stored on the disk.

The easiest way is to open the file as a Stream and wrap it with a compression API like GZipStream.
using (var fileStream = File.Open(theFilePath, FileMode.OpenOrCreate) {
using (var stream = new GZipStream(fileStream, CompressionMode.Compress)) {
// Write to the `stream` here and the result will be compressed
}
}

Description
You can use the GZipStream class not only with a fileName. It is possible to compress a Stream.
GZipStream Class Provides methods and properties used to compress and decompress streams.
Sample
System.IO.MemoryStream ms = new System.IO.MemoryStream();
System.IO.Compression.GZipStream sw = new System.IO.Compression.GZipStream(ms,
System.IO.Compression.CompressionMode.Compress);
// now you can save the file to disc
More Information
MSDN - GZipStream Class

Can't you use the GZipStream class? It's stream based, so you shouldn't need an on-disk file to use this class.
Which kind of data are you trying to compress?

Use MemoryStream and GZipStream.
File is an array of bytes so you can try following code according to http://www.dotnetperls.com/compress :
using System;
using System.IO;
using System.IO.Compression;
using System.Text;
namespace ConsoleApplication1
{
internal class Program
{
private static void Main(string[] args)
{
byte[] text = Encoding.ASCII.GetBytes(new string('X', 10000));
byte[] compress = Compress(text);
Console.WriteLine("Compressed");
foreach (var b in compress)
{
Console.WriteLine("{0} ", b);
}
Console.ReadKey();
}
public static byte[] Compress(byte[] raw)
{
using (var memory = new MemoryStream())
{
using (var gzip = new GZipStream(memory, CompressionMode.Compress, true))
{
gzip.Write(raw, 0, raw.Length);
}
return memory.ToArray();
}
}
}
}

Related

C# GZipStream cannot use the CopyTo method when decompressed via MemoryStream

// code href="https://www.cnblogs.com/mahuanpeng/p/6851793.html"
// Compress bytes
//1. Create a compressed data stream
//2. Set compressStream to store the compressed file stream and set it to compression mode
//3. Write the bytes to be compressed to the compressed file stream
public static byte[] CompressBytes(byte[] bytes)
{
Using (MemoryStream by compressStream = new MemoryStream())
{
Using (var zipStream = new GZipStream(compressStream, System.IO.Compression.CompressionLevel.SmallestSize))
ZipStream.Write(bytes,0, bytes.Length).
Return compressStream.ToArray();
}
}
// Unzip the bytes
//1. Create a compressed data stream
//2. Create the GzipStream object and pass in the unzipped file stream
//3. Create the target flow
//4. Copy zipStream to the destination stream
//5. Return destination stream output bytes
public static byte[] Decompress(byte[] bytes)
{
Using (var compressStream = new MemoryStream(bytes))
{
Using (var zipStream = new GZipStream(compressStream, System.IO.Compression.CompressionLevel.SmallestSize)
{
Using (var resultStream = new MemoryStream())
{
ZipStream.CopyTo(resultStream);
Return resultStream.ToArray();
}
}
}
}
This may seem correct, but in "unzipped code", the following exception occurs:
Unhandled exception. System.NotSupportedException: Specified method is not supported.
At System.IO.Compression.DeflateStream.CopyTo(Stream destination, Int32 bufferSize)
At System.IO.Compression.GZipStream.CopyTo(Stream destination, Int32 bufferSize)
At System.IO.Stream.CopyTo(Stream destination)
Version: .NET6
Although I tried this: C# Unable to copy to MemoryStream from GZipStream
I had to compress and decompress the data in memory. Instead of using FileStream and temporary files, I used. NET6, the compression function is not specified, as long as it can be used. NET library instead of nuget package. If there is a better alternative on Nuget, I would consider it. Other alternatives are also acceptable, as long as the performance of byte[] compression and decompression is achieved. This program needs to be cross-platform!
You created a compression stream, you need a decompression stream instead:
using var unzipStream = new GZipStream(compressStream, CompressionMode.Decompress);

Reading zip file from byte array using Ionic.zip

I have a piece of code that allows to decompress a byte array:
public static byte[] Decompress(this byte[] data)
{
using (ZipFile zout = ZipFile.Read(data))
{
ZipEntry entry = zout.FirstOrDefault();
Assert.ObjectIsNotNull(entry, "Unable to find default ZIP entry");
MemoryStream zos = new MemoryStream();
entry.Extract(zos);
return zos.ToArray();
}
}
I upgraded to the latest version of Ionic.zip and now I am getting the following error:
Cannot convert byte[] to string.
The overload ZipFile.Read(byte[]) is no longer available in the most recent version.
How can I read a zip file from a byte array?
The ZipFile.Read method takes either a filename or a stream to read, so you need to provide a stream for it to read:
using (MemoryStream stream = new MemoryStream(data))
using (ZipFile zout = ZipFile.Read(stream))
{
// ....
You can use the built-in ZipArchive class in System.IO.Commpression.
using(var stream = new MemoryStream(data))
{
using(var archive = new ZipArchive(stream))
{
// Use the archive
}
}
ZipArchive
https://msdn.microsoft.com/en-us/library/hh158268(v=vs.110).aspx
MemoryStream
https://msdn.microsoft.com/en-us/library/e55f3s5k(v=vs.110).aspx
You will need to add a reference to System.IO.Compression, it is not in mscorlib.

Extracting Zip file save to disk

I have a zip file at a URI (such as http://www.abc.com/a.zip) that I would like to open and save a file from it to disk. Is there a way in C# to open it without saving it to disk and then saving a file from it to disk?
Thanks,
Sachin
Use ZipFile .Net Framework 4.5 class or DotNetZip API.
ZipFile.ExtractToDirectory(zipPath, extractPath);
EDIT: You can prepare a stream or obtain byte array of URL via method of WebClient class.
string urlStr = "https://xyz.com/sample.zip";
using (WebClient client = new WebClient())
{
byte []bytes=client.DownloadData(urlStr);
using (MemoryStream ms = new MemoryStream(bytes))
{
using (ZipFile zip = ZipFile.Read(ms))
{
zip.ExtractAll(#"C:\csnet");
}
}
}
The example of the extracting the zip file without saving the archive to the file system using DotNetZip.
private static void ExtractFromUrl(Uri uri, string directoryPath)
{
using (var webClient = new WebClient())
{
var data = webClient.DownloadData(uri);
using (var memoryStream = new MemoryStream(data))
using (var zipFile = ZipFile.Read(memoryStream))
{
zipFile.ExtractAll(directoryPath);
}
}
}

Why is compressed then uncompressed stream of different length

I'm using the SevenZipSharp library to compress and then uncompress a MemoryStream which contains a simple serialized object. However, the compressed and decompressed streams are of different length.
From the code run below I get
Input length: 174
Output length: 338
(the SevenZipSharp dll is included as a reference and the 7z.dll is included in the project output)
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.IO;
using System.Runtime.Serialization.Formatters.Binary;
namespace DataTransmission {
class Program {
static void Main(string[] args)
{
SevenZip.SevenZipCompressor compressor = new SevenZip.SevenZipCompressor();
//compressor.CompressionMethod = SevenZip.CompressionMethod.Lzma2;
//compressor.CompressionLevel = SevenZip.CompressionLevel.Normal;
MemoryStream inputStream = new MemoryStream();
Person me = new Person("John", "Smith");
BinaryFormatter formatter = new BinaryFormatter();
formatter.Serialize(inputStream, me);
Int32 inputStreamLength = (Int32)inputStream.Length;
MemoryStream outputStream = new MemoryStream();
compressor.CompressStream(inputStream, outputStream);
SevenZip.SevenZipExtractor decompressor = new SevenZip.SevenZipExtractor(outputStream);
decompressor.ExtractFile(0, outputStream);
Int32 outputStreamLength = (Int32)outputStream.Length;
Console.WriteLine("Input length: {0}", inputStreamLength);
Console.WriteLine("Output length: {0}", outputStreamLength);
Console.ReadLine();
}
}
[Serializable]
public class Person {
public string firstName;
public string lastName;
public Person(string fname, string lname) {
firstName = fname;
lastName = lname;
}
}
}
Can anyone help me with why this may be?
Thanks,
You've decompressed into outputStream despite that already containing data. You should use a new MemoryStream for the output.
(In fact, it's very odd because the decompressor is reading from outputStream and also writing to outputStream. Bad idea. Use two different streams.)
You should also rewind each stream after you've written to it and before something else wants to read it, e.g. with
inputStream.Position = 0;
It's possible that SevenZipLib is doing that for you in this case, but in general if you want something to act from the start of the stream, you should reset it appropriately.
I've just made the following change to your code, at which point I get the same length for input and output:
MemoryStream targetStream = new MemoryStream();
decompressor.ExtractFile(0, targetStream);
Int32 outputStreamLength = (Int32)targetStream.Length;
As I say, you should make the appropriate other changes too.
However, the compressed and decompressed streams are of different length
That is the whole purpose of compression ...
Look at this piece of the code:
SevenZip.SevenZipExtractor decompressor =
new SevenZip.SevenZipExtractor(outputStream);
decompressor.ExtractFile(0, outputStream);
You are decompressing from outputStream to outputStream. It will probably fail with larger data. Make changes so that it reads
SevenZip.SevenZipExtractor decompressor =
new SevenZip.SevenZipExtractor(compressedStream);
decompressor.ExtractFile(0, outputStream);

Create Zip archive from multiple in memory files in C#

Is there a way to create a Zip archive that contains multiple files, when the files are currently in memory? The files I want to save are really just text only and are stored in a string class in my application. But I would like to save multiple files in a single self-contained archive. They can all be in the root of the archive.
It would be nice to be able to do this using SharpZipLib.
Use ZipEntry and PutNextEntry() for this. The following shows how to do it for a file, but for an in-memory object just use a MemoryStream
FileStream fZip = File.Create(compressedOutputFile);
ZipOutputStream zipOStream = new ZipOutputStream(fZip);
foreach (FileInfo fi in allfiles)
{
ZipEntry entry = new ZipEntry((fi.Name));
zipOStream.PutNextEntry(entry);
FileStream fs = File.OpenRead(fi.FullName);
try
{
byte[] transferBuffer[1024];
do
{
bytesRead = fs.Read(transferBuffer, 0, transferBuffer.Length);
zipOStream.Write(transferBuffer, 0, bytesRead);
}
while (bytesRead > 0);
}
finally
{
fs.Close();
}
}
zipOStream.Finish();
zipOStream.Close();
Using SharpZipLib for this seems pretty complicated. This is so much easier in DotNetZip. In v1.9, the code looks like this:
using (ZipFile zip = new ZipFile())
{
zip.AddEntry("Readme.txt", stringContent1);
zip.AddEntry("readings/Data.csv", stringContent2);
zip.AddEntry("readings/Index.xml", stringContent3);
zip.Save("Archive1.zip");
}
The code above assumes stringContent{1,2,3} contains the data to be stored in the files (or entries) in the zip archive. The first entry is "Readme.txt" and it is stored in the top level "Directory" in the zip archive. The next two entries are stored in the "readings" directory in the zip archive.
The strings are encoded in the default encoding. There is an overload of AddEntry(), not shown here, that allows you to explicitly specify the encoding to use.
If you have the content in a stream or byte array, not a string, there are overloads for AddEntry() that accept those types. There are also overloads that accept a Write delegate, a method of yours that is invoked to write data into the zip. This works for easily saving a DataSet into a zip file, for example.
DotNetZip is free and open source.
This function should create a byte array from a stream of data: I've created a simple interface for handling files for simplicity
public interface IHasDocumentProperties
{
byte[] Content { get; set; }
string Name { get; set; }
}
public void CreateZipFileContent(string filePath, IEnumerable<IHasDocumentProperties> fileInfos)
{
using (var memoryStream = new MemoryStream())
{
using (var zipArchive = new ZipArchive(memoryStream, ZipArchiveMode.Create, true))
{
foreach(var fileInfo in fileInfos)
{
var entry = zipArchive.CreateEntry(fileInfo.Name);
using (var entryStream = entry.Open())
{
entryStream.Write(fileInfo.Content, 0, fileInfo.Content.Length);
}
}
}
using (var fileStream = new FileStream(filePath, FileMode.OpenOrCreate, System.IO.FileAccess.Write))
{
memoryStream.Position = 0;
memoryStream.CopyTo(fileStream);
}
}
}
Yes, you can use SharpZipLib to do this - when you need to supply a stream to write to, use a MemoryStream.
I come across this problem, using the MSDN example I created this class:
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.IO.Packaging;
using System.IO;
public class ZipSticle
{
Package package;
public ZipSticle(Stream s)
{
package = ZipPackage.Open(s, FileMode.Create);
}
public void Add(Stream stream, string Name)
{
Uri partUriDocument = PackUriHelper.CreatePartUri(new Uri(Name, UriKind.Relative));
PackagePart packagePartDocument = package.CreatePart(partUriDocument, "");
CopyStream(stream, packagePartDocument.GetStream());
stream.Close();
}
private static void CopyStream(Stream source, Stream target)
{
const int bufSize = 0x1000;
byte[] buf = new byte[bufSize];
int bytesRead = 0;
while ((bytesRead = source.Read(buf, 0, bufSize)) > 0)
target.Write(buf, 0, bytesRead);
}
public void Close()
{
package.Close();
}
}
You can then use it like this:
FileStream str = File.Open("MyAwesomeZip.zip", FileMode.Create);
ZipSticle zip = new ZipSticle(str);
zip.Add(File.OpenRead("C:/Users/C0BRA/SimpleFile.txt"), "Some directory/SimpleFile.txt");
zip.Add(File.OpenRead("C:/Users/C0BRA/Hurp.derp"), "hurp.Derp");
zip.Close();
str.Close();
You can pass a MemoryStream (or any Stream) to ZipSticle.Add such as:
FileStream str = File.Open("MyAwesomeZip.zip", FileMode.Create);
ZipSticle zip = new ZipSticle(str);
byte[] fileinmem = new byte[1000];
// Do stuff to FileInMemory
MemoryStream memstr = new MemoryStream(fileinmem);
zip.Add(memstr, "Some directory/SimpleFile.txt");
memstr.Close();
zip.Close();
str.Close();
Note this answer is outdated; since .Net 4.5, the ZipArchive class allows zipping files in-memory. See johnny 5's answer below for how to use it.
You could also do it a bit differently, using a Serializable object to store all strings
[Serializable]
public class MyStrings {
public string Foo { get; set; }
public string Bar { get; set; }
}
Then, you could serialize it into a stream to save it.
To save on space you could use GZipStream (From System.IO.Compression) to compress it. (note: GZip is stream compression, not an archive of multiple files).
That is, of course if what you need is actually to save data, and not zip a few files in a specific format for other software.
Also, this would allow you to save many more types of data except strings.
I was utilizing Cheeso's answer by adding MemoryStreams as the source of the different Excel files. When I downloaded the zip, the files had nothing in them. This could be the way we were getting around trying to create and download a file over AJAX.
To get the contents of the different Excel files to be included in the Zip, I had to add each of the files as a byte[].
using (var memoryStream = new MemoryStream())
using (var zip = new ZipFile())
{
zip.AddEntry("Excel File 1.xlsx", excelFileStream1.ToArray());
zip.AddEntry("Excel File 2.xlsx", excelFileStream2.ToArray());
// Keep the file off of disk, and in memory.
zip.Save(memoryStream);
}
Use a StringReader to read from your string objects and expose them as Stream s.
That should make it easy to feed them to your zip-building code.

Categories

Resources