Improve progress callback.

- Pass zip archive and user provided context (void pointer) into callback.
- Optional free function for context.
diff --git a/lib/CMakeLists.txt b/lib/CMakeLists.txt
index 148455d..2fe891d 100644
--- a/lib/CMakeLists.txt
+++ b/lib/CMakeLists.txt
@@ -111,6 +111,7 @@
   zip_name_locate.c
   zip_new.c
   zip_open.c
+  zip_progress.c
   zip_rename.c
   zip_replace.c
   zip_set_archive_comment.c
diff --git a/lib/Makefile.am b/lib/Makefile.am
index 524f418..e631db1 100644
--- a/lib/Makefile.am
+++ b/lib/Makefile.am
@@ -87,6 +87,7 @@
 	zip_name_locate.c \
 	zip_new.c \
 	zip_open.c \
+	zip_progress.c \
 	zip_rename.c \
 	zip_replace.c \
 	zip_set_archive_comment.c \
diff --git a/lib/zip.h b/lib/zip.h
index e8c9eaa..5de1a9e 100644
--- a/lib/zip.h
+++ b/lib/zip.h
@@ -309,10 +309,12 @@
 typedef zip_uint32_t zip_flags_t;
 
 typedef zip_int64_t (*zip_source_callback)(void *, void *, zip_uint64_t, zip_source_cmd_t);
-typedef void (*zip_progress_callback_t)(double);
-
+typedef void (*zip_progress_callback)(zip_t *, double, void *);
 
 #ifndef ZIP_DISABLE_DEPRECATED
+typedef void (*zip_progress_callback_t)(double);
+ZIP_EXTERN void zip_register_progress_callback(zip_t *, zip_progress_callback_t);
+
 ZIP_EXTERN zip_int64_t zip_add(zip_t *, const char *, zip_source_t *); /* use zip_file_add */
 ZIP_EXTERN zip_int64_t zip_add_dir(zip_t *, const char *); /* use zip_dir_add */
 ZIP_EXTERN const char *zip_get_file_comment(zip_t *, zip_uint64_t, int *, int); /* use zip_file_get_comment */
@@ -378,7 +380,7 @@
 ZIP_EXTERN zip_int64_t zip_name_locate(zip_t *, const char *, zip_flags_t);
 ZIP_EXTERN zip_t *zip_open(const char *, int, int *);
 ZIP_EXTERN zip_t *zip_open_from_source(zip_source_t *, int, zip_error_t *);
-ZIP_EXTERN void zip_register_progress_callback(zip_t *, zip_progress_callback_t);
+ZIP_EXTERN int zip_register_progress_callback_with_state(zip_t *, double, zip_progress_callback, void (*)(void *), void *);
 ZIP_EXTERN int zip_set_archive_comment(zip_t *, const char *, zip_uint16_t);
 ZIP_EXTERN int zip_set_archive_flag(zip_t *, zip_flags_t, int);
 ZIP_EXTERN int zip_set_default_password(zip_t *, const char *);
diff --git a/lib/zip_close.c b/lib/zip_close.c
index 3180e15..3a2ff0a 100644
--- a/lib/zip_close.c
+++ b/lib/zip_close.c
@@ -51,18 +51,10 @@
 #endif
 
 
-typedef struct {
-    double last_update;  /* last value callback function was called with */
-
-    double start;        /* start of sub-progress setcion */
-    double end;          /* end of sub-progress setcion */
-} progress_state_t;
-
-static int add_data(zip_t *, zip_source_t *, zip_dirent_t *, progress_state_t *);
-static int copy_data(zip_t *, zip_uint64_t, progress_state_t *);
-static int copy_source(zip_t *, zip_source_t *, progress_state_t *, zip_int64_t);
+static int add_data(zip_t *, zip_source_t *, zip_dirent_t *);
+static int copy_data(zip_t *, zip_uint64_t);
+static int copy_source(zip_t *, zip_source_t *, zip_int64_t);
 static int write_cdir(zip_t *, const zip_filelist_t *, zip_uint64_t);
-static void _zip_progress(zip_t *, progress_state_t *, double);
 
 ZIP_EXTERN int
 zip_close(zip_t *za)
@@ -72,7 +64,6 @@
     int error;
     zip_filelist_t *filelist;
     int changed;
-    progress_state_t progress_state;
 
     if (za == NULL)
 	return -1;
@@ -129,22 +120,15 @@
 	free(filelist);
 	return -1;
     }
