'How to show progress of zip download without zip file existing before hand but knowing the file sizes

I have logic that downloads a group of files as a zip. The issue is there is no progress so the user does not know how far along the download is.

This Zip file doesn't exist before hand, the user selects the files they want to download and then I use the SharpZipLib nuget package to create a zip and stream it to the response.

It seems I need to set the Content-Length header for the browser to show a total size progress indicator. The issue I'm having is it seems this value has to be exact, if its too low or too high by 1 byte the file does not get downloaded properly. I can get an approximate end value size by adding all the files size together and setting there to be no compressions level but I don't see a way I can calculate the final zip size exactly.

I hoped I could of just overesitmated the final size a bit and the browser would allow that but that doesn't work, the file isn't downloaded properly so you cant access it.

Here are some possible solution I've come up with but they have there own issues.

1 - I can create the zip on the server first and then stream it, therefore knowing the exact size I can set the Content-length. Issue with this is the user will have to wait for all the files to be streamed to the web server, the zip to be created and then I can start streaming it to the user. While this is going on the user wont even see the file download as being started. This also results in more memory usage of the web server as it has to persist the entire zip file in memory.

2 - I can come up with my own progress UI, I will use the combined file sizes to get a rough final size estimation and then as the files are streamed I push updates to the user via signalR indicating the progress.

3- I show the user the total file size before download begins, this way they will at least have a way to assess themselves how far along it is. But the browser has no indication of how far along it is so if they may forget and when they look at the browser download progress there will be no indication how far along it is

These all have their own drawbacks. Is there a better way do this, ideally so its all handled by the browser?

Below is my ZipFilesToRepsonse method. It uses some objects that aren't shown here for simplicity sake. It also streams the files from azure blob storage

        public void ZipFilesToResponse(HttpResponseBase response, IEnumerable<Tuple<string,string>> filePathNames, string zipFileName)
    {
            using (var zipOutputStream = new ZipOutputStream(response.OutputStream))
            {
                zipOutputStream.SetLevel(0); // 0 - store only to 9 - means best compression
                response.BufferOutput = false;
                response.AddHeader("Content-Disposition", "attachment; filename=" + zipFileName);
                response.ContentType = "application/octet-stream";
              
                Dictionary<string,long> sizeDictionary = new Dictionary<string, long>();

                long totalSize = 0;
                foreach (var file in filePathNames)
                {
                    long size = GetBlobProperties(file.Item1).Length;
                    totalSize += size;

                    sizeDictionary.Add(file.Item1,size);
                }


                //Zip files breaks if we dont have exact content length 
                //and it isn't nesccarily the total lengths of the contents
                //dont see a simple way to get it set correctly without downloading entire file to server first 
                //so for now we wont include a content length
                //response.AddHeader("Content-Length",totalSize.ToString());

                foreach (var file in filePathNames)
                {
                    long size = sizeDictionary[file.Item1];

                    var entry = new ZipEntry(file.Item2)
                    {
                        DateTime = DateTime.Now,
                        Size = size
                    };

                    zipOutputStream.PutNextEntry(entry);
                    Container.GetBlockBlobReference(file.Item1).DownloadToStream(zipOutputStream);

                    response.Flush();
                    if (!response.IsClientConnected)
                    {
                        break;
                    }
                }
                zipOutputStream.Finish();
                zipOutputStream.Close();
            }
            response.End();

    }


Sources

This article follows the attribution requirements of Stack Overflow and is licensed under CC BY-SA 3.0.

Source: Stack Overflow

Solution Source