I'm trying to write a simple Windows Console Application which does the following:
Create a MD5 hash for each file in a given folder and write the filename including the file's directories and the generated hash in a textfile.
I am able to create the hashes and write the filename with the hash into a textfile.
The part which i can't solve is the thing where I write the filename with its directory in front of it.
An example:
my root directory is C:\temp
In C:\tempI have 3 files:
a.txt, b.txt and c.txt.
Furthermore there are 2 folders inside C:\temp:
001and 002
001 contains 3 other files and 002 contains some files and another directory.
Roughly said what I want my output to be is:
a.txt 1B2M2Y8AsgTpgAmY7PhCfg2
b.txt 8pilZcutPpW5x6atctbWWQ2
c.txt 7wGB3hjvOVGAbArWO4l7pA2
001\d.txt _Uqz2gB17nv1Vxrh-MPrCw2
001\e.txt 1B2M2Y8AsgTpgAmY7PhCfg2
001\f.txt _Uqz2gB17nv1Vxrh-MPrCw2
002\g.txt 1B2M2Y8AsgTpgAmY7PhCfg2
002\h.txt 7wGB3hjvOVGAbArWO4l7pA2
002\test\i.txt _Uqz2gB17nv1Vxrh-MPrCw2
002\test\j.txt 7wGB3hjvOVGAbArWO4l7pA2
My current output is
a.txt 1B2M2Y8AsgTpgAmY7PhCfg2
b.txt 8pilZcutPpW5x6atctbWWQ2
c.txt 7wGB3hjvOVGAbArWO4l7pA2
d.txt _Uqz2gB17nv1Vxrh-MPrCw2
e.txt 1B2M2Y8AsgTpgAmY7PhCfg2
f.txt _Uqz2gB17nv1Vxrh-MPrCw2
g.txt 1B2M2Y8AsgTpgAmY7PhCfg2
h.txt 7wGB3hjvOVGAbArWO4l7pA2
i.txt _Uqz2gB17nv1Vxrh-MPrCw2
j.txt 7wGB3hjvOVGAbArWO4l7pA2
This is my current code. I am thankful for any advice or hints to make my code better.
using System;
using System.Linq;
using System.Security.Cryptography;
using System.IO;
using System.Web;
namespace MD5_Generator
{
class Program
{
static void Main(string[] args)
{
Console.WriteLine("MD5 Hash Generator");
Console.WriteLine("This program creates MD5 hashes for all files in the folder.");
Console.WriteLine("Work in progress...");
string root = Directory.GetCurrentDirectory();
string hashList = root + "/hashList.txt";
if (!File.Exists(hashList))
{
var initHastListFile = File.Create(hashList);
initHastListFile.Close();
}
else
{
File.Delete(hashList);
var hastListFile = File.Create(hashList);
hastListFile.Close();
}
int i = 0;
string[] allFiles = Directory.GetFiles(root, "*.*", SearchOption.AllDirectories);
string[] lines = new string[allFiles.Count()];
lines = DirSearch(root, lines, i);
File.AppendAllLines(hashList, lines);
Console.ReadKey();
}
static string[] DirSearch(string dir, string[] lines, int counter)
{
string hashListFileName = "hashList.txt";
foreach (string f in Directory.GetFiles(dir))
{
//2. Create an MD5 hash per file
using (var md5 = MD5.Create())
{
FileInfo info = new FileInfo(f);
string filename = info.FullName;
if (filename != hashListFileName)
{
using (var stream = File.OpenRead(filename))
{
byte[] fileMD5 = md5.ComputeHash(stream);
string hash = HttpServerUtility.UrlTokenEncode(fileMD5);
string currDir = Path.GetDirectoryName(filename);
lines[counter] = info.Name + " " + hash;
}
counter++;
}
}
}
foreach (string d in Directory.GetDirectories(dir))
{
DirSearch(d, lines, counter);
}
return lines;
}
}
}
Something like this i guess
Method
public static IEnumerable<(string fileName, string hash)> GetHasList(string path, bool isRelative)
{
foreach (var file in Directory.GetFiles(path, "*.*", SearchOption.AllDirectories))
{
string hash;
using (var md5 = MD5.Create())
using (var stream = File.OpenRead(file))
hash = HttpServerUtility.UrlTokenEncode(md5.ComputeHash(stream));
if (isRelative)
yield return (file.Remove(0, path.TrimEnd('/').Length+1), hash);
else
yield return (file, hash);
}
}
Usage
string output = Path.Combine(#"D:\", "hashList.txt");
// format results
var data = GetHasList(#"D:\Temp",true).Select(x => $"{x.fileName} {x.hash}");
// write file
File.WriteAllLines(output, data);
If you want to make it a bit more fault tolerant of spaces
// format results
var data = GetHasList(#"D:\Temp",true).Select(x => $"\"{x.fileName}\" {x.hash}");
Output
"2284804723016.xml" UBtEG5qItCVKf8VTdamoCQ2
"2301708833016.xml" vRiXj012Q9RlU9xEgZPjcA2
"New folder\2320158695015.xml" hpDYqQuy_wvQMD5tOMJxjA2
"New folder\2282121972016.xml" j-Y06SdEM3kHjbhTIqhTKg2
"New folder (2)\2281419740016.xml" 3GWrCgtrda-W4ymCNSi4MA2
"New folder (2)\2281593123016.xml" ncqGPehpHflpzjl0j0nFfQ2
I now did it with the following code:
using System;
using System.Linq;
using System.Security.Cryptography;
using System.IO;
using System.Web;
namespace MD5_Generator
{
class Program
{
static void Main(string[] args)
{
Console.WriteLine("MD5 Hash Generator");
Console.WriteLine("This program creates MD5 hashes for all files in the folder.");
Console.WriteLine("Work in progress...");
string root = Directory.GetCurrentDirectory();
string hashList = root + "/hashList.txt";
if (!File.Exists(hashList))
{
var initHastListFile = File.Create(hashList);
initHastListFile.Close();
}
else
{
File.Delete(hashList);
var hastListFile = File.Create(hashList);
hastListFile.Close();
}
int i = 0;
string[] allFiles = Directory.GetFiles(root, "*.*", SearchOption.AllDirectories);
string[] lines = new string[allFiles.Count()];
lines = DirSearch(root, lines, i, root);
File.AppendAllLines(hashList, lines);
Console.ReadKey();
}
static string[] DirSearch(string dir, string[] lines, int counter, string root)
{
string hashListFileName = "hashList.txt";
foreach (string f in Directory.GetFiles(dir))
{
//2. Create an MD5 hash per file
using (var md5 = MD5.Create())
{
FileInfo info = new FileInfo(f);
string filename = info.FullName;
if (filename != hashListFileName)
{
using (var stream = File.OpenRead(filename))
{
byte[] fileMD5 = md5.ComputeHash(stream);
string hash = HttpServerUtility.UrlTokenEncode(fileMD5);
string currDir = Path.GetDirectoryName(filename);
lines[counter] = info.FullName.Substring(root.Length+1) + " " + hash;
}
counter++;
}
}
}
foreach (string d in Directory.GetDirectories(dir))
{
DirSearch(d, lines, counter, root);
}
return lines;
}
}
}
I just used the Substring Method to 'cut off' the not relevant parts where root is Directory.GetCurrentDirectory(); :
lines[counter] = info.FullName.Substring(root.Length+1) + " " + hash;
The result looks exactly like I want it to be!
MD5_Generator.exe NVt6BVhZyy0QQPA-Cumntg2
MD5_Generator.exe.config 7wGB3hjvOVGAbArWO4l7pA2
MD5_Generator.pdb _M_l1ka8Jg_c0LOtbXfF6g2
System.ValueTuple.dll mc7Hfb7gqxC5_E1SodQUvg2
System.ValueTuple.xml So9YKXUqAxilrTjfmxgVPQ2
Test\123.txt 1B2M2Y8AsgTpgAmY7PhCfg2
Test\Test2\dhf9.txt 1B2M2Y8AsgTpgAmY7PhCfg2
Thank you for contributing!
I have a small application that checks all of the logs in a directory named after domain usernames and generates a results file with each username and the relevant first and surname for that user.
The console is outputting the full list successfully but it seems that the StreamWriter is stopping halfway through an entry.
The number of characters it writes before stopping is consistent - to some extent. If I set the outputstring to include more characters between the two variables filename and the result from FindNameFromUsername then the character count with or without spaces changes so I've ruled that out.
Any ideas as to why the console outputs the line but the streamwriter doesn't?
Code below:
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.IO;
using System.Threading.Tasks;
using System.DirectoryServices;
using System.DirectoryServices.AccountManagement;
using System.DirectoryServices.ActiveDirectory;
namespace Filename_Finder
{
class Program
{
static void Main(string[] args)
{
Console.WriteLine("Please insert the full directory path. All filenames in the root of this folder will be logged to one file.");
string outputFilename = "results.txt";
string userPath = Console.ReadLine();
string domain = "ou=users,dc=domain,dc=local";
try
{
string outputFilepath = userPath + outputFilename;
string[] filepaths = Directory.GetFiles(userPath);
using (StreamWriter file = new StreamWriter(outputFilepath, false))
{
for (int i = 0; i < filepaths.Length; i++)
{
string filepath = filepaths[i];
char split = '.';
string filename = filepath.Remove(0, userPath.Count());
if (filename != outputFilename)
{
int extensionBegins = filename.LastIndexOf(split);
filename = filename.Remove(extensionBegins);
string outputstring = (filename + " -- " + FindNameFromUsername(filename, domain));
Console.WriteLine(outputstring);
file.WriteLine(outputstring);
}
}
Console.ReadLine();
}
}
catch (Exception e) { Console.WriteLine(e); Console.ReadLine(); }
}
public static string FindNameFromUsername(string username, string domainScope)
{
string connectionPrefix = "LDAP://" + domainScope;
DirectoryEntry entry = new DirectoryEntry(connectionPrefix);
DirectorySearcher searcher = new DirectorySearcher(entry);
string filter = "(&(objectClass=user)";
filter += "(|(sAMAccountName=" + username + ")))";
searcher.Filter = filter;
SearchResult result = searcher.FindOne();
string resultValue = string.Empty;
DirectoryEntry obj = result.GetDirectoryEntry();
resultValue = "" + obj.Properties["givenName"].Value + " " + obj.Properties["sn"].Value;
if (resultValue == " ") { resultValue = username; }
entry.Close(); entry.Dispose();
obj.Close(); obj.Dispose();
searcher.Dispose();
return resultValue;
}
}
}
Your Console.ReadLine() will pause the execution and I'm guessing that's where you are checking the content of your file.
However, the file won't be flushed and closed until the StreamWriter is being disposed. That happens at the end of the using block, i.e. after your ReadLine() statement.
Take a look at this question.
You might find useful setting your StreamWriter.AutoFlush property to true in order to get similar behavior to the corresponding Console method, like this:
using (var file = new StreamWriter(outputFilepath, false) { AutoFlush = true })
i want to save zip file to server for more than one file only.
first it should create zip file and then upload it to folder
if (FileUpload1.HasFile)
{
string fileName = Path.GetFileName(FileUpload1.PostedFile.FileName);
string fileLocation = Server.MapPath("~/uploads/" + fileName);
FileUpload1.SaveAs(fileLocation);
ZipFile createZipFile = new ZipFile();
createZipFile.AddFile(fileLocation, string.Empty);
createZipFile.Save(Server.MapPath("~/uploads/CsharpAspNetArticles.zip"));
}
If I understood your "noquestion" question :) You should use IHttpHandler like this:
using System;
using System.IO;
using System.Web;
using ICSharpCode.SharpZipLib.Zip;
public class ZipHandler : IHttpHandler
{
public bool IsReusable
{
get { return true; }
}
public void ProcessRequest(HttpContext ctx)
{
var path = HttpUtility.UrlDecode(ctx.Request.QueryString["folder"]);
if (path == null)
{
return;
}
var folderName = Path.GetFileName(path);
if (folderName == String.Empty)
{
folderName = "root";
}
int folderOffset = ctx.Server.MapPath("~").Length;
using (var zipStream = new ZipOutputStream(ctx.Response.OutputStream))
{
ctx.Response.Clear();
ctx.Response.BufferOutput = false;
ctx.Response.AddHeader("Content-Disposition", "attachment; filename=" + folderName + ".zip");
ctx.Response.AddHeader("Content-Type", "application/zip");
zipStream.SetLevel(3);
CompressFolder(path, zipStream, folderOffset);
ctx.Response.Flush();
}
}
private static void CompressFolder(string path, ZipOutputStream zipStream, int folderOffset)
{
string[] files = Directory.GetFiles(path);
foreach (string filename in files)
{
try
{
using (var streamReader = File.OpenRead(filename))
{
var fi = new FileInfo(filename);
string entryName = filename.Substring(folderOffset);
entryName = ZipEntry.CleanName(entryName);
var newEntry = new ZipEntry(entryName)
{
DateTime = fi.LastWriteTime,
Size = fi.Length
};
zipStream.PutNextEntry(newEntry);
streamReader.CopyTo(zipStream, 4096);
zipStream.CloseEntry();
}
}
catch (IOException) { }
}
var folders = Directory.GetDirectories(path);
foreach (string folder in folders)
{
CompressFolder(folder, zipStream, folderOffset);
}
}
}
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();
}