-    
-    if (za->progress_callback) {
-	progress_state.last_update = 0.0;
-	za->progress_callback(0.0);
-    }
+
+    _zip_progress_start(za->progress);
     error = 0;
     for (j=0; j<survivors; j++) {
 	int new_data;
 	zip_entry_t *entry;
 	zip_dirent_t *de;
 
-	if (za->progress_callback) {
-	    progress_state.start = (double)j / (double)survivors;
-	    progress_state.end = (double)(j+1) / (double)survivors;
-	    _zip_progress(za, &progress_state, 0.0);
-	}
+        _zip_progress_subrange(za->progress, (double)j / (double)survivors, (double)(j+1) / (double)survivors);
 
 	i = filelist[j].idx;
 	entry = za->entry+i;
@@ -184,7 +168,7 @@
 	    }
 
 	    /* add_data writes dirent */
-	    if (add_data(za, zs ? zs : entry->source, de, &progress_state) < 0) {
+	    if (add_data(za, zs ? zs : entry->source, de) < 0) {
 		error = 1;
 		if (zs)
 		    zip_source_free(zs);
@@ -211,7 +195,7 @@
 		error = 1;
 		break;
 	    }
-	    if (copy_data(za, de->comp_size, &progress_state) < 0) {
+	    if (copy_data(za, de->comp_size) < 0) {
 		error = 1;
 		break;
 	    }
@@ -237,9 +221,7 @@
 	return -1;
     }
 
-    if (za->progress_callback) {
-	_zip_progress(za, &progress_state, 1.0);
-    }
+    _zip_progress_end(za->progress);
 
     zip_discard(za);
     
@@ -248,7 +230,7 @@
 
 
 static int
-add_data(zip_t *za, zip_source_t *src, zip_dirent_t *de, progress_state_t *progress_state)
+add_data(zip_t *za, zip_source_t *src, zip_dirent_t *de)
 {
     zip_int64_t offstart, offdata, offend, data_length;
     struct zip_stat st;
@@ -430,7 +412,7 @@
         return -1;
     }
 
-    ret = copy_source(za, src_final, progress_state, data_length);
+    ret = copy_source(za, src_final, data_length);
 	
     if (zip_source_stat(src_final, &st) < 0) {
 	_zip_error_set_from_source(&za->error, src_final);
@@ -495,7 +477,7 @@
 
 
 static int
-copy_data(zip_t *za, zip_uint64_t len, progress_state_t *progress_state)
+copy_data(zip_t *za, zip_uint64_t len)
 {
     zip_uint8_t buf[BUFSIZE];
     size_t n;
@@ -512,10 +494,8 @@
 	}
 	
 	len -= n;
-	
-	if (za->progress_callback) {
-	    _zip_progress(za, progress_state, (total - (double)len) / total);
-	}
+
+        _zip_progress_update(za->progress, (total - (double)len) / total);
     }
 
     return 0;
@@ -523,7 +503,7 @@
 
 
 static int
-copy_source(zip_t *za, zip_source_t *src, progress_state_t *progress_state, zip_int64_t data_length)
+copy_source(zip_t *za, zip_source_t *src, zip_int64_t data_length)
 {
     zip_uint8_t buf[BUFSIZE];
     zip_int64_t n, current;
@@ -541,9 +521,9 @@
 	    ret = -1;
 	    break;
 	}
-	if (n == sizeof(buf) && za->progress_callback && data_length > 0) {
+	if (n == sizeof(buf) && za->progress && data_length > 0) {
 	    current += n;
-	    _zip_progress(za, progress_state, (double)current/(double)data_length);
+	    _zip_progress_update(za->progress, (double)current/(double)data_length);
 	}
     }
     
@@ -602,20 +582,3 @@
 
     return changed;
 }
-
-static void
-_zip_progress(zip_t *za, progress_state_t *progress_state, double sub_current)
-{
-    double current;
-
-    if (za->progress_callback == NULL) {
-	return;
-    }
-
-    current = ZIP_MIN(ZIP_MAX(sub_current, 0.0), 1.0) * (progress_state->end - progress_state->start) + progress_state->start;
-
-    if (current - progress_state->last_update > 0.001) {
-	za->progress_callback(current);
-	progress_state->last_update = current;
-    }
-}
diff --git a/lib/zip_discard.c b/lib/zip_discard.c
index ef891e3..93bdf3a 100644
--- a/lib/zip_discard.c
+++ b/lib/zip_discard.c
@@ -71,6 +71,8 @@
     }
     free(za->open_source);
 
+    _zip_progress_free(za->progress);
+
     zip_error_fini(&za->error);
     
     free(za);
diff --git a/lib/zip_new.c b/lib/zip_new.c
index a257a0d..96ece2d 100644
--- a/lib/zip_new.c
+++ b/lib/zip_new.c
@@ -68,7 +68,7 @@
     za->entry = NULL;
     za->nopen_source = za->nopen_source_alloc = 0;
     za->open_source = NULL;
