Can someone tell me what's wrong with my code? I want to zip multiple xml into one file yet the result file is always empty.
using (MemoryStream zipStream = new MemoryStream())
{
using (ZipArchive zip = new ZipArchive(zipStream, ZipArchiveMode.Create, true))
{
string[] xmls = Directory.GetFiles(#"c:\temp\test", "*.xml");
foreach (string xml in xmls)
{
var file = zip.CreateEntry(xml);
using (var entryStream = file.Open())
using (var streamWriter = new StreamWriter(entryStream))
{
streamWriter.Write(xml);
}
}
}
using (FileStream fs = new FileStream(#"C:\Temp\test.zip", FileMode.Create))
{
zipStream.Position = 0;
zipStream.CopyTo(fs);
}
}
See the remarks in the documentation (emphasis mine):
The entryName string should reflect the relative path of the entry you want to create within the zip archive. There is no restriction on the string you provide. However, if it is not formatted as a relative path, the entry is created, but you may get an exception when you extract the contents of the zip archive. If an entry with the specified path and name already exists in the archive, a second entry is created with the same path and name.
You are using an absolute path here:
var file = zip.CreateEntry(xml);
My guess is that when you try to open the archive, it is failing silently to show the entries.
Change your code to use the names of the files without their path:
var file = zip.CreateEntry(Path.GetFileName(xml));
As a separate issue, notice that you're just writing the name of the file to the ZIP entry, rather than the actual file. I imagine you want something like this instead:
var zipEntry = zip.CreateEntry(Path.GetFileName(xml));
using (var entryStream = file.Open())
{
using var fileStream = File.OpenRead(xml);
fileStream.CopyTo(entryStream);
}
Related
I'm working on a project where I want to obtain the file from a compressed archive which only holds a single file. The files I'm interested in opening are of the file extension .als (Ableton Live Set) and from 7Zip I've found they use the gzip compression type.
I've found some code which let's me easily obtain the file from the archive:
using (ZipArchive archive = new ZipArchive(zipToOpen, ZipArchiveMode.Read))
{
var entryName = Path.GetFileNameWithoutExtension(txt_file_select.Text);
ZipArchiveEntry entry = archive.GetEntry(entryName);
using (StreamReader reader = new StreamReader(entry.Open()))
{
String fileContent = reader.ReadToEnd();
Debug.WriteLine(fileContent);
}
}
This code opens the file from the stream zipToOpen which has the same filepath as txt_file_select.Text. The single entry that I'm interested in seems to always have the same name as the zipped file itself so this should always give me an entry for any compressed archive that uses zip type. But this code does not accept gzip types in the current state as I get:
Exception thrown: 'System.IO.InvalidDataException' in System.IO.Compression.dll
An exception of type 'System.IO.InvalidDataException' occurred in System.IO.Compression.dll but was not handled in user code
End of Central Directory record could not be found.
I've found that you can open gzip type archives using the GZipStream class. so I added this line:
using (GZipStream zipToOpen = new GZipStream(new FileStream(txt_file_select.Text, FileMode.Open), CompressionMode.Decompress))
which still results in the same exception being thrown. I'm assuming the problem is in converting from GZipStream to ZipArchive.
Here is a simple reproducible example:
using System.Diagnostics;
using System.IO.Compression;
String filepath = #"path\to\als\file.als";
using (GZipStream zipToOpen = new GZipStream(new FileStream(filepath, FileMode.Open), CompressionMode.Decompress))
{
using (ZipArchive archive = new ZipArchive(zipToOpen, ZipArchiveMode.Read))
{
var entryName = Path.GetFileNameWithoutExtension(filepath);
ZipArchiveEntry entry = archive.GetEntry(entryName);
using (StreamReader reader = new StreamReader(entry.Open()))
{
String fileContent = reader.ReadToEnd();
Debug.WriteLine(fileContent);
}
}
}
How do I convert the GZipStream into a ZipArchive?
I am making a GET request using HttpClient to download a zip file from the internet.
I want to extract all the files contained in the zip file without saving the zip file to disk.
Currently, I am able to download and save the zip file to disk, extract its contents and then delete the zip file from disk. This perfectly fine. However, I want to optimize the process.
I found a way to extract the contents directly from the downloaded zip stream but I have to specify the filenames and extensions.
I am not sure how to extract the contents while preserving their original filenames and extensions without me specifying them.
Current Approach:
string requestUri = "https://www.nuget.org/api/v2/package/" + PackageName + "/" + PackageVersion;
HttpResponseMessage response = await client.GetAsync(requestUri);
response.EnsureSuccessStatusCode();
using Stream PackageStream = await response.Content.ReadAsStreamAsync();
SaveStream($"{DownloadPath}.zip", PackageStream);
ZipFile.ExtractToDirectory($"{DownloadPath}.zip", ExtractPath);
File.Delete($"{DownloadPath}.zip");
// Directly extract Zip contents without saving file and without losing filename and extension
using (ZipArchive archive = new ZipArchive(await response.Content.ReadAsStreamAsync()))
{
foreach (ZipArchiveEntry entry in archive.Entries)
{
using (Stream stream = entry.Open())
{
using (FileStream file = new FileStream("file.txt", FileMode.Create, FileAccess.Write))
{
stream.CopyTo(file);
}
}
}
}
.NET 4.8
.NET Core 3.1
C# 8.0
Any help in this regards would be appreciated.
Please feel free to comment on alternative approaches or suggestions.
Thank you in advance.
ZipArchiveEntry has a Name and FullName property that can be used to get the names of the files within the archive while preserving their original filenames and extensions
The FullName property contains the relative path, including the subdirectory hierarchy, of an entry in a zip archive. (In contrast, the Name property contains only the name of the entry and does not include the subdirectory hierarchy.)
For example
using (ZipArchive archive = new ZipArchive(await response.Content.ReadAsStreamAsync())) {
foreach (ZipArchiveEntry entry in archive.Entries) {
using (Stream stream = entry.Open()) {
string destination = Path.GetFullPath(Path.Combine(downloadPath, entry.FullName));
var directory = Path.GetDirectoryName(destination);
if (!Directory.Exists(directory))
Directory.CreateDirectory(directory);
using (FileStream file = new FileStream(destination, FileMode.Create, FileAccess.Write)) {
await stream.CopyToAsync(file);
}
}
}
}
will extract the files in the same subdirectory hierarchy as they were stored in the archive while if entry.Name was used, all the files would be extracted to the same location.
Using MailKit in .NET CORE an attachement can be loaded using:
bodyBuilder.Attachments.Add(FILE);
I'm trying to attach a file from inside a ZIP file using:
using System.IO.Compression;
string zipPath = #"./html-files.ZIP";
using (ZipArchive archive = ZipFile.OpenRead(zipPath))
{
// bodyBuilder.Attachments.Add("msg.html");
bodyBuilder.Attachments.Add(archive.GetEntry("msg.html"));
}
But it did not work, and gave me APP\"msg.html" not found, which means it is trying to load a file with the same name from the root directory instead of the zipped one.
bodyBuilder.Attachments.Add() doesn't have an overload that takes a ZipArchiveEntry, so using archive.GetEntry("msg.html") has no chance of working.
Most likely what is happening is that the compiler is casting the ZipArchiveEntry to a string which happens to be APP\"msg.html" which is why you get that error.
What you'll need to do is extract the content from the zip archive and then add that to the list of attachments.
using System.IO;
using System.IO.Compression;
string zipPath = #"./html-files.ZIP";
using (ZipArchive archive = ZipFile.OpenRead (zipPath)) {
ZipArchiveEntry entry = archive.GetEntry ("msg.html");
var stream = new MemoryStream ();
// extract the content from the zip archive entry
using (var content = entry.Open ())
content.CopyTo (stream);
// rewind the stream
stream.Position = 0;
bodyBuilder.Attachments.Add ("msg.html", stream);
}
Is it somehow possible to create a ZipArchive from the file(s) in memory (and not actually on the disk).
Following is the use case:
Multiple files are received in an IEnumerable<HttpPostedFileBase> variable. I want to zip all these files together using ZipArchive. The problem is that ZipArchive only allows CreateEntryFromFile, which expects a path to the file, where as I just have the files in memory.
Question:
Is there a way to use a 'stream' to create the 'entry' in ZipArchive, so that I can directly put in the file's contents in the zip?
I don't want to first save the files, create the zip (from the saved files' paths) and then delete the individual files.
Here, attachmentFiles is IEnumerable<HttpPostedFileBase>
using (var ms = new MemoryStream())
{
using (var zipArchive = new ZipArchive(ms, ZipArchiveMode.Create, true))
{
foreach (var attachment in attachmentFiles)
{
zipArchive.CreateEntryFromFile(Path.GetFullPath(attachment.FileName), Path.GetFileName(attachment.FileName),
CompressionLevel.Fastest);
}
}
...
}
Yes, you can do this, using the ZipArchive.CreateEntry method, as #AngeloReis pointed out in the comments, and described here for a slightly different problem.
Your code would then look like this:
using (var ms = new MemoryStream())
{
using (var zipArchive = new ZipArchive(ms, ZipArchiveMode.Create, true))
{
foreach (var attachment in attachmentFiles)
{
var entry = zipArchive.CreateEntry(attachment.FileName, CompressionLevel.Fastest);
using (var entryStream = entry.Open())
{
attachment.InputStream.CopyTo(entryStream);
}
}
}
...
}
First off thanks #Alex for the perfect answer.
Also for the scenario you need to read from file systems :
using (var ms = new MemoryStream())
{
using (var zipArchive = new ZipArchive(ms, ZipArchiveMode.Create, true))
{
foreach (var file in filesAddress)
{
zipArchive.CreateEntryFromFile(file, Path.GetFileName(file));
}
}
...
}
with the help of System.IO.Compression.ZipFileExtensions
I am trying to .tar.gz a list of files from a folder using SharpZipLib. The problem is no matter how pass the files paths - the result always contains the files paths - and not only the files thems selves. What am i missing here?
string filesFolder = "c:\\testfolder\\test\\";
List<string> filesToZip = new List<string>() { filesFolder +"test1", filesFolder +"test2"};
using (FileStream fs = new FileStream(filesFolder +"myGz.tar.gz" , FileMode.Create, FileAccess.Write, FileShare.None))
using (Stream gzipStream = new GZipOutputStream(fs))
using (TarArchive tarArchive = TarArchive.CreateOutputTarArchive(gzipStream))
{
foreach (string filename in filesToZip )
{
{
TarEntry tarEntry = TarEntry.CreateEntryFromFile(filename);
tarArchive.WriteEntry(tarEntry, false);
}
}
}
what i get is a "myGz.tar.gz" files. When i try to open it with 7.zip - i get the full folders structure in the archive - c:\testfolder\test\, and in it - "test1", "test".
How do i remove the files paths?
Thanks
I had the same problem, and I figured it out just after finding this question.
The key is to set the Name property of the tarEntry, before adding it into the archive.
TarEntry tarEntry = TarEntry.CreateEntryFromFile(filename);
tarEntry.Name = Path.GetFileName(filename);
tarArchive.WriteEntry(tarEntry, false);