The program I am working on is currently using a StreamWriter to create one or many text files in a target folder. Off StreamWriter class, I am using WriteLine and its IDisposable interface via Using directive (for implicit .Close).
I need to add an option to create one or many text files in a zip archive inside a target folder. I was going to change existing code to use streams, so it's possible to use a ZIP file as an output (planning to use DotNetZip).
I was thinking to create some GetOutputStream function and feed that into the currently existing method. This function would determine whether archive option is set, and either create plain files, or archive them. Problem is that MemoryStream, which looks like a good buffer class to use with DotNetZip, does not intersect with StreamWriter in the inheritance hierarchy.
Looks like my only option is to create some IWriteLine interface, which would implement WriteLine and IDisposable. Then branch two new child classes from StreamWriter and MemoryStream, and implement IWriteLine in them.
Is there a better solution?
The current code conceptually looks like this:
Using sw As StreamWriter = File.CreateText(fullPath)
sw.WriteLine(header)
sw.WriteLine(signature)
While dr.Read 'dr=DataReader
Dim record As String = GetDataRecord(dr)
sw.WriteLine(record)
End While
End Using
For code samples, either VB.NET or C# is fine, although this is more of a conceptual question.
EDIT: Cannot use .NET 4.5's System.IO.Compression.ZipArchive, have to stick with .NET 4.0. We still need to support clients running on Windows 2003.
Use the StreamWriter(Stream) constructor to have it write to a MemoryStream. Set the Position back to 0 so you can then save the written text to the archive with ZipFile.Save(Stream). Check the ZipIntoMemory helper method in the project's sample code for guidance.
First of all, with .NET 4.5 System.IO.Compression.ZipArchive class (see http://msdn.microsoft.com/en-us/library/system.io.compression.ziparchive.aspx) you no longer need DotNetZip at least for common zipping tasks.
It could look like this:
string filePath = "...";
//Create file.
using (FileStream fileStream = File.Create(filePath))
{
//Create archive infrastructure.
using (ZipArchive archive = new ZipArchive(fileStream, ZipArchiveMode.Create, true, Encoding.UTF8))
{
SqlDataReader sqlReader = null;
//Reading each row into a separate text file in the archive.
while(sqlReader.Read())
{
string record = sqlReader.GetString(0);
//Archive entry is a file inside archive.
ZipArchiveEntry entry = archive.CreateEntry("...", CompressionLevel.Optimal);
//Get stream to write the archive item body.
using (Stream entryStream = entry.Open())
{
//All you need here is to write data into archive item stream.
byte[] recordData = Encoding.Unicode.GetBytes(record);
MemoryStream recordStream = new MemoryStream(recordData);
recordStream.CopyTo(entryStream);
//Flush the archive item to avoid data loss on dispose.
entryStream.Flush();
}
}
}
}
Related
I have a large zip file (let's say 10 GB), to which I want to add a single small file (let's say 50 KB). I'm using the following code:
using System.IO.Compression;
using (var targetZip = ZipFile.Open(largeZipFilePath), ZipArchiveMode.Update)
{
targetZip.CreateEntryFromFile(smallFilePath, "foobar");
}
While this works (eventually), it takes a very long time and consumes a ludicrous amount of memory. It seems to extract and recompress the whole archive.
How can I improve this in .Net 4.7? Solution without external dependencies is preferred, but not required if impossible.
use visual studio nuget package manager and install that
Install-Package DotNetZip -Version 1.11.0
using (ZipFile zip = new ZipFile())
{
zip.AddFile("ReadMe.txt"); // no password for this one
zip.Password= "123456!";
zip.AddFile("7440-N49th.png");
zip.Password= "!Secret1";
zip.AddFile("2005_Annual_Report.pdf");
zip.Save("Backup.zip");
}
https://www.nuget.org/packages/DotNetZip/
Since you are in above .NET 4.5, you can use the ZipArchive (System.IO.Compression) class to achieve this. Here is the MSDN documentation: (MSDN).
Here is their example, it just writes text, but you could read in a .csv file and write it out to your new file. To just copy the file in, you would use CreateFileFromEntry, which is an extension method for ZipArchive.
using (FileStream zipToOpen = new FileStream(#"c:\users\exampleuser\release.zip", FileMode.Open))
{
using (ZipArchive archive = new ZipArchive(zipToOpen, ZipArchiveMode.Update))
{
ZipArchiveEntry readmeEntry = archive.CreateEntry("Readme.txt");
using (StreamWriter writer = new StreamWriter(readmeEntry.Open()))
{
writer.WriteLine("Information about this package.");
writer.WriteLine("========================");
}
}
}
Check this:- https://stackoverflow.com/a/22339337/9912441
https://learn.microsoft.com/en-us/dotnet/standard/io/how-to-compress-and-extract-files
I found the reason for this behaviour in another Stack Overflow answer: Out of memory exception while updating zip in c#.net.
The gist of it is that this takes a long time because ZipArchiveMode.Update caches the zip file into memory. The suggestion for avoiding this caching behaviour is to create a new archive, and copy the old archive contents along with the new file to it.
See the MSDN documentation which explains how ZipArchiveMode.Update behaves:
So what I'm trying to do right now is opening a .Zip file and renaming a file inside it (.NET 4.6.1). I don't think I'm allowed to use third-party libraries since this is a very simple operation (or so I thought, because I couldn't find any MSDN function to rename entries).
I found a couple of ways, but they are nasty. You can extract the file to disk and add it again with a different name, or you can also create a new entry with the new name in the zip, copy the file through a stream, and delete the original entry.
Is there any effective way to do this? I don't mind any ideas at this point. I know that with DotNetZip its only one line but I can't use a third part library.
Thanks a lot for the help!
Using the ZipArchive in System.IO.Compression. Here is an example that adds a .dat extension to every entry in the specified zip file:
private static void RenameZipEntries(string file)
{
using (var archive = new ZipArchive(File.Open(file, FileMode.Open, FileAccess.ReadWrite), ZipArchiveMode.Update))
{
var entries = archive.Entries.ToArray();
foreach (var entry in entries)
{
var newEntry = archive.CreateEntry(entry.Name + ".dat");
using (var a = entry.Open())
using (var b = newEntry.Open())
a.CopyTo(b);
entry.Delete();
}
}
}
How can I read content of a text file inside a zip archive?
For example I have an archive qwe.zip, and insite it there's a file asd.txt, so how can I read contents of that file?
Is it possible to do without extracting the whole archive? Because it need to be done quick, when user clicks a item in a list, to show description of the archive (it needed for plugin system for another program). So extracting a whole archive isn't the best solution... because it might be few Mb, which will take at least few seconds or even more to extract... while only that single file need to be read.
You could use a library such as SharpZipLib or DotNetZip to unzip the file and fetch the contents of individual files contained inside. This operation could be performed in-memory and you don't need to store the files into a temporary folder.
Unzip to a temp-folder take the file and delete the temp-data
public static void Decompress(string outputDirectory, string zipFile)
{
try
{
if (!File.Exists(zipFile))
throw new FileNotFoundException("Zip file not found.", zipFile);
Package zipPackage = ZipPackage.Open(zipFile, FileMode.Open, FileAccess.Read);
foreach (PackagePart part in zipPackage.GetParts())
{
string targetFile = outputDirectory + "\\" + part.Uri.ToString().TrimStart('/');
using (Stream streamSource = part.GetStream(FileMode.Open, FileAccess.Read))
{
using (Stream streamDestination = File.OpenWrite(targetFile))
{
Byte[] arrBuffer = new byte[10000];
int iRead = streamSource.Read(arrBuffer, 0, arrBuffer.Length);
while (iRead > 0)
{
streamDestination.Write(arrBuffer, 0, iRead);
iRead = streamSource.Read(arrBuffer, 0, arrBuffer.Length);
}
}
}
}
}
catch (Exception)
{
throw;
}
}
Although late in the game and the question is already answered, in hope that this still might be useful for others who find this thread, I would like to add another solution.
Just today I encountered a similar problem when I wanted to check the contents of a ZIP file with C#. Other than NewProger I cannot use a third party library and need to stay within the out-of-the-box .NET classes.
You can use the System.IO.Packaging namespace and use the ZipPackage class. If it is not already included in the assembly, you need to add a reference to WindowsBase.dll.
It seems, however, that this class does not always work with every Zip file. Calling GetParts() may return an empty list although in the QuickWatch window you can find a property called _zipArchive that contains the correct contents.
If this is the case for you, you can use Reflection to get the contents of it.
On geissingert.com you can find a blog article ("Getting a list of files from a ZipPackage") that gives a coding example for this.
SharpZipLib or DotNetZip may still need to get/read the whole .zip file to unzip a file. Actually, there is still method could make you just extract special file from the .zip file without reading the entire .zip file but just reading small segment.
I needed to have insights into Excel files, I did it like so:
using (var zip = ZipFile.Open("ExcelWorkbookWithMacros.xlsm", ZipArchiveMode.Update))
{
var entry = zip.GetEntry("xl/_rels/workbook.xml.rels");
if (entry != null)
{
var tempFile = Path.GetTempFileName();
entry.ExtractToFile(tempFile, true);
var content = File.ReadAllText(tempFile);
[...]
}
}
this is my first question on here, so bear with me.
What I'm aiming to do is just create a basic .zip archive in C#. I have tried using the built-in GZipStream class of .NET and have managed to accomplish this, but then I have the problem that I cannot name the file "usercode.zip" without the archived file losing it's extension. Due to constraints I cannot make my program create these files as "usercode.trf.zip", which is the only way I've found of leaving the filename's extension intact inside the archive.
I've tried using a number of other zipping libraries and I can't seem to manage getting them working properly or the way I want it to.
I came upon the SevenZipHelper library that provides some nifty functions to use the LZMA (or 7-zip) library to compress a file.
The code I'm using looks as follows:
//Take the BF file and zip it, using 7ZipHelper
BinaryReader bReader = new BinaryReader(File.Open(pFileName, FileMode.Open));
byte[] InBuf = new byte[Count];
bReader.Read(InBuf, 0, InBuf.Length);
Console.WriteLine("ZIP: read for buffer length:" + InBuf.Length.ToString());
byte[] OutBuf = SevenZip.Compression.LZMA.SevenZipHelper.Compress(InBuf);
FileStream BZipFile = new FileStream(pZipFileName, FileMode.OpenOrCreate, FileAccess.Write);
BZipFile.Seek(0, SeekOrigin.Begin);
BZipFile.Write(OutBuf, 0, OutBuf.Length);
BZipFile.Close();
This creates a compressed file neatly, using the 7-zip algorithm. Problem is I can't guarantee that the clients using this program will have access to 7-zip, so the file has to be in normal zip algorithm. I've gone through the helper- as well as the 7-zip libraries and it seems it is possible to use this library to compress a file with the normal "ZIP" algorithm. I just cannot seem to figure out how to do this. I've noticed properties settings in a few places, but I cannot find any documentation or googling to tell me where to set this.
I realize there's probably better ways to do this and that I'm just missing something, but I can't sit and struggle with such a supposedly easy task forever. Any help would be greatly appreciated.
If you want you can take a look at this library, I've used it before and it's preaty simple to use : dotnetzip
EDIT(example):
using (ZipFile zip = new ZipFile())
{
foreach (String filename in FilesList)
{
Console.WriteLine("Adding {0}...", filename);
ZipEntry e = zip.AddFile(filename,"");
e.Comment = "file " +filename+" added "+DateTime.Now;
}
Console.WriteLine("Done adding files to zip:" + zipName);
zip.Comment = String.Format("This zip archive was created by '{0}' on '{1}'",
System.Net.Dns.GetHostName(), DateTime.Now);
zip.Save(zipName);
Console.WriteLine("Zip made:" + zipName);
}
I have a .tar file containing multiple compressed .gz files. I have no issue itterating through the .tar file creating each .gz file in a destination directory. I'd like to skip writting the .gz all together and just decompress it from the TarEntry/TarArchive? and write its contents on the fly via the .Net native GZipStream. Not even sure this is possible.
Here is my current code that writes each g'zipped file out. Not sure what to modify to get where I need to be.
using (FileStream _fsIn = new FileStream(#"F:\data\abc.tar", FileMode.Open, FileAccess.Read))
{
using (TarInputStream _tarIn = new TarInputStream(_fsIn))
{
TarEntry _tarEntry;
while ((_tarEntry = _tarIn.GetNextEntry()) != null)
{
string _archiveName = _tarEntry.Name;
using (FileStream _outStr = new FileStream(#"F:\data\" + _archiveName, FileMode.Create))
{
_tarIn.CopyEntryContents(_outStr);
}
}
}
}
I'am not sure what you want to do. Maybe you can clarify your aim. The sharpzlib is not that good documented as I Expected to be.
I've iterated through a tar archive and pushed the content of a file into a new Stream, maybe you can use this as a starting point. Have a look at this StackOverflow Article