Create zips dynamically with variable files and folders

Nov 15, 2010 at 2:43 AM

I'm trying to implement DNZ into my app, but I'm having a hard time wrapping my head around the syntax (VB.NET). I've looked at all of the examples I could find in the library, and while they looked promising, ultimately did not return the desired results (usually just an empty zip file, or an error message). I tried all of the Zip.Add methods, and just wasn't having any luck. There has to be a way to get this to work, I just need help understanding it. I need someone who knows how it works to assist me.

 

The situations are variable (different folders, different file types, etc.). If I need to be more precise, let me know. But what I need it to do is this, all with one batch of code, if possible:

 

I need it to sometimes add all files in a folder.  --I got this working.

 

I need it to sometimes add certain subfolders within a folder -- I can't get this to work.

I do have the subfolders in an array, which I can loop through to obtain a line of code for each folder I need, however, I have not been able to get that to work, either. The proper values are returned on my array, but DNZ is rejecting them.

 

I need it to sometimes add certain file types, ie *.abc, and also an entire subfolder, all within the same base folder. In any case, wildcard support is imperative here.

 

Is DNZ going to work in my instance?

Coordinator
Nov 15, 2010 at 11:19 AM

DotNetZip can do what you describe, I think.

I won't be able to help you though, with the information you've provided.  I can help explain unexpected behavior with the use of the library.  I can explain and doagnose exception stacktraces.   But, if you say "I can't get this working" or "I get an error message", I won't be much help.

you'll need to be very specific about :

  1. what you are trying to do,
  2. the code you are using to accomplish same
  3. in the event of an error - the exception (including stacktrace) or error message you see.  In the event of surprising results (eg, the created zip file does not contain what you expect), a clear description of that, as well.   

I'll try to help but you will need to be very specific.  I suggest that, for the purposes of development, you separate your goals into independent items, and narrow your focus on one of those items at a time.  Rather than wrestling with "a bunch of things that are not working" at once, it's easier to solve a set of problems when you can focus on ONE at a time, each one very narrowly drawn. 

Regarding your desire to add selections of files and directories, there are 2 ways to proceed, in general:

  1. your code determines the exact list of files to include in the zip file, then calls a combination of ZipFile.AddFile() and ZipFile.AddFiles() to add those files to the zip file to be created. You could potentially use LINQ queries over the filesystem to accomplish this.
  2. You use ZipFile.AddSelectedFiles() and specify your selection criteria, including using filename wildcards, to DotNetZip.  Based on those criteria, DotNetZip determines the exact list of files to be added to the zip file.

Most of the documentation in DotNetZIp is reference doc - it describes how to use a particular object or method call.  This is helpful if you already know the object or method you want. If you are starting from a particular goal, though, this reference documentation is less useful.    There are, however, some examples geared toward tasks - how to accomplish a particular thing using DotNetZip.  See http://dotnetzip.codeplex.com/wikipage?title=VB-examples for those.

 

Nov 15, 2010 at 11:07 PM

For testing purposes, I built a folder with some files in it:

DNZtest (base folder)
DNZtest\test1.txt
DNZtest\test1.cfg
DNZtest\test1.ini
test2 (subfolder)
test2\test2.txt
test2\test2.cfg
test2\test2.ini
test3 (empty subfolder)

I can use these files to reproduce pretty much any situation that would be encountered.





First, I tried:

        Dim datapath As String = "C:\Users\Adam\Desktop\DNZtest"
        Dim itempaths As String() = New String() {datapath & "\test1.ini", _
                                                  datapath & "\test1.cfg", _
                                                  datapath & "\test1.txt", _
                                                  datapath & "\test2", _
                                                  datapath & "\test3"}
        Try
            Using Zip As ZipFile = New ZipFile(datapath & "\test.zip")
                Dim i As Integer
                For i = 0 To itempaths.Length - 1
                    Zip.AddItem(itempaths(i), "")
                Next i
                Zip.Save()
            End Using
        Catch ex As Exception
            MsgBox(ex.Message)
        End Try


This adds all files specified, but does not rebuild subdirectories within the zip structure.




Next:


        Dim datapath As String = "C:\Users\Adam\Desktop\DNZtest"

        Try
            Using Zip As ZipFile = New ZipFile(datapath & "\test.zip")
                Zip.AddSelectedFiles("name = *.cfg, name = *.txt", datapath, "", True)
                Zip.Save()
            End Using
        Catch ex As Exception
            MsgBox(ex.Message)
        End Try


