I found the code below on stack overflow which I tried but didn't know how to use it fully.
Basically I want to be able to zip all the files separately using foreach loop but I won't have a list of the files as they change each time.
So how can I get a list of the folders/directories inside the root directory into an array?
public static void CreateZipFile(string fileName, IEnumerable<string> files)
{
var zip = ZipFile.Open(fileName, ZipArchiveMode.Create);
foreach (var file in files)
{
zip.CreateEntryFromFile(file, Path.GetFileName(file), CompressionLevel.Optimal);
}
zip.Dispose();
}
Normally I just use DotNetZip
And this code:
using (var file = new ZipFile(zipName))
{
file.AddFiles(fileNames, directoryPathInArchive);
file.Save();
}
Where zipName is the name of zip archive you want to create and fileNames are names of files you want to put in it.
you need little script for this
public static class ZipUtil
{
public static void CreateFromMultifleFolders(string targetZip, string[] foldersSrc, string[] fileSrc = null, CompressionLevel compressionLevel = CompressionLevel.Fastest)
{
if (File.Exists(targetZip))
File.Delete(targetZip);
using (ZipArchive archive = ZipFile.Open(targetZip, ZipArchiveMode.Create))
{
foreach (string dir in foldersSrc)
AddFolederToZip(dir, archive, Path.GetFileName(dir), compressionLevel);
if(fileSrc != null)
foreach (string file in fileSrc)
archive.CreateEntryFromFile(file, Path.GetFileName(file), compressionLevel);
}
}
private static void AddFolederToZip(string folder, ZipArchive archive, string srcPath, CompressionLevel compressionLevel)
{
srcPath += "/";
foreach (string dir in Directory.GetDirectories(folder))
AddFolederToZip(dir, archive, srcPath + Path.GetFileName(dir), compressionLevel);
foreach (string file in Directory.GetFiles(folder))
archive.CreateEntryFromFile(file, srcPath + Path.GetFileName(file), compressionLevel);
}
}
Related
I am able to zip files from a specific folder using ZipFile.CreateFromDirectory in the following test code (I only used this code to test how zipping works):
// Where the files are located
string strStartPath = txtTargetFolder.Text;
// Where the zip file will be placed
string strZipPath = #"C:\Users\smelmo\Desktop\testFinish\" + strFileNameRoot + "_" + txtDateRange1.Text.Replace(#"/", "_") + "_" + txtDateRange2.Text.Replace(#"/", "_") + ".zip";
ZipFile.CreateFromDirectory(strStartPath, strZipPath);
However, this zips together ALL of the contents in the folder. I am trying to zip together specific items in the folder using ZipArchive in the following code:
// Where the files are located
string strStartPath = txtTargetFolder.Text;
// Where the zip file will be placed
string strZipPath = #"C:\Users\smelmo\Desktop\testFinish\" + strFileNameRoot + "_" + txtDateRange1.Text.Replace(#"/", "_") + "_" + txtDateRange2.Text.Replace(#"/", "_") + ".zip";
using (ZipArchive archive = ZipFile.OpenRead(strStartPath))
{
foreach (ZipArchiveEntry entry in archive.Entries)
{
if (!(entry.FullName.EndsWith(".TIF", StringComparison.OrdinalIgnoreCase)))
{
entry.ExtractToFile(Path.Combine(strZipPath, entry.FullName));
}
}
}
It is giving the error at ZipFile.OpenRead(strStartPath). Why am I able to access the exact folder in the first block of code but not the second? Or is there an easier way to search through a folder and only zip specific items?
You are utilizing the Zip libraries wrong
Effectively you are trying to open a directory as if it were a zip file, then loop over the contents of that directory (which again is actually a zip file) and then attempting to extract each member into a different zip file
Here is a working example of what you have described you are trying to do:
string strStartPath = #"PATH TO FILES TO PUT IN ZIP FILE";
string strZipPath = #"PATH TO ZIP FILE";
if (File.Exists(strZipPath))
File.Delete(strZipPath);
using (ZipArchive archive = ZipFile.Open(strZipPath, ZipArchiveMode.Create))
{
foreach (FileInfo file in new DirectoryInfo(strStartPath).GetFiles())
{
if (!(file.FullName.EndsWith(".TIF", StringComparison.OrdinalIgnoreCase)))
{
archive.CreateEntryFromFile(Path.Combine(file.Directory.ToString(), file.Name), file.Name);
}
}
}
This will take all the root level contents of a folder and put it in the zip file. You will need to implement your own way of getting subfolders and their contents recursively, but that is beyond the scope of this question.
EDIT: Here is a working example with proper folder recursion to select all files even in subdirectories
public void ZipFolder()
{
string strStartPath = #"PATH TO FILES TO PUT IN ZIP FILE";
string strZipPath = #"PATH TO ZIP FILE";
if (File.Exists(strZipPath))
File.Delete(strZipPath);
using (ZipArchive archive = ZipFile.Open(strZipPath, ZipArchiveMode.Create))
{
foreach (FileInfo file in RecurseDirectory(strStartPath))
{
if (!(file.FullName.EndsWith(".TIF", StringComparison.OrdinalIgnoreCase)))
{
var destination = Path.Combine(file.DirectoryName, file.Name).Substring(strStartPath.Length + 1);
archive.CreateEntryFromFile(Path.Combine(file.Directory.ToString(), file.Name), destination);
}
}
}
}
public IEnumerable<FileInfo> RecurseDirectory(string path, List<FileInfo> currentData = null)
{
if (currentData == null)
currentData = new List<FileInfo>();
var directory = new DirectoryInfo(path);
foreach (var file in directory.GetFiles())
currentData.Add(file);
foreach (var d in directory.GetDirectories())
RecurseDirectory(d.FullName, currentData);
return currentData;
}
I did the following, but i don't see the zip file in the directroy. C#
public static void AddToZip(string fileToAdd, string directory)
{
string entryName = fileToAdd.Replace(directory, string.Empty);
string archiveName = entryName.Replace(Path.GetExtension(entryName), ".zip");
using (ZipArchive za = ZipFile.Open(archiveName, ZipArchiveMode.Create))
{
za.CreateEntryFromFile(fileToAdd, entryName, CompressionLevel.Optimal);
}
}
and this is the link i followed.
http://msdn.microsoft.com/en-us/library/system.io.compression.ziparchive(v=vs.110).aspx
Finally got it working after some trial and error.
public static void AddToZip(string fileToAdd, string directory)
{
string entryName = fileToAdd.Replace(directory, string.Empty);//name of the file inside zip archive
string tempDir = Path.Combine(directory, Path.GetFileNameWithoutExtension(entryName));
if (Directory.Exists(tempDir)) DeleteDirector(tempDir);
else Directory.CreateDirectory(tempDir);
System.IO.File.Move(fileToAdd, Path.Combine(tempDir, entryName));//as the CreateFromDirectoy add all the file from the directory provided, we are moving our file to temp dir.
string archiveName = entryName.Replace(Path.GetExtension(entryName), ".zip"); //name of the zip file.
ZipFile.CreateFromDirectory(tempDir, Path.Combine(directory, archiveName));
DeleteDirector(tempDir);
}
private static void DeleteDirector(string deletedir)
{
foreach (string file in Directory.GetFiles(deletedir))
{
System.IO.File.Delete(file);
}
Directory.Delete(deletedir);
}
I know this is not the best solution. so, you are welcome to modify/improve it.
I have been using the following lines to search a folder structure for specific filetypes and just copy those filetypes and maintain their original folder structure. It works very well.
Is there any modification I can make to my method to only copy the directories that contain the filtered filetype?
*edit: I can let the user select a only certain set of files, (example *.dwg or *.pdf), using text box named txtFilter.
private void button1_Click(object sender, EventArgs e)
{
string sourceFolder = txtSource.Text;
string destinationFolder = txtDestination.Text;
CopyFolderContents(sourceFolder, destinationFolder);
}
// Copies the contents of a folder, including subfolders to an other folder, overwriting existing files
public void CopyFolderContents(string sourceFolder, string destinationFolder)
{
string filter = txtFilter.Text;
if (Directory.Exists(sourceFolder))
{
// Copy folder structure
foreach (string sourceSubFolder in Directory.GetDirectories(sourceFolder, "*", SearchOption.AllDirectories))
{
Directory.CreateDirectory(sourceSubFolder.Replace(sourceFolder, destinationFolder));
}
// Copy files
foreach (string sourceFile in Directory.GetFiles(sourceFolder, filter, SearchOption.AllDirectories))
{
string destinationFile = sourceFile.Replace(sourceFolder, destinationFolder);
File.Copy(sourceFile, destinationFile, true);
}
}
}
Something like this in your main loop?
if (Directory.EnumerateFiles(sourceSubFolder, "*.pdf").Any())
Directory.CreateDirectory(sourceSubFolder.Replace(sourceFolder, destinationFolder));
or for multiple file types:
if (Directory.EnumerateFiles(sourceSubFolder).Where(x => x.ToLower.EndsWith(".pdf") || x.ToLower.EndsWith(".dwg")).Any())
Directory.CreateDirectory(sourceSubFolder.Replace(sourceFolder, destinationFolder));
You can simply concatenate both operations into one loop and complete the algorithm in O(n).
foreach(string sourceFile in Directory.GetFiles(sourceFolder, filter, SearchOption.AllDirectories))
{
Directory.CreateDirectory(Path.GetDirectoryName(sourceFile.Replace(sourceFolder,destinationFolder)));
File.Copy(sourceFile,sourceFile.Replace(sourceFolder,destinationFolder),true);
}
You can get the distinct directories from the files you find, then iterate through them and create the directories before copying the files.
if (Directory.Exists(sourceFolder))
{
var files = Directory.GetFiles(sourceFolder, filter, SearchOption.AllDirectories);
foreach(string directory in files.Select(f => Path.GetDirectoryName(f)).Distinct())
{
string destinationDirectory = directory.Replace(sourceFolder, destinationFolder);
if (!Directory.Exists(destinationDirectory))
{
Directory.CreateDirectory(destinationDirectory);
}
}
foreach (string sourceFile in files)
{
string destinationFile = sourceFile.Replace(sourceFolder, destinationFolder);
File.Copy(sourceFile, destinationFile, true);
}
}
I am trying to copy an entire directory including all sub-directories to another folder on the same drive using the below function:
private static void CopyAll(DirectoryInfo source, DirectoryInfo target)
{
// Check if the target directory exists, if not, create it.
if (Directory.Exists(target.FullName) == false)
{
Directory.CreateDirectory(target.FullName);
}
// Copy each file into it’s new directory.
foreach (FileInfo fi in source.GetFiles())
{
Console.WriteLine(#"Copying {0}\{1}", target.FullName, fi.Name);
fi.CopyTo(Path.Combine(target.ToString(), fi.Name), true);
}
// Copy each subdirectory using recursion.
foreach (DirectoryInfo diSourceSubDir in source.GetDirectories())
{
DirectoryInfo nextTargetSubDir = target.CreateSubdirectory(diSourceSubDir.Name);
CopyAll(diSourceSubDir, nextTargetSubDir);
}
}
Called with:
public void copyTemplate(string templatepath, string destpath)
{
DirectoryInfo s = new DirectoryInfo(#"C:\temp\templates\template1");
DirectoryInfo t = new DirectoryInfo(#"C:\temp\www_temp\gtest");
CopyAll(s, t);
}
Which produces the error:
The process cannot access the file 'C:\temp\templates\template1\New folder\alf.txt' because it is being used by another process.
The file is not in use by me and third-party software tells me no processes are locking the file so I suspect the copy function is tripping itself up somewhere.
Can anyone shed any light on why this might be happening or suggest a function that would better do the job?
Thanks
You might want to consider locking access to the files, it shouldn't reduce your speed too much and should avoid any attempts to access the file at the same time.
private object lockObj = new object();
lock(lockObj)
{
//Code to access file
}
This should solve your issue.
Replacing the CopyAll() function with the below solved the issue.
public static void CopyFolder(string sourceFolder, string destFolder)
{
if (!Directory.Exists(destFolder))
Directory.CreateDirectory(destFolder);
string[] files = Directory.GetFiles(sourceFolder);
foreach (string file in files)
{
string name = Path.GetFileName(file);
string dest = Path.Combine(destFolder, name);
File.Copy(file, dest);
}
string[] folders = Directory.GetDirectories(sourceFolder);
foreach (string folder in folders)
{
string name = Path.GetFileName(folder);
string dest = Path.Combine(destFolder, name);
CopyFolder(folder, dest);
}
}
I found a small snippet for doing a recursive file copy in C#, but am somewhat stumped. I basically need to copy a directory structure to another location, along the lines of this...
Source: C:\data\servers\mc
Target: E:\mc
The code for my copy function as of right now is...
//Now Create all of the directories
foreach (string dirPath in Directory.GetDirectories(baseDir, "*", SearchOption.AllDirectories))
{
Directory.CreateDirectory(dirPath.Replace(baseDir, targetDir));
}
// Copy each file into it’s new directory.
foreach (string file in Directory.GetFiles(baseDir, "*.*", SearchOption.AllDirectories))
{
Console.WriteLine(#"Copying {0}\{1}", targetDir, Path.GetFileName(file));
if (!CopyFile(file, Path.Combine(targetDir, Path.GetFileName(file)), false))
{
int err = Marshal.GetLastWin32Error();
Console.WriteLine("[ERROR] CopyFile Failed on {0} with code {1}", file, err);
}
}
The issue is that in the second scope, I either:
use Path.GetFileName(file) to get the actual file name without the path but I lose the directory "mc" directory structure or
use "file" without Path.Combine.
Either way I have to do some nasty string work. Is there a good way to do this in C# (my lack of knowledge with the .NET API leads me to over complicating things)
MSDN has a complete sample: How to: copy directories
using System;
using System.IO;
class DirectoryCopyExample
{
static void Main()
{
// Copy from the current directory, include subdirectories.
DirectoryCopy(".", #".\temp", true);
}
private static void DirectoryCopy(string sourceDirName, string destDirName,
bool copySubDirs)
{
// Get the subdirectories for the specified directory.
DirectoryInfo dir = new DirectoryInfo(sourceDirName);
if (!dir.Exists)
{
throw new DirectoryNotFoundException(
"Source directory does not exist or could not be found: "
+ sourceDirName);
}
DirectoryInfo[] dirs = dir.GetDirectories();
// If the destination directory doesn't exist, create it.
if (!Directory.Exists(destDirName))
{
Directory.CreateDirectory(destDirName);
}
// Get the files in the directory and copy them to the new location.
FileInfo[] files = dir.GetFiles();
foreach (FileInfo file in files)
{
string temppath = Path.Combine(destDirName, file.Name);
file.CopyTo(temppath, false);
}
// If copying subdirectories, copy them and their contents to new location.
if (copySubDirs)
{
foreach (DirectoryInfo subdir in dirs)
{
string temppath = Path.Combine(destDirName, subdir.Name);
DirectoryCopy(subdir.FullName, temppath, copySubDirs);
}
}
}
}
A non-recursive replacement for this answer would be:
private static void DirectoryCopy(string sourceBasePath, string destinationBasePath, bool recursive = true)
{
if (!Directory.Exists(sourceBasePath))
throw new DirectoryNotFoundException($"Directory '{sourceBasePath}' not found");
var directoriesToProcess = new Queue<(string sourcePath, string destinationPath)>();
directoriesToProcess.Enqueue((sourcePath: sourceBasePath, destinationPath: destinationBasePath));
while (directoriesToProcess.Any())
{
(string sourcePath, string destinationPath) = directoriesToProcess.Dequeue();
if (!Directory.Exists(destinationPath))
Directory.CreateDirectory(destinationPath);
var sourceDirectoryInfo = new DirectoryInfo(sourcePath);
foreach (FileInfo sourceFileInfo in sourceDirectoryInfo.EnumerateFiles())
sourceFileInfo.CopyTo(Path.Combine(destinationPath, sourceFileInfo.Name), true);
if (!recursive)
continue;
foreach (DirectoryInfo sourceSubDirectoryInfo in sourceDirectoryInfo.EnumerateDirectories())
directoriesToProcess.Enqueue((
sourcePath: sourceSubDirectoryInfo.FullName,
destinationPath: Path.Combine(destinationPath, sourceSubDirectoryInfo.Name)));
}
}
instead of
foreach (string file in Directory.GetFiles(baseDir, "*.*", SearchOption.AllDirectories))
{
do something like this
foreach (FileInfo fi in source.GetFiles())
{
fi.CopyTo(Path.Combine(target.ToString(), fi.Name), true);
}