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:
Related
I'm attempting to zip up a handful of files but these files could exist in different directories. The zipping portion is working correctly but I cannot figure out how to get it to preserve the directory structure within the zip file.
Here's what I have so far:
public static void CreateZipFile(IEnumerable<FileInfo> files, string archiveName)
{
using (var stream = File.OpenWrite(archiveName))
using (var archive = new ZipArchive(stream, ZipArchiveMode.Create))
{
foreach (var item in files)
{
archive.CreateEntryFromFile(item.FullName, item.Name, CompressionLevel.Optimal);
}
}
}
Is this possible?
#ErocM the link provided by #Flydog57 gives you exactly what you want. You are not exploiting the entryName argument correctly (the second argument in your case when calling CreateEntryFromFile).
Independently of which file you are adding to the archive (from same of different folders), you have to structure your archive using the entryName argument the C# api gives to you.
If your file's fullname is /tmp/myfile.txt, and you do archive.CreateEntryFromFile(item.FullName, item.Name), then the archive entry name will be myfile.txt. No folder created as the entry name doesn't contain folder structure in it's name.
However, if you call archive.CreateEntryFromFile(item.FullName, item.FullName), you will then have you file folder structure into the archive.
You can try with your function just changing item.Name into item.FullName.
Just be careful, on windows; if you path is C:\tmp\myfile.txt for instance, the archive will not be extractable correctly. You can then add some little code to remove C: from the full name of your files.
Some examples taking your implementation:
using System;
using System.IO;
using System.Collections.Generic;
using System.IO.Compression;
namespace ConsoleApp
{
internal class Program
{
static void Main(string[] args)
{
FileInfo f1 = new FileInfo(#"/tmp/test1.txt");
FileInfo f2 = new FileInfo(#"/tmp/testdir/test2.txt");
List<FileInfo> files = new();
files.Add(f1);
files.Add(f2);
CreateZipFile(files, #"/tmp/archive.zip");
}
public static void CreateZipFile(IEnumerable<FileInfo> files, string archiveName)
{
using (var stream = File.OpenWrite(archiveName))
using (var archive = new ZipArchive(stream, ZipArchiveMode.Create))
{
foreach (var item in files)
{
// Here for instance, I put all files in the input list in the same directory, without checking from where they are in the host file system.
archive.CreateEntryFromFile(item.FullName, $"mydir/{item.Name}", CompressionLevel.Optimal);
// Here, I am just using the actual full path of the file. Be careful on windows with the disk name prefix (C:, D:, etc...).
// archive.CreateEntryFromFile(item.FullName, item.FullName, CompressionLevel.Optimal);
}
}
}
}
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);
}
I have a number of zip files that contain .txt files nested in sub directories within the zip file. I am trying to extract the .txt files and output them to another directory, however I am getting an error 'Could not find a part of the path...' This error occurs at the 'entry.FullName' point
I believe I need to remove the file path at some stage of the unzip process since I can get the code to run if I use zip files with .txt files in them without any sub-directories. Any pointers would be much appreciated.
Here is my code:
class Program
{
static void Main(string[] args)
{
DateTime dt = DateTime.Now;
foreach (var zp in Directory.GetFiles(#"D:\\My Documents\\DMU\\Frontrunner2015\\ZipIn\\", "*.zip"))
{
string zipPath = zp;
string extractPath = #"D:\\My Documents\\DMU\\Frontrunner2015\\ZipOut\\";
using (ZipArchive archive = ZipFile.OpenRead(zipPath))
{
foreach (ZipArchiveEntry entry in archive.Entries)
{
if (entry.FullName.EndsWith(".txt", StringComparison.OrdinalIgnoreCase))
{
entry.ExtractToFile(Path.Combine(extractPath, entry.FullName));
}
foreach (var file in Directory.GetFiles(extractPath))
{.....
Would have helped if you left the path in the error message there so people could see what path was not found. I would guess that when you combine extractPath and FullName you end up with a folder name that does not exist - as you mentioned the files in the zip file have subdirectories.
I think you really meant to use Name property in your Path.Combine call.
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);
}
}
}
I have 1,000 Uniquely named folders in one large folder.
Inside of each uniquely named folder these is another folder called /images.
Inside each image folder there is a file named "Read-Web-Site-Design-{UNIQUEFOLDERNAME}-ca-logo.png"
I want to replace the 1,768 .png files (while keeping the original name) from a .png file which I am supplying.
The folder structure and filenames need to remain same. Basically I'm updating the old file with a new file, using the same (unique) name, 1,000 times.
I have written this code and I am able to get all the files and directories in loop but I want to know how will I update files here,please check my code:
private List<String> DirSearch(string sDir)
{
List<String> files = new List<String>();
try
{
foreach (string f in Directory.GetFiles(sDir))
{
files.Add(f);
}
foreach (string d in Directory.GetDirectories(sDir))
{
files.AddRange(DirSearch(d));
}
}
catch (System.Exception excpt)
{
//MessageBox.Show(excpt.Message);
}
return files;
}
Simply you have to do a File.Copy() to replace old files with new .png file you are supplying.
Assuming you will have all the files to be replaced in the list,
List<String> files = new List<String>();
foreach (var file in files)
{
if (!string.IsNullOrWhiteSpace(file))
{
File.Copy("New File Path", "file to be replaced", true);
}
}
See, you are passing true as the 3rd parameter of the Copy() method, Which is to overrite if there is a file already in the destination path.
or you can use File.Replace(). Here you can keep the original file as a backup.
File.Replace("New File", "File to be replaced", "Back up of Original File");
You may wish to consider the following, It will handle the recursion for you, only find png files that have "Read-Web-Site" in their file path
foreach(var file in Directory.EnumerateFiles(dir, "*.png", SearchOption.AllDirectories)
.Where(x => x.Contains("Read-Web-Site")))
{
File.Copy(file, Path.Combine(new FileInfo(file).DirectoryName,"newName.png"), true);
}
If its another file you wish to overwrite to this file instead then its the same but
File.Copy("newFile", file, true);
Edit
Even better
foreach(var file in Directory.EnumerateFiles(dir,
"Read-Web-Site*.png",
SearchOption.AllDirectories))