All .txt files are zipped. Is there any way to specify more than one include/exclude?

Trying to specify a folder (name = ) yielded me an empty zip file.
I need to have this ability. Do I need to use Zip.AddDirectory if I want to grab a certain folder?

Coordinator
Nov 16, 2010 at 1:56 AM

Hi Adam,

in your first snip of code, you call ZipFile.AddItem(filename, "")

The second parameter there specifies the path to use within the zip file. You specified "", the empty string, which gives you a "flattened" set of files in the zip.  If you don't want to flatten the directory structure in the zip, then don't pass "" for that parameter.  You can opt for the single-param overload - ZipFile.AddItem(filename).  In this case DotNetZip will use the full path on the filename for the path within the zip.  In your case the path within the zip file would be users\Adam\Destkop\DNZTest and so on.  You want something other than either of those two options. You want the files to be in the toplevel, and the contents of the directories to be nested in directories. 

In that case, you should add the files and directories differently.  Like this:

        Dim datapath As String = "C:\Users\Adam\DNZTest"
        Dim filepaths As String() = New String() {"test1.ini", _
                                                  "test1.cfg", _
                                                  "test1.txt"}
        Dim dirpaths As String() = New String() {    "test2", _
                                                      "test3"}

        Using Zip As ZipFile = New ZipFile("test1.zip")
            Dim i As Integer
            For i = 0 To filepaths.Length - 1
                Zip.AddFile(Path.Combine(datapath, filepaths(i)), "")
            Next i
            For i = 0 To dirpaths.Length - 1
                Zip.AddDirectory(Path.Combine(datapath, dirpaths(i)), dirpaths(i))
            Next i
            Zip.Save()
        End Using

With your directory contents, this will give a zipfile with these contents:

test1.ini
test1.cfg
test1.txt
test2/
test2/test2.cfg
test2/test2.ini
test2/test2.txt
test3/

Regarding your use of AddSelectedFiles, yes, you can have more than one include or exclude. The criteria string uses boolean logic.  Like this:

        Dim datapath As String = "C:\Users\Adam\DNZtest"
        Using Zip As ZipFile = New ZipFile("test2.zip")
            Zip.AddSelectedfiles("(name = *.cfg) or (name = *.txt)", datapath, "", True)
            Zip.Save()
        End Using

You can omit the parens in simple criteria specifier strings.  With your directory contents, the code above will give a zip file with these contents:

test1.cfg
test1.txt
test2/test2.cfg
test2/test2.txt
Nov 16, 2010 at 3:04 AM

Thank you, that information is very helpful. It looks like AddSelectedFiles would work for me about 90% of the time. But for that other 10%;

 

Is there any way for AddSelectedFiles to recognize directories? (name = dir1), etc.

If not, why isn't there an option like that? An all inclusive option, I mean. A coding nightmare? If there is no "." in the name, it's a folder. It's easy to say that, not always so easy to code, though.

 

If you cannot recognize directories with AddSelectedFiles, is it possible to exclude directories with AddDirectory, in a similar fashion to AddSelectedFiles? I seem to either be doing it incorrectly (syntax issue), or it's not possible. My code is below.

 

Here is an instance where I need this: Zip.AddDirectory("(name != Flat - *)", datapath & "\skins", "skins")

 

I need to grab a directory named skins, but exclude the folder matching mask "Flat - *" within the skins folder. But I need to grab all other folders inside skins. Basically, the same concept as AddSelectedFiles, only with folders.

 

DNZ seems to be very powerful if one understands how to use it, but I think maybe for large batch operations with a lot of variables, it can tend to get a bit cumbersome.

Coordinator
Nov 16, 2010 at 8:52 AM
Aerowinder wrote:

Thank you, that information is very helpful. It looks like AddSelectedFiles would work for me about 90% of the time. But for that other 10%;

 Aerowinder, you're telling me, once again "it doesn't work" but you're not telling me why or how.  I can't help you with the information you're giving me.

If there is no "." in the name, it's a folder. It's easy to say that, not always so easy to code, though.

Your assumption is not valid. NTFS allows directories to include dots in their names.  You can name a directory "this.is.a.directory" .  Even so, i don't understand the point of your proposal - you suggest that AddSelectedFiles should distinguish between directories and files.  I don't understand the problem that would solve or avoid.

I need to grab a directory named skins, but exclude the folder matching mask "Flat - *" within the skins folder. But I need to grab all other folders inside skins.