-    za->progress_callback = NULL;
+    za->progress = NULL;
     
     return za;
 }
diff --git a/lib/zip_open.c b/lib/zip_open.c
index e4e9d97..265eb2e 100644
--- a/lib/zip_open.c
+++ b/lib/zip_open.c
@@ -157,12 +157,6 @@
     }
 }
 
-ZIP_EXTERN void
-zip_register_progress_callback(zip_t *za, zip_progress_callback_t progress_callback)
-{
-    za->progress_callback = progress_callback;
-}
-
 
 zip_t *
 _zip_open(zip_source_t *src, unsigned int flags, zip_error_t *error)
diff --git a/lib/zip_progress.c b/lib/zip_progress.c
new file mode 100644
index 0000000..b1b6e58
--- /dev/null
+++ b/lib/zip_progress.c
@@ -0,0 +1,184 @@
+/*
+ zip_progress.c -- progress reporting
+ Copyright (C) 2017 Dieter Baron and Thomas Klausner
+
+ This file is part of libzip, a library to manipulate ZIP archives.
+ The authors can be contacted at <libzip@nih.at>
+
+ Redistribution and use in source and binary forms, with or without
+ modification, are permitted provided that the following conditions
+ are met:
+ 1. Redistributions of source code must retain the above copyright
+ notice, this list of conditions and the following disclaimer.
+ 2. Redistributions in binary form must reproduce the above copyright
+ notice, this list of conditions and the following disclaimer in
+ the documentation and/or other materials provided with the
+ distribution.
+ 3. The names of the authors may not be used to endorse or promote
+ products derived from this software without specific prior
+ written permission.
+
+ THIS SOFTWARE IS PROVIDED BY THE AUTHORS ``AS IS'' AND ANY EXPRESS
+ OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
+ WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
+ ARE DISCLAIMED.  IN NO EVENT SHALL THE AUTHORS BE LIABLE FOR ANY
+ DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
+ DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE
+ GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
+ INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER
+ IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR
+ OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN
+ IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+
+#include <stdlib.h>
+
+
+#define _ZIP_COMPILING_DEPRECATED
+#include "zipint.h"
+
+struct zip_progress {
+    zip_t *za;
+    zip_progress_callback callback;
+    void (*ud_free)(void *);
+
+    void *ud;
+
+    double precision;
+
+    /* state */
+    double last_update;  /* last value callback function was called with */
+
+    double start;        /* start of sub-progress section */
+    double end;          /* end of sub-progress section */
+};
+
+
+void
+_zip_progress_end(zip_progress_t *progress) {
+    _zip_progress_update(progress, 1.0);
+}
+
+
+void
+_zip_progress_free(zip_progress_t *progress) {
+    if (progress == NULL) {
+        return;
+    }
+
+    if (progress->ud_free) {
+        progress->ud_free(progress->ud);
+    }
+
+    free(progress);
+}
+
+
+zip_progress_t *
+_zip_progress_new(zip_t *za, double precision, zip_progress_callback callback, void (*ud_free)(void *), void *ud) {
+    zip_progress_t *progress = (zip_progress_t *)malloc(sizeof(*progress));
+
+    if (progress == NULL) {
+        zip_error_set(&za->error, ZIP_ER_MEMORY, 0);
+        return NULL;
+    }
+
+    progress->za = za;
+    progress->callback = callback;
+    progress->ud_free = ud_free;
+    progress->ud = ud;
+    progress->precision = precision;
+
+    return progress;
+}
+
+
+void
+_zip_progress_start(zip_progress_t *progress) {
+    if (progress == NULL) {
+        return;
+    }
+
+    progress->last_update = 0.0;
+    progress->callback(progress->za, 0.0, progress->ud);
+}
+
+
+void
+_zip_progress_subrange(zip_progress_t *progress, double start, double end) {
+    if (progress == NULL) {
+        return;
+    }
+
+    progress->start = start;
+    progress->end = end;
+
+    _zip_progress_update(progress, 0.0);
+}
+
+void
+_zip_progress_update(zip_progress_t *progress, double sub_current) {
+    double current;
+
+    if (progress == NULL) {
+        return;
+    }
+
+    current = ZIP_MIN(ZIP_MAX(sub_current, 0.0), 1.0) * (progress->end - progress->start) + progress->start;
+
+    if (current - progress->last_update > progress->precision) {
+        progress->callback(progress->za, current, progress->ud);
+        progress->last_update = current;
+    }
+}
+
+
+ZIP_EXTERN int
+zip_register_progress_callback_with_state(zip_t *za, double precision, zip_progress_callback callback, void (*ud_free)(void *), void *ud) {
+    zip_progress_t *progress = NULL;
+
+    if (callback != NULL) {
+        if ((progress = _zip_progress_new(za, precision, callback, ud_free, ud)) == NULL) {
+            return -1;
+        }
+    }
+
+    _zip_progress_free(za->progress);
+    za->progress = progress;
+
+    return 0;
+}
+
+
+struct legacy_ud {
+    zip_progress_callback_t callback;
+};
+
+
+static void
+_zip_legacy_progress_callback(zip_t *za, double progress, void *vud) {
+    struct legacy_ud *ud = (struct legacy_ud *)vud;
+
+    ud->callback(progress);
+}
+
+ZIP_EXTERN void
+zip_register_progress_callback(zip_t *za, zip_progress_callback_t progress_callback)
+{
+    struct legacy_ud *ud;
+    
+    if (progress_callback == NULL) {
+        zip_register_progress_callback_with_state(za, 0, NULL, NULL, NULL);
+    }
+
+    if ((ud = (struct legacy_ud *)malloc(sizeof(*ud))) == NULL) {
+        return;
+    }
+
+    ud->callback = progress_callback;
+
+    if (zip_register_progress_callback_with_state(za, 0.001, _zip_legacy_progress_callback, free, ud) < 0) {
+        free(ud);
+    }
+}
diff --git a/lib/zipint.h b/lib/zipint.h
index 6883c3a..9e30585 100644
--- a/lib/zipint.h
+++ b/lib/zipint.h
@@ -197,6 +197,7 @@
 typedef enum zip_encoding_type zip_encoding_type_t;
 
 struct zip_hash;
