![]() |
General Programming »
Algorithms & Recipes »
General
Intermediate
License: The Code Project Open License (CPOL)
Zip/Unzip using java.util.zip .net namespace and moreBy dmihailescuZip/Unzip using java.util.zip .net from managed code |
C# 2.0.NET 2.0, WinXP, Vista, ADO.NET, VS2005, Dev
|
||||||||||
|
Advanced Search Add to IE Search |
|
|
|
||||||||||||||||
When dealing with zip files you have a few choices: use native APIs from third party Dlls, java APIs or .Net APIs.
If you rush to use APIs from System.IO.Compress .net namespace you will be very disappointed.
For reasons only Microsoft knows, the support is limited to streams only and lacks completely
for multi-files archives. This was probably a reason why third party .net libraries like SharpZipLib cropped up.
If you don't trust the free software, you might be surprised to find out that you can find .net support
for multi-file archives in .net buried in J# assemblies that offers parity with Java APIs.
To make a useful application that uses it I started with an existing code project application that is very handy
when backing up source code. I replaced the SharpZipLib references and used the Microsoft's J# APIs instead.
When porting the application I noticed that SharpZipLib API's were looking very similar with J# APIs and that
made my work so much easier.
To make this utility more enticing to use I've added quite a few features that I will detail below.
In order to use Microsoft's API for multi-file zips and Java streams, you have to add vjslib.dll and vjslibcw.dll
.net assemblies as project references. They are part of J# distribution pack.
The Java like types will show up in the java.util.zip namespace. Since Microsoft's documentation on this
topic is quite Spartan ,I often had to rely on intellisense to figure it out.
For simplicity's sake, some nonessential UI code is omitted bellow and can be found only in the source code provided.
Below you could see a snippet of code edited for simplicity that enumerates the files in the archive:
public static List<string > GetZipFileNames(string zipFile) { ZipFile zf = null; List<string > list = new List<string >(); try { zf = new ZipFile(zipFile); java.util.Enumeration enu = zf.entries(); while (enu.hasMoreElements()) { ZipEntry zen = enu.nextElement() as ZipEntry; if (zen.isDirectory()) continue;//ignore directories list.Add(zen.getName()); } } catch(Exception ex) { throw new ApplicationException("Please drag/drop only valid zip files\nthat are not password protected.",ex); } finally { if (zf != null) zf.close(); } return list; }
As you probably noticed ZipEntry and ZipFile are easy to use for this goal.
Below you could see a helper method used to zip the files from a folder:
private static void _CreateZipFromFolder(string Folder, IsFileStrippableDelegate IsStrip) { System.IO.DirectoryInfo dirInfo = new System.IO.DirectoryInfo(Folder); System.IO.FileInfo[] files = dirInfo.GetFiles("*");//all files foreach (FileInfo file in files) { if (IsStrip != null && IsStrip(file.FullName)) continue;//skip, don't zip it java.io.FileInputStream instream = new java.io.FileInputStream(file.FullName); int bytes = 0; string strEntry = file.FullName.Substring(m_trimIndex); _zos.putNextEntry(new ZipEntry(strEntry)); while ((bytes = instream.read(_buffer, 0, _buffer.Length)) > 0) { _zos.write(_buffer, 0, bytes); } _zos.closeEntry(); instream.close(); } System.IO.DirectoryInfo[] folders = null; folders = dirInfo.GetDirectories("*"); if (folders != null) { foreach (System.IO.DirectoryInfo folder in folders) { _CreateZipFromFolder(folder.FullName, IsStrip); } } }
The IsStrip delegate acts as a filter that trashes the unwanted files.
Below you could see an edited for brevity piece of code used to unzip the files from a zip:
ZipInputStream zis = null; zis = new ZipInputStream(new java.io.FileInputStream(file)); ZipEntry ze = null; while ((ze = zis.getNextEntry()) != null) { if (ze.isDirectory()) continue;//ignore directories string fname = ze.getName(); bool bstrip = IsStrip != null && IsStrip(fname); if (!bstrip) { //unzip entry int bytes = 0; FileStream filestream = null; BinaryWriter w = null; string filePath = Folder + @"\" + fname; if(!Directory.Exists(Path.GetDirectoryName(filePath))) Directory.CreateDirectory(Path.GetDirectoryName(filePath)); filestream = new FileStream(filePath, FileMode.Create); w = new BinaryWriter(filestream); while ((bytes = zis.read(_buffer, 0, _buffer.Length)) > 0) { for (int i = 0; i < bytes; i++) { unchecked { w.Write((byte)_buffer[i]); } } } } zis.closeEntry(); w.Close(); filestream.Close(); } if (zis != null) zis.close(); }
Again the IsStrip delegate acts as a filter that trashes the unwanted files.
Also I had to mix the java.io with System.IO namespaces because of the sbyte[] array.
You can not directly modify a zip file. However you can create another zip and copy only select files in it.
When the transfer is complete we can rename the new file as the original and it would look like as we
changed the zip. The edited for brevity method below receives a list of string with the unwanted files:
public static void StripZip(string zipFile, List<string > trashFiles) { ZipOutputStream zos = null; ZipInputStream zis = null; //remove 'zip' extension bool bsuccess = true; string strNewFile = zipFile.Remove(zipFile.Length - 3, 3) + "tmp"; zos = new ZipOutputStream(new java.io.FileOutputStream(strNewFile)); zis = new ZipInputStream(new java.io.FileInputStream(zipFile)); ZipEntry ze = null; while ((ze = zis.getNextEntry()) != null) { if (ze.isDirectory()) continue;//ignore directories string fname = ze.getName(); bool bstrip = trashFiles.Contains(fname); if (!bstrip) { //copy the entry from zis to zos int bytes = 0; //deal with password protected files zos.putNextEntry(new ZipEntry(fname)); while ((bytes = zis.read(_buffer, 0, _buffer.Length)) > 0) { zos.write(_buffer, 0, bytes); } zis.closeEntry(); zos.closeEntry(); } } if (zis != null) zis.close(); if (zos != null) zos.close(); if (bsuccess) { System.IO.File.Delete(zipFile + ".old"); System.IO.File.Move(zipFile, zipFile + ".old"); System.IO.File.Move(strNewFile, zipFile); } else System.IO.File.Delete(strNewFile); }
To make this tool more attractive I've added some improvements of my own: The first one to notice
is the usage of a checked list box that allows doing manual changes on the fly.
My favorite is the ability to edit the list of filter extensions that are bound to the CPZipStripper.exe.xml
file through a DataTable. Here is an edited snapshot of this file.
<configuration> <maskRow maskField="*.plg" / > <maskRow maskField=".opt" / > <maskRow maskField=".ncb" / > <maskRow maskField=".suo" / > <maskRow maskField="*.pdb" / > ...... </configuration>
Notice that in the application configuration file we keep not only the appSetttings node,
but also the files, paths, and most importantly the DataTable content.
Loading the data from this xml file in the respective lists and dataSet is easy:
XmlDocument xd = new XmlDocument(); xd.Load(cfgxmlpath); //use plain xml xpath for the rest m_paths.Clear(); XmlNode xnpath = xd["configuration"]["paths"]; if(xnpath!=null) { foreach(XmlNode xn in xnpath.ChildNodes) { m_paths.Add(xn.InnerXml); } } XmlNode xnfile = xd["configuration"]["files"]; if(xnfile!=null) { foreach(XmlNode xn in xnfile.ChildNodes) { m_files.Add(xn.InnerXml); } } //use the data set m_extensions.Clear(); _dataSet = new DataSet("configuration"); DataTable mytable = new DataTable("maskRow"); DataColumn exColumn = new DataColumn("maskField", Type.GetType("System.String"), null, MappingType.Attribute); mytable.Columns.Add(exColumn); _dataSet.Tables.Add(mytable); _dataSet.Tables[0].ReadXml(MainForm.cfgxmlpath); for (int i = 0; i < _dataSet.Tables[0].Rows.Count; i++) { DataRow row = _dataSet.Tables[0].Rows[i]; string val = row[0].ToString().ToLower(); if (val.Length > 0)//no empty mask { //.....code eliminated for brevity } else { //don't show empty rows row.Delete(); } } _dataSet.Tables[0].AcceptChanges();
Using WriteXml from the dataSet will eliminate the data that does not belong to the table.
For this reason we have to save it before calling WriteXml and restore it afterwards:
XmlDocument xd = new XmlDocument(); //get the original xd.Load(MainForm.cfgxmlpath); //save nodes not part of the dataset XmlNode xnpath = xd["configuration"]["paths"]; XmlNode xnfile = xd["configuration"]["files"]; XmlNode lastFolderpath = xd["configuration"]["LastUsedFolder"]; //write the masks _dataSet.WriteXml(MainForm.cfgxmlpath); //restore the old saved nodes xd.Load(MainForm.cfgxmlpath); if(xnpath != null) xd.DocumentElement.AppendChild(xnpath); if (xnfile != null) xd.DocumentElement.AppendChild(xnfile); if (lastFolderpath != null) xd.DocumentElement.AppendChild(lastFolderpath); xd.Save(MainForm.cfgxmlpath);
I'm not going to get into details on this one, but as you've already noticed in the xml snippet,
you can use * and ? chars.
It's a good ideea that the first thing you do when you open this application is setting the configuration.

I've added some new functionality in regards to using the context menu from Explorer.
You should start the exe only once before you can right click on the folder and zip it.
As a utility I consider version 2.x to be an improvement over the old one.
You can use it to some extent as a Winzip replacement, but it lacks features like encryption.
.Net 2.0 and J# package have to be installed on your machine to run it. If you have problems running
the exe alone,it might be because you are missing the J# distribution package or the .Net 2.0 runtime.
In that that's the case, I recommend you try to install this msi install file I've created or download
vjredist_32bit.zip and install it from locally.
General
News
Question
Answer
Joke
Rant
Admin
|
PermaLink |
Privacy |
Terms of Use
Last Updated: 16 May 2008 Editor: |
Copyright 2007 by dmihailescu Everything else Copyright © CodeProject, 1999-2009 Web22 | Advertise on the Code Project |