Ok, I detect a problem statement there.  You can do what you want specifying paths for the name constraint in the criteria string. like this:

   Using Zip As ZipFile = New ZipFile(zipfile)
       Zip.AddSelectedFiles("name != '*\excludethis - *\*.*'", datapath, "", True)
       Zip.Save()
   End Using

I think maybe for large batch operations with a lot of variables, it can tend to get a bit cumbersome..

Adam: newsflash: I'm the guy that wrote DotNetZip. You found it and you're trying to use it and I'm trying to be helpful. If you find it cumbersome, it seems like good choices for you would be: (a) find another library, (b) live with it, or (c) make concrete suggestions for improvements.  Criticizing it to the author, while asking for help from him,... well that doesn't seem like a strategy you would find in "How to Win Friends and Influence People."  Just my perspective.

The AddSelectedFiles method is described carefully in the documentation.  There are example code snips showing how to use it.  Maybe you missed these?  Could it be that before passing judgement on the library you need to put a little more time in?  I know, I know... everyone wants everything to work instantly.  Sometimes it takes time to understand how to do something you want, with a new tool.

Good day, sir.

Nov 19, 2010 at 10:44 PM

I would like to apologize for what I said the other day. While I did not intend for my comments to be virulent, I understand how it might be interpreted that way. If I was in your position, I would have likely interpreted it just as you did.

 

I am now using this code:

        Try
            Using Zip As ZipFile = New ZipFile(Path.Combine(bwSavePath, bwAsset) & ".zip")
                Zip.UpdateSelectedFiles(DNZMask, arrDataLoc(bwIndex), "", True)
                Zip.Save()
            End Using
        Catch ex As Exception
            MsgBox(ex.Message)
        End Try

This code zips the proper files 100% of the time. The variables shouldn't be too important here because my question is in regards to the UpdateSelectedFiles method. Is there any way to force DNZ to respect folder case, and include empty folders using UpdateSelectedFiles? As it is, if I use (name = *\Directory1\*.*), this folder will be named "directory1" within the zip file. If that folder happens to be empty, it will not be included within the zip file. This is likely intended behavior.

 

I understand that UpdateSelectedFiles is clearly intended for files only. But I thought I would make sure I wasn't missing any options before I tried something else. I wasn't able to find any information on this issue. After looking at the other methods available, it looks like AddItem would work. Or perhaps a combination of AddDirectory and AddFiles, like you suggested earlier.

Coordinator
Nov 20, 2010 at 1:19 PM
Edited Nov 20, 2010 at 1:40 PM

No problem Aerowinder. 

I noticed the other day the same thing - that the case of folders was not being preserved in this method.

I think this is a bug in the library.  There's no test for this in the list of test cases I run before each reelease.

I'll file a workitem. Til I fix it, I can't think of a way you can avoid this problem.

Also - regarding the behavior of the library to NOT add directories into the zip that are empty... yes that behavior was by design, sort of arbitrarily decided by me.  My thinking at the time, as you alluded to, was that the {Add,Update}SelectedFiles methods were dealing with files, and if there were no files in a directory then the directory wouldn't get added.  Turns out that is surprising to some people.

You're not the first to have asked for that behavior to default the other way - to add empty directories. 

It's not as straightforward as it seems initially.  In the case of an empty directory, some users might prefer that the directory not be included in the library, and some might prefer the other way. This is the simple case.  Now imagine a directory in the filesystem that is not empty, yet the file selection criteria doesn't match for any file in the directory.  Should *that* directory be added to the zip?   Now take it a step further. Suppose there is a nested directory structure in the filesystem, nested 10 levels deep, and none of the files in that directory subtree are selected with the criteria.  Should this entire directory subtree be replicated into the zip file, empty, with no files in any of these subdirs? 

I thought a little about how a developer might express just what is intended here, but I didn't come up with an easy, obvious solution. When I say "easy" I don't mean easy to code, I mean easy to explain and understand and use.  So I'm still thinking about that.

In the meantime, .... I'll file a workitem on this as well. 

One way to possibly get what you want is to call AddDirectory, and add all the files in a directory.  Then, before saving the zip file,  use LINQ to select entries in the zip and remove them, or call RemoveSelectEntries .  This might work.

 

 

 

 

Coordinator
Nov 20, 2010 at 1:23 PM
This discussion has been copied to a work item. Click here to go to the work item and continue the discussion.
Coordinator
Nov 20, 2010 at 1:49 PM
This discussion has been copied to a work item. Click here to go to the work item and continue the discussion.