How to compress both files and folders with ZipFile API - c#

I want to compress Zip to be same structure with folder.
but, ZipFile API seems unable to compress folder.
How to compress these folder structure?
The following code can't compress folder itself.
using (ZipArchive archive = ZipFile.Open(Path.Combine(m_strWorkingDirectory, "build.zip"), ZipArchiveMode.Create))
{
foreach( string path in m_listTargetPath )
{
string strPath = Path.Combine(m_strWorkingDirectory, path);
archive.CreateEntryFromFile(strPath, path);
}
}

If ZipFile.CreateFromDirectory() doesn't do what you need (I thought it would) then you can just get all the files/folders you need and add them in using an extension method:
public static class FileExtensions
{
public static IEnumerable<FileSystemInfo> AllFilesAndFolders(this DirectoryInfo dir)
{
foreach (var f in dir.GetFiles())
{
yield return f;
}
foreach (var d in dir.GetDirectories())
{
yield return d;
foreach (var o in AllFilesAndFolders(d))
{
yield return o;
}
}
}
}
And then using your same format, you should be able to do something like so (not tested):
DirectoryInfo dir = new DirectoryInfo(m_strWorkingDirectory);
using (ZipArchive archive = ZipFile.Open(Path.Combine(m_strWorkingDirectory, "build.zip"), ZipArchiveMode.Create))
{
foreach (FileInfo file in dir.AllFilesAndFolders().Where(o => o is FileInfo).Cast<FileInfo>())
{
var relPath = file.FullName.Substring(dir.FullName.Length + 1);
archive.CreateEntryFromFile(file.FullName, relPath);
}
}

Related

C# Get top level files from zip file only

I am trying to get all the file names that are located in the top level of a zip file and not anything in subdirectories.
The code I'm currently using is
using System.IO.Compression.ZipFile;
using (var zip = ZipFile.OpenRead(pathToZip))
{
foreach (var e in zip.Entries)
{
var filename = e.Name;
}
}
But this code gets all the files in the zip. Any help is much apricated.
Thanks
This code will extract only files that are not contained in a directory:
using (var zip = ZipFile.OpenRead(pathToZip))
{
foreach (var e in zip.Entries.Where(e => !e.FullName.Contains("/")))
{
{
var filename = e.Name;
Console.WriteLine(filename);
}
}
}

Create UDF ISO file with .NET DiscUtils

