Creating a zip archive in C# that preserves folder structure - c#

I'm struggling with creating zip archives and could use some guidance from more experienced coders. I am attempting to create a zip archive of 800 folders and subfolders and about 8,000 files with the following method. This code works in so far that it will create an archive but there is no internal directory structure. All 8,000 files are stored in the zip in a single flat list. Is there a way to do this so that its like a typical zip archive in that the folder structure is also stored in the zip file? I am aware of the ZipFile.CreateFromDirectory() method [which does preserve folder structure] but do not want to use it because it fails in the event that a file is locked. I am also aware that there are other libraries but I'd like to use the C# library if that is possible. Any guidance will be appreciated. Thank you.
{
SearchOption searchOption = SearchOption.AllDirectories;
IEnumerable<string> fileSystem;
fileSystem = Directory.EnumerateFileSystemEntries(_zipRoot, "*.*", searchOption);
using (ZipArchive archive = ZipFile.Open(_zipPath, ZipArchiveMode.Create))
{
foreach (var fPath in fileSystem)
{
try
{
archive.CreateEntryFromFile(fPath,Path.GetFileName(fPath));
}
catch
{
FailedFiles.Add(fPath);
Debug.Log(fPath);
}
}
}
Debug.Log($"{FailedFiles.Count} files failed to archive.");
}```

After reading the thread posted by #mjwills which discusses several approaches, the following code suggested by #Curti works like a charm.
public static void StructuredZip(this ZipArchive archive, string sourceDirName, CompressionLevel compressionLevel = CompressionLevel.Fastest)
{
int fileCount = 0;
int folderCount = 0;
int failedCount = 0;
var folders = new Stack<string>();
folders.Push(sourceDirName);
do
{
var currentFolder = folders.Pop();
folderCount++;
foreach (var item in Directory.GetFiles(currentFolder))
{
try
{
archive.CreateEntryFromFile(item, item.Substring(sourceDirName.Length + 1),
compressionLevel);
fileCount++;
}
catch
{
failedCount++;
}
}
foreach (var item in Directory.GetDirectories(currentFolder))
{
folders.Push(item);
}
}
while (folders.Count > 0);
Debug.Log($"Archived {fileCount} in {folderCount} folders. There were {failedCount} failed files!");
}
}
using (var zip = ZipFile.Open(_zipPath, ZipArchiveMode.Create))
{
zip.StructuredZip(_zipRoot);
}

Related

ZipFile.CreateFromDirectory continue for the rest of file when some files are not accessible in the dir

ZipFile.CreateFromDirectory(source_dir, target_dir) will throw exception and stop the zipping when any file in the directory is being accessed. how can I make it do the zipping for the rest of the files ??
Killing the processes are not allowed, they are vital.
Thanks
Resolved:
Here is how I get this around.
Split the task into 2 phase.
(Note only use the Zipfile and ZipArchive from System.IO.Compression)
Step 1. create a dummy zip file; (this must not hit access issue)
Step 2. Scan and Add files to the dummy zip file
Additional: Keep the original directory hierarchy by adding folder into the dummy zip file
Here is the Step 2, only copy the file when hit exception.
private void UpdateBallFile(String source_dir, String target_zipfile)
{
using (ZipArchive archive = ZipFile.Open(target_zipfile + suffix, ZipArchiveMode.Update))
{
foreach (String subdir in Directory.GetDirectories(source_dir, "*.*", SearchOption.AllDirectories))
{
String relatedPath = subdir.Replace(source_dir, String.Empty);
String entry = relatedPath.Replace("\\", "/").Substring(1);
foreach (String file in Directory.GetFiles(subdir))
{
if (File.Exists(file))
{
FileInfo info = new FileInfo(file);
try
{
archive.CreateEntryFromFile(file, entry+"/"+info.Name);
}
catch
{
try
{
String copied_item = Path.Combine(#"c:\", info.Name);
File.Copy(file, copied_item, true);
archive.CreateEntryFromFile(copied_item, entry + "/" + info.Name);
File.Delete(copied_item);
}
catch (Exception ex)
{
UpdateLog(String.Format("Fails to zip: {0}, {1} ", file, ex.Message));
}
}
}
}
}
}
}
I am new to C# and this community, Please let me know if you have better idea.
Thanks
You can add each file to your zip by checking the accessibility, so you can code like this:
ZipFile myzip = new ZipFile("myzipFile");
foreach (string file in Directory.GetFiles(#"D:\sample"))
{
try
{
var stream = File.Open(file, FileMode.Open, FileAccess.ReadWrite, FileShare.None);
stream.Dispose();
myzip.AddFile(file);// add file to zip only if it is accessible. else it will throw some exception
//hence it wont added to the zipped folder.
}
catch
{ }
}
myzip.Save(#"D:\sample\myfile.zip");// this zip file contains only file that have access
Hope that this is actually you ware looking;
An easy solution would be preparing a kind of copy/temp folder in the User/AppData/Local directory, where all readable files are in. You will than zip this folder, because you can be sure that the data are not used. After the zip-process you have to delete the folder. Not the optimum but it should work. Another solution would be using some other zip component...

How to recursively copy file an folder in .Net?

I have the following script, which take a source folder and copy using FileStream files to another folder.
I need to change it in a way to recursively get any sub-folders and copy their files too.
How to modifythe method?
- source folder
- file
- file
- folder
- file
- file
- folder
- file
- folder
- file
- file
- folder
- file
public static void SynchFolders()
{
DirectoryInfo StartDirectory = new DirectoryInfo(SourceUNC);
DirectoryInfo EndDirectory = new DirectoryInfo(TargetUNC);
foreach (FileInfo file in StartDirectory.EnumerateFiles())
{
using (FileStream SourceStream = file.OpenRead())
{
string dirPath = StartDirectory.FullName;
string outputPath = dirPath.Replace(StartDirectory.FullName, EndDirectory.FullName);
using (FileStream DestinationStream = File.Create(outputPath + "\\" + file.Name))
{
SourceStream.CopyToAsync(DestinationStream);
}
}
}
}
Basically what you need to do is to expand your function slightly such that after copying all files found in a particular directory, it will then search for subfolders within the current folder and recurse into that folder so that the same procedure is carried out on each subfolder.
An example function, based on your original code :
public static void SynchFolders(string SourceUNC, string TargetUNC)
{
DirectoryInfo StartDirectory = new DirectoryInfo(SourceUNC);
DirectoryInfo EndDirectory = new DirectoryInfo(TargetUNC);
// Copy Files
foreach (FileInfo file in StartDirectory.EnumerateFiles())
{
using (FileStream SourceStream = file.OpenRead())
{
string dirPath = StartDirectory.FullName;
string outputPath = dirPath.Replace(StartDirectory.FullName, EndDirectory.FullName);
using (FileStream DestinationStream = File.Create(outputPath + "\\" + file.Name))
{
SourceStream.CopyToAsync(DestinationStream);
}
}
}
// Copy subfolders
var folders = StartDirectory.EnumerateDirectories();
foreach (var folder in folders)
{
// Create subfolder target path by concatenating folder name to original target UNC
string target = Path.Combine(TargetUNC, folder.Name);
Directory.CreateDirectory(target);
// Recurse into the subfolder
SynchFolders(folder.FullName, target);
}
}
Hope this helps
How to: Copy Directories is an article from MSDN showing how to do exactly what you need.
Read this MSDN Tutorial (exacly what you need): http://msdn.microsoft.com/en-us/library/bb762914(v=vs.110).aspx
Note: if you'd like to use SourceStream.CopyToAsync instead of file.CopyTo, just replace it with your original snippet

Reading contents from several files and writing to one file

In my application there is a situation like this.Before creating a file, my application search for files in a directory under a particular filename. If any file/files found, then it should read each files contents and write these contents(of each file) to a new file. I have googled many and tried some like this:
string temp_file_format = "ScriptLog_" + DateTime.Now.ToString("dd_MM_yyyy_HH");
string[] files = Directory.GetFiles(path,temp_file_format);
foreach (FileAccess finfo in files)
{
string text = File.ReadAllText(finfo);
}
and
System.IO.DirectoryInfo dir = new DirectoryInfo(path);
System.IO.FileInfo[] files = dir.GetFiles(temp_file_format);
foreach (FileInfo finfo in files)
{
finfo.OpenRead();
}
But all these failed..Can anyone show me an alternative for this?
Is there anything wrong in my temp_file_format string?
It will be nice if I could prepend these contents to the new file. Else also, no worries..
any help would be really appreciated..
This is a compete working implementation that does all of that
without reading everything in memory at one time (which doesn't work for large files)
without keeping any files open for more than the required time
using System.IO;
using System.Linq;
public static class Program {
public static void Main()
{
var all = Directory.GetFiles("/tmp", "*.cpp")
.SelectMany(File.ReadAllLines);
using (var w = new StreamWriter("/tmp/output.txt"))
foreach(var line in all)
w.WriteLine(line);
}
}
I tested it on mono 2.10, and it should work on any .NET 4.0+ (for File.ReadAllLines which is a lazy linewise enumerable)
Here's a short snippet that reads all the files and out puts them to the path outputPath
var lines = from file in Directory.GetFiles(path,temp_file_format)
from line in File.ReadAllLines(file)
select line;
File.WriteAllLines(outputPath, content);
The problem you are having with your code is not really related to reading files but simply trying to use an object as a type it's not. Directory.GetFiles returns an array of string and File.ReadXXX and File.OpenRead expects the path as a string. So you simply need to pass each of the strings returned as the path argument to the appropriate method. The above is one such example. Hope it helps both solve your problem and explain the actually issue with your code
try this:
foreach (FileInfo finfo in files)
{
try
{
using (StreamReader sr = new StreamReader("finfo "))
{
String line = sr.ReadToEnd();
Console.WriteLine(line);
}
}
catch (Exception e)
{
Console.WriteLine("The file could not be read:");
Console.WriteLine(e.Message);
}
}
using (var output = File.Create(outputPath))
{
foreach (var file in Directory.GetFiles(InputPath,temp_file_format))
{
using (var input = File.OpenRead(file))
{
input.CopyTo(output);
}
}
}

Looping over files in subdirectories of a ZIP archive

Assume I have a zip file which contains 10 text files. It's easy to iterate over these text files using:
using (ZipArchive archive = ZipFile.OpenRead(zipIn))
{
foreach (ZipArchiveEntry entry in archive.Entries)
{
Console.writeLine(entry)
}
}
However, suppose the text files are within a subdirectory:
zip/subdirectory/file1.txt
In this case the above code only outputs the subdirectory folder ('subdirectory'), as opposed to all the text files within that folder.
Is there a simple way of looping over the files in the subdirectory also?
I have reproduced your program. When I iterate over a zip archive the way you do it, I get a list of all files in the full directory structure within the archive. So you do not need recursion, just iterate like you are doing now.
I understand your confusion since the API does not make a distinction between files and folders. Here is an extension method to help:
static class ZipArchiveEntryExtensions
{
public static bool IsFolder(this ZipArchiveEntry entry)
{
return entry.FullName.EndsWith("/");
}
}
Then you can do:
using (var archive = ZipFile.OpenRead("bla.zip"))
{
foreach (var s in archive.Entries)
{
if (s.IsFolder())
{
// do something special
}
}
}
I can't reproduce your problem. It works fine in my test case:
using (var archive = ZipFile.OpenRead(zipIn))
{
foreach (var zipArchiveEntry in archive.Entries)
{
Console.WriteLine(zipArchiveEntry);
}
}
Console.ReadLine();
Result:

zip files with same names but different extensions using Ioniz.Zip DLL

I need help in writing a function which zips all files with same name but different extensions in a folder.I am using Ionic.Zip dll to achieve this.I am using .Net compact framework 2.0,VS2005. My code looks like this:
public void zipFiles()
{
string path = "somepath";
string[] fileNames = Directory.GetFiles(path);
Array.Sort(fileNames);//sort the filename in ascending order
string lastFileName = string.Empty;
string zipFileName = null;
using (ZipFile zip = new ZipFile())
{
zip.AlternateEncodingUsage = ZipOption.AsNecessary;
zip.AddDirectoryByName("Files");
for (int i = 0; i < fileNames.Length; i++)
{
string baseFileName = fileNames[i];
if (baseFileName != lastFileName)
{
zipFileName=String.Format("Zip_{0}.zip",DateTime.Now.ToString("yyyy-MMM-dd-HHmmss"));
zip.AddFile(baseFileName, "Files");
lastFileName = baseFileName;
}
}
zip.Save(zipFileName);
}
}
The problem:The folder will have 3 files with same name but their extensions will be different.Now,these files are being FTPed by a device so the filenames are auto-generated by it and I have no control over it.So,for example,there are 6 files in the folder:"ABC123.DON","ABC123.TGZ","ABC123.TSY","XYZ456.DON","XYZ456.TGZ","XYZ456.TSY". I have to zip the 3 files whose names are "ABC123" together and other 3 files with names "XYZ456".As I said,I wouldnt know the names of the files and my function has to run in background.My current code zips all the files in a single zip folder.
Can anyone please help me with this?
Try out the following code
string path = #"d:\test";
//First find all the unique file name i.e. ABC123 & XYZ456 as per your example
List<string> uniqueFiles=new List<string>();
foreach (string file in Directory.GetFiles(path))
{
if (!uniqueFiles.Contains(Path.GetFileNameWithoutExtension(file)))
uniqueFiles.Add(Path.GetFileNameWithoutExtension(file));
}
foreach (string file in uniqueFiles)
{
string[] filesToBeZipped = Directory.GetFiles(#"d:\test",string.Format("{0}.*",file));
//Zip all the files in filesToBeZipped
}

Categories

Resources