+struct zip_progress;
 
 typedef struct zip_cdir zip_cdir_t;
 typedef struct zip_dirent zip_dirent_t;
@@ -205,6 +206,7 @@
 typedef struct zip_string zip_string_t;
 typedef struct zip_buffer zip_buffer_t;
 typedef struct zip_hash zip_hash_t;
+typedef struct zip_progress zip_progress_t;
 
 /* zip archive, part of API */
 
@@ -232,7 +234,7 @@
 
     zip_hash_t *names;			/* hash table for name lookup */
 
-    zip_progress_callback_t progress_callback; /* progress callback for zip_close() */
+    zip_progress_t *progress;            /* progress callback for zip_close() */
 };
 
 /* file in zip archive, part of API */
@@ -488,6 +490,13 @@
 
 zip_t *_zip_open(zip_source_t *, unsigned int, zip_error_t *);
 
+void _zip_progress_end(zip_progress_t *progress);
+void _zip_progress_free(zip_progress_t *progress);
+zip_progress_t *_zip_progress_new(zip_t *za, double precision, zip_progress_callback callback, void (*ud_free)(void *), void *ud);
+void _zip_progress_start(zip_progress_t *progress);
+void _zip_progress_subrange(zip_progress_t *progress, double start, double end);
+void _zip_progress_update(zip_progress_t *progress, double value);
+
 bool zip_random(zip_uint8_t *buffer, zip_uint16_t length);
 
 int _zip_read(zip_source_t *src, zip_uint8_t *data, zip_uint64_t length, zip_error_t *error);
diff --git a/src/ziptool.c b/src/ziptool.c
index 6c3f25e..99b27ff 100644
--- a/src/ziptool.c
+++ b/src/ziptool.c
@@ -402,13 +402,13 @@
 }
 
 static void
-progress_callback(double percentage) {
+progress_callback(zip_t *za, double percentage, void *ud) {
     printf("%.1lf%% done\n", percentage*100);
 }
 
 static int
 print_progress(int argc, char *argv[]) {
-    zip_register_progress_callback(za, progress_callback);
+    zip_register_progress_callback_with_state(za, 0.001, progress_callback, NULL, NULL);
     return 0;
 }
 
diff --git a/xcode/libzip.xcodeproj/project.pbxproj b/xcode/libzip.xcodeproj/project.pbxproj
index 31e4288..e407e7f 100644
--- a/xcode/libzip.xcodeproj/project.pbxproj
+++ b/xcode/libzip.xcodeproj/project.pbxproj
@@ -235,6 +235,8 @@
 		4BD6CB6619E71CD100710654 /* libz.dylib in Frameworks */ = {isa = PBXBuildFile; fileRef = 4B01D70815B2F4CF002D5007 /* libz.dylib */; };
 		4BD6CB6719E71CD100710654 /* libzip.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 4B01D68B15B2F3F1002D5007 /* libzip.framework */; };
 		4BD6CB6F19E71D6900710654 /* hole.c in Sources */ = {isa = PBXBuildFile; fileRef = 4BD6CB5E19E71B3B00710654 /* hole.c */; };