I have Bluray folder structure
- BMDV
- ANY!
- FAB!
...
I use library https://github.com/DiscUtils/DiscUtils and I tried:
CDBuilder builder = new CDBuilder();
builder.UseJoliet = false;
AddDir("paths", builder, "paths");
builder.Build("stuff.iso");
static void AddDir(string dirPath, CDBuilder builder, string originalPath)
{
var files = Directory.GetFiles(dirPath);
var dirs = Directory.GetDirectories(dirPath);
var pathDiff = string.Join(separator, dirPath.Split(separator).Except(originalPath.Split(separator)));
if (!string.IsNullOrWhiteSpace(pathDiff))
{
Console.WriteLine($"Adding directory {pathDiff}");
builder.AddDirectory(pathDiff);
}
foreach (var file in files)
{
string location = string.IsNullOrWhiteSpace(pathDiff) ?
Path.GetFileName(file) :
$"{pathDiff}{separator}{Path.GetFileName(file)}";
Console.WriteLine($"Adding file: {location}");
builder.AddFile(location, file);
}
foreach (var dir in dirs)
{
AddDir(dir, builder, originalPath);
}
}
When open ISO with BDInfo (https://www.videohelp.com/software/BDInfo), it says that is not valid UDF stream.
How to create UDF ISO file ?

Delete TMP file in a local folder

I have these files in my local folder.
As you can see from the image above, there is a TMP file in it. I don't know how it is generated but I believe it is a useless temp file, so I try to delete it this way:
foreach (var item in await ApplicationData.Current.LocalFolder.GetFilesAsync())
if (item.Name.EndsWith(".TMP"))
await item.DeleteAsync();
However, using neither GetFilesAsync() nor GetItemsAsync() finds the temp file. The former gives me the json files only and the latter finds everything but the tmp file.
How should I find and delete it?
You could use the following code to detect the hidden file where in the sandbox(It only works in sandbox).
public static class ParseDir
{
public static FileInfo[] GetFilesFromDirectory(string DirName, string pattern, bool Recursive)
{
if (!Directory.Exists(DirName))
throw new Exception("No such Directory.");
DirectoryInfo dirInfo = new DirectoryInfo(DirName);
SearchOption Recur = Recursive ? SearchOption.AllDirectories : SearchOption.TopDirectoryOnly;
return dirInfo.GetFiles(pattern, Recur);
}
public static FileInfo[] GetHiddenOnlyFiles(FileInfo[] Files)
{
List<FileInfo> result = new List<FileInfo>();
foreach (FileInfo file in Files)
if ((file.Attributes & System.IO.FileAttributes.Hidden) == System.IO.FileAttributes.Hidden)
result.Add(file);
return result.ToArray();
}
}
Usage
FileInfo[] filesInS = ParseDir.GetFilesFromDirectory(ApplicationData.Current.LocalFolder.Path, "*.*", false);
FileInfo[] hiddenFiles = ParseDir.GetHiddenOnlyFiles(filesInS);
hiddenFiles.First().Delete();

Creating Directories in a ZipArchive C# .Net 4.5

A ZipArchive is a collection of ZipArchiveEntries, and adding/removing "Entries" works nicely.
But it appears there is no notion of directories / nested "Archives". In theory, the class is decoupled from a file system, in that you can create the archive completely in a memory stream. But if you wish to add a directory structure within the archive, you must prefix the entry name with a path.
Question: How would you go about extending ZipArchive to create a better interface for creating and managing directories?
For example, the current method of adding a file to a directory is to create the entry with the directory path:
var entry = _archive.CreateEntry("directory/entryname");
whereas something along these lines seems nicer to me:
var directory = _archive.CreateDirectoryEntry("directory");
var entry = _directory.CreateEntry("entryname");
You can use something like the following, in other words, create the directory structure by hand:
using (var fs = new FileStream("1.zip", FileMode.Create))
using (var zip = new ZipArchive(fs, ZipArchiveMode.Create))
{
zip.CreateEntry("12/3/"); // just end with "/"
}
I know I'm late to the party (7.25.2018),
this works flawlessly to me, even with recursive directories.
Firstly, remember to install the NuGet package:
Install-Package System.IO.Compression
And then, Extension file for ZipArchive:
public static class ZipArchiveExtension {
public static void CreateEntryFromAny(this ZipArchive archive, string sourceName, string entryName = "") {
var fileName = Path.GetFileName(sourceName);
if (File.GetAttributes(sourceName).HasFlag(FileAttributes.Directory)) {
archive.CreateEntryFromDirectory(sourceName, Path.Combine(entryName, fileName));
} else {
archive.CreateEntryFromFile(sourceName, Path.Combine(entryName, fileName), CompressionLevel.Fastest);
}
}
public static void CreateEntryFromDirectory(this ZipArchive archive, string sourceDirName, string entryName = "") {
string[] files = Directory.GetFiles(sourceDirName).Concat(Directory.GetDirectories(sourceDirName)).ToArray();
archive.CreateEntry(Path.Combine(entryName, Path.GetFileName(sourceDirName)));
foreach (var file in files) {
archive.CreateEntryFromAny(file, entryName);
}
}
}
And then you can pack anything, whether it is file or directory:
using (var memoryStream = new MemoryStream()) {
using (var archive = new ZipArchive(memoryStream, ZipArchiveMode.Create, true)) {
archive.CreateEntryFromAny(sourcePath);
}
}
If you are working on a project that can use full .NET you may try to use
the ZipFile.CreateFromDirectory method, as explained here:
using System;
using System.IO;
using System.IO.Compression;
namespace ConsoleApplication
{
class Program
{
static void Main(string[] args)
{
string startPath = #"c:\example\start";
string zipPath = #"c:\example\result.zip";
string extractPath = #"c:\example\extract";
ZipFile.CreateFromDirectory(startPath, zipPath, CompressionLevel.Fastest, true);
ZipFile.ExtractToDirectory(zipPath, extractPath);
}
}
}
Of course this will only work if you are creating new Zips based on a given directory.
As per the comment, the previous solution does not preserve the directory structure. If that is needed, then the following code might address that:
var InputDirectory = #"c:\example\start";
var OutputFilename = #"c:\example\result.zip";
using (Stream zipStream = new FileStream(Path.GetFullPath(OutputFilename), FileMode.Create, FileAccess.Write))
using (ZipArchive archive = new ZipArchive(zipStream, ZipArchiveMode.Create))
{
foreach(var filePath in System.IO.Directory.GetFiles(InputDirectory,"*.*",System.IO.SearchOption.AllDirectories))
{
var relativePath = filePath.Replace(InputDirectory,string.Empty);
using (Stream fileStream = new FileStream(filePath, FileMode.Open, FileAccess.Read))
using (Stream fileStreamInZip = archive.CreateEntry(relativePath).Open())
fileStream.CopyTo(fileStreamInZip);
}
}
Here is one possible solution:
public static class ZipArchiveExtension
{
public static ZipArchiveDirectory CreateDirectory(this ZipArchive #this, string directoryPath)
{
return new ZipArchiveDirectory(#this, directoryPath);
}
}
public class ZipArchiveDirectory
{
private readonly string _directory;
private ZipArchive _archive;
internal ZipArchiveDirectory(ZipArchive archive, string directory)
{
_archive = archive;
_directory = directory;
}
public ZipArchive Archive { get{return _archive;}}
public ZipArchiveEntry CreateEntry(string entry)
{
return _archive.CreateEntry(_directory + "/" + entry);
}
public ZipArchiveEntry CreateEntry(string entry, CompressionLevel compressionLevel)
{
return _archive.CreateEntry(_directory + "/" + entry, compressionLevel);
}
}
and used:
var directory = _archive.CreateDirectory(context);
var entry = directory.CreateEntry(context);
var stream = entry.Open();
but I can foresee problems with nesting, perhaps.
I was also looking for a similar solution and found #Val & #sDima's solution more promising to me. But I found some issues with the code and fixed them to use with my code.
Like #sDima, I also decided to use Extension to add more functionality to ZipArchive.
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using System.IO.Compression;
using System.IO;
namespace ZipTest
{
public static class ZipArchiveExtensions
{
public static void CreateEntryFromAny(this ZipArchive archive, string sourceName, string entryName, CompressionLevel compressionLevel = CompressionLevel.Optimal)
{
try
{
if (File.GetAttributes(sourceName).HasFlag(FileAttributes.Directory))
{
archive.CreateEntryFromDirectory(sourceName, entryName, compressionLevel);
}
else
{
archive.CreateEntryFromFile(sourceName, entryName, compressionLevel);
}
}
catch
{
throw;
}
}
public static void CreateEntryFromDirectory(this ZipArchive archive, string sourceDirName, string entryName, CompressionLevel compressionLevel)
{
try
{
var files = Directory.EnumerateFileSystemEntries(sourceDirName);
if (files.Any())
{
foreach (var file in files)
{
var fileName = Path.GetFileName(file);
archive.CreateEntryFromAny(file, Path.Combine(entryName, fileName), compressionLevel);
}
}
else
{
//Do a folder entry check.
if (!string.IsNullOrEmpty(entryName) && entryName[entryName.Length - 1] != '/')
{
entryName += "/";
}
archive.CreateEntry(entryName, compressionLevel);
}
}
catch
{
throw;
}
}
}
}
You can try the extension using the simple given below,
class Program
{
static void Main(string[] args)
{
string filePath = #"C:\Users\WinUser\Downloads\Test.zip";
string dirName = Path.GetDirectoryName(filePath);
if (File.Exists(filePath))
File.Delete(filePath);
using (ZipArchive archive = ZipFile.Open(filePath, ZipArchiveMode.Create))
{
archive.CreateEntryFromFile( #"C:\Users\WinUser\Downloads\file1.jpg", "SomeFolder/file1.jpg", CompressionLevel.Optimal);
archive.CreateEntryFromDirectory(#"C:\Users\WinUser\Downloads\MyDocs", "OfficeDocs", CompressionLevel.Optimal);
archive.CreateEntryFromAny(#"C:\Users\WinUser\Downloads\EmptyFolder", "EmptyFolder", CompressionLevel.Optimal);
};
using (ZipArchive zip = ZipFile.OpenRead(filePath))
{
string dirExtract = #"C:\Users\WinUser\Downloads\Temp";
if (Directory.Exists(dirExtract))
{
Directory.Delete(dirExtract, true);
}
zip.ExtractToDirectory(dirExtract);
}
}
}
I tried to keep the exact behavior of standard CreateEntryFromFilefrom the .Net Framework for extension methods.
To use the sample code given, add a reference to System.IO.Compression.dll and System.IO.Compression.FileSystem.dll.
Following are the advantages of this extension class
Recursive adding of folder content.
Support for an empty folder.
My answer is based on the Val's answer, but a little improved for performance and without producing empty files in ZIP.
public static class ZipArchiveExtensions
{
public static void CreateEntryFromAny(this ZipArchive archive, string sourceName, string entryName = "")
{
var fileName = Path.GetFileName(sourceName);
if (File.GetAttributes(sourceName).HasFlag(FileAttributes.Directory))
{
archive.CreateEntryFromDirectory(sourceName, Path.Combine(entryName, fileName));
}
else
{
archive.CreateEntryFromFile(sourceName, Path.Combine(entryName, fileName), CompressionLevel.Optimal);
}
}
public static void CreateEntryFromDirectory(this ZipArchive archive, string sourceDirName, string entryName = "")
{
var files = Directory.EnumerateFileSystemEntries(sourceDirName);
foreach (var file in files)
{
archive.CreateEntryFromAny(file, entryName);
}
}
}
Example of using:
// Create and open a new ZIP file
using (var zip = ZipFile.Open(ZipPath, ZipArchiveMode.Create))
{
foreach (string file in FILES_LIST)
{
// Add the entry for each file
zip.CreateEntryFromAny(file);
}
}
One more tuned ZipArchive extension, which adds folder structure including all subfolders and files to zip. Solved IOException (Process can't access the file...) which is thrown if files are in use at zipping moment, for example by some logger
public static class ZipArchiveExtensions
{
public static void AddDirectory(this ZipArchive #this, string path)
{
#this.AddDirectory(path, string.Empty);
}
private static void AddDirectory(this ZipArchive #this, string path, string relativePath)
{
var fileSystemEntries = Directory.EnumerateFileSystemEntries(path);
foreach (var fileSystemEntry in fileSystemEntries)
{
if (File.GetAttributes(fileSystemEntry).HasFlag(FileAttributes.Directory))
{
#this.AddDirectory(fileSystemEntry, Path.Combine(relativePath, Path.GetFileName(fileSystemEntry)));
continue;
}
var fileEntry = #this.CreateEntry(Path.Combine(relativePath, Path.GetFileName(fileSystemEntry)));
using (var zipStream = fileEntry.Open())
using (var fileStream = new FileStream(fileSystemEntry, FileMode.Open, FileAccess.Read, FileShare.ReadWrite))
using (var memoryStream = new MemoryStream())
{
fileStream.CopyTo(memoryStream);
var bytes = memoryStream.ToArray();
zipStream.Write(bytes, 0, bytes.Length);
}
}
}
}
A little change in the very good approach from #Andrey
public static void CreateEntryFromDirectory2(this ZipArchive archive, string sourceDirName, CompressionLevel compressionLevel = CompressionLevel.Fastest)
{
var folders = new Stack<string>();
folders.Push(sourceDirName);
do
{
var currentFolder = folders.Pop();
foreach (var item in Directory.GetFiles(currentFolder))
{
archive.CreateEntryFromFile(item, item.Substring(sourceDirName.Length + 1), compressionLevel);
}
foreach (var item in Directory.GetDirectories(currentFolder))
{
folders.Push(item);
}
}
while (folders.Count > 0);
}
Use the recursive approach to Zip Folders with Subfolders.
using System;
using System.Collections.Generic;
using System.IO;
using System.IO.Compression;
public static async Task<bool> ZipFileHelper(IFolder folderForZipping, IFolder folderForZipFile, string zipFileName)
{
if (folderForZipping == null || folderForZipFile == null
|| string.IsNullOrEmpty(zipFileName))
{
throw new ArgumentException("Invalid argument...");
}
IFile zipFile = await folderForZipFile.CreateFileAsync(zipFileName, CreationCollisionOption.ReplaceExisting);
// Create zip archive to access compressed files in memory stream
using (MemoryStream zipStream = new MemoryStream())
{
using (ZipArchive zip = new ZipArchive(zipStream, ZipArchiveMode.Create, true))
{
await ZipSubFolders(folderForZipping, zip, "");
}
zipStream.Position = 0;
using (Stream s = await zipFile.OpenAsync(FileAccess.ReadAndWrite))
{
zipStream.CopyTo(s);
}
}
return true;
}
//Create zip file entry for folder and subfolders("sub/1.txt")
private static async Task ZipSubFolders(IFolder folder, ZipArchive zip, string dir)
{
if (folder == null || zip == null)
return;
var files = await folder.GetFilesAsync();
var en = files.GetEnumerator();
while (en.MoveNext())
{
var file = en.Current;
var entry = zip.CreateEntryFromFile(file.Path, dir + file.Name);
}
var folders = await folder.GetFoldersAsync();
var fEn = folders.GetEnumerator();
while (fEn.MoveNext())
{
await ZipSubFolders(fEn.Current, zip, dir + fEn.Current.Name + "/");
}
}
I don't like recursion as it was proposed by #Val, #sDima, #Nitheesh. Potentially it leads to the StackOverflowException because the stack has limited size. So here is my two cents with tree traversal.
public static class ZipArchiveExtensions
{
public static void CreateEntryFromDirectory(this ZipArchive archive, string sourceDirName, CompressionLevel compressionLevel = CompressionLevel.Fastest)
{
var folders = new Stack<string>();
folders.Push(sourceDirName);
do
{
var currentFolder = folders.Pop();
Directory.GetFiles(currentFolder).ForEach(f => archive.CreateEntryFromFile(f, f.Substring(sourceDirName.Length+1), compressionLevel));
Directory.GetDirectories(currentFolder).ForEach(d => folders.Push(d));
} while (folders.Count > 0);
}
}
It's working for me.
Static class
public static class ZipArchiveExtension
{
public static void CreateEntryFromAny(this ZipArchive archive, string sourceName, string entryName = "")
{
var fileName = Path.GetFileName(sourceName);
if (File.GetAttributes(sourceName).HasFlag(FileAttributes.Directory))
{
archive.CreateEntryFromDirectory(sourceName, Path.Combine(entryName, fileName));
}
else
{
archive.CreateEntryFromFile(sourceName, Path.Combine(entryName, fileName), CompressionLevel.Fastest);
}
}
public static void CreateEntryFromDirectory(this ZipArchive archive, string sourceDirName, string entryName = "")
{
string[] files = Directory.GetFiles(sourceDirName).Concat(Directory.GetDirectories(sourceDirName)).ToArray();
if (files.Any())
{
foreach (var file in files)
{
archive.CreateEntryFromAny(file, entryName);
}
}
else
{
if (!string.IsNullOrEmpty(entryName) && entryName[entryName.Length - 1] != '/')
{
entryName += "\\";
}
archive.CreateEntry(entryName);
}
}
}
Calling the method like that
byte[] archiveFile;
using (var memoryStream = new MemoryStream())
{
using (var archive = new ZipArchive(memoryStream, ZipArchiveMode.Create, true))
{
archive.CreateEntryFromAny(file.Path);
}
archiveFile = memoryStream.ToArray();
}

Unzip one File from Directory

Everything is working fine.. I can Unzip files, from an Zip/Rar .. Archive.
The Problem is, how to Unzip a file, thats in a Directory?
To Unzip a File directly I use (SharpZipLib):
FastZip fastZip = new FastZip();
fastZip.ExtractZip(source, targetDirectory, null);
using (var fs = new FileStream(source, FileMode.Open, FileAccess.Read))
using (var zf = new ZipFile(fs))
{
var ze = zf.GetEntry("toc.out");
if (ze == null)
{
throw new ArgumentException("toc.out", "not found in Zip");
}
using (var s = zf.GetInputStream(ze))
{
// do something with ZipInputStream
}
}
}
Or with DotNetZip/ZipDotNet:
using (ZipFile zip = ZipFile.Read(source))
{
ZipEntry e = zip["toc.out"];
e.Extract();
}
Thats not working, cause hes searching the file in the root..
And I also wont do something like: DirectoryName/toc.out
How can I achieve this`? Isn't there a parameter, where I can include all subfolders - for searching or something similar? :(
You can write a LINQ expression to find the file in sub folders as shown below
DirectoryInfo dirInfo = new DirectoryInfo(#"C:\");
foreach (var file in dirs.Select(dir => dir.EnumerateFiles().Where(i => i.Name.ToLower() == "wsdl.zip").FirstOrDefault()).Where(file => file != null))
{
Console.WriteLine(file.ToString());
Console.WriteLine(file.Length);
}
The above code searches all subfolder under C drive for the file wsdl.zip and prints its name and length to the console.
Hope that helps.
You can check the last part of the Name of the entry. Even if the file is in a subfolder, the Name entry would be something like "Folder/file.ext".
An extension method to accomplish this would be like:
public static ZipEntry GetEntryExt(this ZipFile file, string fileName)
{
foreach (ZipEntry entry in file)
{
if (entry.IsFile && entry.Name.EndsWith(Path.DirectorySeparatorChar + fileName))
return entry;
}
return null;
}

Categories

Resources