I recently added progress bars (actually, a percentage instead of a bar, but it was the same to implement) to
Ringlight uploads and downloads. I was suprised to find that the available server-side libraries for dealing with file uploads seemed to be inadequate for adding this functionality to my website.
The basic technique for adding progress bars is relatively simple. With jQuery:
- Install the Ajax File Upload extension.
- Install the periodic execution extension.
- Register some Ajax event callbacks to reveal the progress bar on the page when the upload starts and also to catch errors.
- Call the $.ajaxFileUpload function with the URL of the upload handler script, the id of the file input element, and the callback function to handle output from the upload handler.
- Have the upload handler return a Json object with an id for the upload.
- Call your progress bar update function with the periodic execution model: $.periodic(updateProgressBar);
- The updateProgressBar function should fetch the download status from a server-side script, supplying the upload id and a callback function: $.getJSON("fetchProgress", {id: id}, function(data) {/* update progress bar with data.percentDownloaded*/});
- The fetchProgress script should return upload progress information in a Json object. I return percentDownloaded, but you can include anything you'd like, such as upload rate. You should also provide error information here, such as if the upload failed.
- The callback function for fetchProgress should update the page to reflect updated progress. For instance, updating a percentage to completion could be as simple as $("#percent").empty().append(data.percentDownloaded);
This was all very simple to implement and jQuery made it possible in very few lines of code. The difficulty was in providing a percentDownloaded value. The difficulty comes from the fact that it is common for browsers to not include the Content-Length field for uploaded files. The file upload handling libraries generally solve this problem by either 1) not providing a content length or 2) loading the whole file into memory (or disk, in some cases) and then finding the length of it. Either way, not very useful for a progress bar! This total failure to handle streaming files is a
common problem in libraries and if you could avoid it in the libraries you implement then the world would be most appreciative.
In the meantime, there are a number of action items that require your attention. First, calculate the file length by taking the HTTP request Content-Length field and subtracting the size of everything which is not the file in order to yield the file length. I did this with the following shoddy algorithm:
- Extract the MIME boundary from the Content-Disposition field.
- Subtract the size of the MIME boundary twice (there is a boundary on both sides of the file).
- Subtract 2 because the second boundary has a trailing "--".
- Subtract 4 because each boundary has a trailing two-character newline (\r\n).
- Subtract 8 because my numbers were always off by exactly 8. I'm not sure where this additional 8 is coming from.
As I said, this algorithm is shoddy, a kludge not fit for use in production. However, it works for now! It needs extensive testing and tweaking on a variety of browsers. The next steps:
- Improve algorithm so that it's robust enough to work with most browsers
- Submit a patch to python's FieldStorage class to support this algorithm
- Submit a bug report to Mozilla requesting that they supply content length in file uploads
For now, my upload progress bars are working, so I'm happy.