Provide more detailed progress information.
diff --git a/NEWS.md b/NEWS.md
index 97bf2de..53f98cf 100644
--- a/NEWS.md
+++ b/NEWS.md
@@ -1,3 +1,8 @@
+1.x.x [201X-XX-XX]
+==================
+
+* Improve file progress callback code.
+
 1.2.0 [2017-02-19]
 ==================
 
diff --git a/lib/zip_close.c b/lib/zip_close.c
index 88fa444..83a38a3 100644
--- a/lib/zip_close.c
+++ b/lib/zip_close.c
@@ -54,11 +54,18 @@
 /* max deflate size increase: size + ceil(size/16k)*5+6 */
 #define MAX_DEFLATE_SIZE_32	4293656963u
 
-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 *);
-static int write_cdir(zip_t *, const zip_filelist_t *, zip_uint64_t);
+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 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)
@@ -68,6 +75,7 @@
     int error;
     zip_filelist_t *filelist;
     int changed;
+    progress_state_t progress_state;
 
     if (za == NULL)
 	return -1;
@@ -125,6 +133,10 @@
 	return -1;
     }
     
+    if (za->progress_callback) {
+	progress_state.last_update = 0.0;
+	za->progress_callback(0.0);
+    }
     error = 0;
     for (j=0; j<survivors; j++) {
 	int new_data;
@@ -132,7 +144,9 @@
 	zip_dirent_t *de;
 
 	if (za->progress_callback) {
-	    za->progress_callback((double)j/survivors);
+	    progress_state.start = (double)j / survivors;
+	    progress_state.end = (double)(j+1) / survivors;
+	    _zip_progress(za, &progress_state, 0.0);
 	}
 
 	i = filelist[j].idx;
@@ -173,7 +187,7 @@
 	    }
 
 	    /* add_data writes dirent */
-	    if (add_data(za, zs ? zs : entry->source, de) < 0) {
+	    if (add_data(za, zs ? zs : entry->source, de, &progress_state) < 0) {
 		error = 1;
 		if (zs)
 		    zip_source_free(zs);
@@ -200,7 +214,7 @@
 		error = 1;
 		break;
 	    }
-	    if (copy_data(za, de->comp_size) < 0) {
+	    if (copy_data(za, de->comp_size, &progress_state) < 0) {
 		error = 1;
 		break;
 	    }
@@ -226,8 +240,8 @@
 	return -1;
     }
 
-    if (za->progress_callback) {
-	za->progress_callback(1);
+    if (za->progress_callback && progress_state.last_update < 1.0) {
+	za->progress_callback(1.0);
     }
 
     zip_discard(za);
@@ -237,16 +251,16 @@
 
 
 static int
-add_data(zip_t *za, zip_source_t *src, zip_dirent_t *de)
+add_data(zip_t *za, zip_source_t *src, zip_dirent_t *de, progress_state_t *progress_state)
 {
-    zip_int64_t offstart, offdata, offend;
+    zip_int64_t offstart, offdata, offend, data_length;
     struct zip_stat st;
     zip_source_t *src_final, *src_tmp;
     int ret;
     int is_zip64;
     zip_flags_t flags;
     bool needs_recompress, needs_decompress, needs_crc, needs_compress, needs_reencrypt, needs_decrypt, needs_encrypt;
-    
+
     if (zip_source_stat(src, &st) < 0) {
 	_zip_error_set_from_source(&za->error, src);
 	return -1;
@@ -275,10 +289,14 @@
 
     flags = ZIP_EF_LOCAL;
 
-    if ((st.valid & ZIP_STAT_SIZE) == 0)
+    if ((st.valid & ZIP_STAT_SIZE) == 0) {
 	flags |= ZIP_FL_FORCE_ZIP64;
+	data_length = -1;
+    }
     else {
 	de->uncomp_size = st.size;
+	/* this is technically incorrect (copy_source counts compressed data), but it's the best we have */
+	data_length = (zip_int64_t)st.size;
 	
 	if ((st.valid & ZIP_STAT_COMP_SIZE) == 0) {
 	    if (( ((de->comp_method == ZIP_CM_DEFLATE || ZIP_CM_IS_DEFAULT(de->comp_method)) && st.size > MAX_DEFLATE_SIZE_32)
@@ -404,7 +422,7 @@
         return -1;
     }
 
-    ret = copy_source(za, src_final);
+    ret = copy_source(za, src_final, progress_state, data_length);
 	
     if (zip_source_stat(src_final, &st) < 0) {
 	ret = -1;
@@ -461,10 +479,11 @@
 
 
 static int
-copy_data(zip_t *za, zip_uint64_t len)
+copy_data(zip_t *za, zip_uint64_t len, progress_state_t *progress_state)
 {
     zip_uint8_t buf[BUFSIZE];
     size_t n;
+    double total = (double)len;
 
     while (len > 0) {
 	n = len > sizeof(buf) ? sizeof(buf) : len;
@@ -477,6 +496,10 @@
 	}
 	
 	len -= n;
+	
+	if (za->progress_callback) {
+	    _zip_progress(za, progress_state, (total - len) / total);
+	}
     }
 
     return 0;
@@ -484,10 +507,10 @@
 
 
 static int
-copy_source(zip_t *za, zip_source_t *src)
+copy_source(zip_t *za, zip_source_t *src, progress_state_t *progress_state, zip_int64_t data_length)
 {
     zip_uint8_t buf[BUFSIZE];
-    zip_int64_t n;
+    zip_int64_t n, current;
     int ret;
 
     if (zip_source_open(src) < 0) {
@@ -496,11 +519,16 @@
     }
 
     ret = 0;
+    current = 0;
     while ((n=zip_source_read(src, buf, sizeof(buf))) > 0) {
 	if (_zip_write(za, buf, (zip_uint64_t)n) < 0) {
 	    ret = -1;
 	    break;
 	}
+	if (n == sizeof(buf) && za->progress_callback && data_length > 0) {
+	    current += n;
+	    _zip_progress(za, progress_state, current/(double)data_length);
+	}
     }
     
     if (n < 0) {
@@ -559,3 +587,20 @@
 
     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;
+    }
+}