+		4BD708791EB1CF73003F351F /* zip_progress.c in Sources */ = {isa = PBXBuildFile; fileRef = 4BD708781EB1CF73003F351F /* zip_progress.c */; };
+		4BD7087A1EB1CF73003F351F /* zip_progress.c in Sources */ = {isa = PBXBuildFile; fileRef = 4BD708781EB1CF73003F351F /* zip_progress.c */; };
 		4BDC724415B1B25E00236D3C /* zip_add_dir.c in Sources */ = {isa = PBXBuildFile; fileRef = 4BDC71F115B1B25E00236D3C /* zip_add_dir.c */; };
 		4BDC724515B1B25E00236D3C /* zip_add_entry.c in Sources */ = {isa = PBXBuildFile; fileRef = 4BDC71F215B1B25E00236D3C /* zip_add_entry.c */; };
 		4BDC724615B1B25E00236D3C /* zip_add.c in Sources */ = {isa = PBXBuildFile; fileRef = 4BDC71F315B1B25E00236D3C /* zip_add.c */; };
@@ -813,6 +815,7 @@
 		4BD6CB5C19E6A5D900710654 /* source_hole.c */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.c; path = source_hole.c; sourceTree = "<group>"; };
 		4BD6CB5E19E71B3B00710654 /* hole.c */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.c; path = hole.c; sourceTree = "<group>"; };
 		4BD6CB6C19E71CD100710654 /* hole */ = {isa = PBXFileReference; explicitFileType = "compiled.mach-o.executable"; includeInIndex = 0; path = hole; sourceTree = BUILT_PRODUCTS_DIR; };
+		4BD708781EB1CF73003F351F /* zip_progress.c */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.c; path = zip_progress.c; sourceTree = "<group>"; };
 		4BDC71E015B182B200236D3C /* libzip_iOS.framework */ = {isa = PBXFileReference; explicitFileType = wrapper.cfbundle; includeInIndex = 0; path = libzip_iOS.framework; sourceTree = BUILT_PRODUCTS_DIR; };
 		4BDC71F115B1B25E00236D3C /* zip_add_dir.c */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.c; name = zip_add_dir.c; path = ../lib/zip_add_dir.c; sourceTree = "<group>"; };
 		4BDC71F215B1B25E00236D3C /* zip_add_entry.c */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.c; name = zip_add_entry.c; path = ../lib/zip_add_entry.c; sourceTree = "<group>"; };
@@ -1372,6 +1375,7 @@
 				4BDC721D15B1B25E00236D3C /* zip_name_locate.c */,
 				4BDC721E15B1B25E00236D3C /* zip_new.c */,
 				4BDC721F15B1B25E00236D3C /* zip_open.c */,
+				4BD708781EB1CF73003F351F /* zip_progress.c */,
 				736ED9B81E3D688C00C36873 /* zip_random_unix.c */,
 				4BDC722015B1B25E00236D3C /* zip_rename.c */,
 				4BDC722115B1B25E00236D3C /* zip_replace.c */,
@@ -1797,6 +1801,7 @@
 				4B01D6A915B2F46B002D5007 /* zip_close.c in Sources */,
 				4B01D6AA15B2F46B002D5007 /* zip_delete.c in Sources */,
 				4B01D6AB15B2F46B002D5007 /* zip_dir_add.c in Sources */,
+				4BD7087A1EB1CF73003F351F /* zip_progress.c in Sources */,
 				4B01D6AC15B2F46B002D5007 /* zip_dirent.c in Sources */,
 				4B01D6AD15B2F46B002D5007 /* zip_discard.c in Sources */,
 				4B0454B81E8E3E02002FA1F9 /* zip_source_get_compression_flags.c in Sources */,
@@ -1979,6 +1984,7 @@
 				736ED9C11E3D6B8300C36873 /* zip_source_winzip_aes_decode.c in Sources */,
 				4BDC724715B1B25E00236D3C /* zip_close.c in Sources */,
 				4BDC724815B1B25E00236D3C /* zip_delete.c in Sources */,
+				4BD708791EB1CF73003F351F /* zip_progress.c in Sources */,
 				736ED9C21E3D6B8600C36873 /* zip_source_winzip_aes_encode.c in Sources */,
 				4BDC724915B1B25E00236D3C /* zip_dir_add.c in Sources */,
 				4B0454B91E8E3E03002FA1F9 /* zip_source_get_compression_flags.c in Sources */,