Implementing Download Resuming

This document describes how an embedder or other Gecko/Necko-using application can implement download resuming.

The interfaces in question exist in the form they are described here since Gecko 1.8a4 (Firefox 1.5, SeaMonkey 1.0, XULRunner 1.8.0.1).

Introduction

Various protocols support getting partial files. This means that if a download was interrupted, it can be resumed from that point on, rather than regetting the whole file. The Necko implementations of HTTP 1.1 (RFC 2616) as well as FTP support this feature.

Not only is the ability to specify a start position important, but it's also important to have some assurance that the file did not change since the initial download attempt. That would lead to a situation where the first part of the file corresponds to the initial version, while the latter part belongs to a different version, leading to an unusable result.

The interface

Resuming is done using the nsIResumableChannel interface. The expected way to use it is this:

  • For all downloads that happen, get the entityID from the channel, and store it for later use. The entity ID is what ensures that the file remains unchanged. This can also be used to check whether the download is resumable: if it is not (e.g. the server is not using HTTP 1.1), then accessing this attribute will throw an NS_ERROR_NOT_RESUMABLE exception.
  • If the download gets interrupted, Necko will call the stream listener's onStopRequest method with a failure status. (TODO: document what webbrowserpersist/exthandler do). The front-end code can then notify the user of this, and offer to resume the download.

Resuming a download

In order to resume a download, you should create a channel as usual (using nsIIOService). Then, you can check if it implements nsIResumableChannel. If it does not, the protocol does not support resuming. Otherwise, call resumeAt with the desired start position, and the previously stored entity ID. (It is possible to pass an empty string as the ID; however, this means that you have no assurance that the file remained unchanged)

Now, you can open the channel as usual (using nsIChannel.asyncOpen() in the common case) and write to the file in the onDataAvailable notifications. You may want to use nsISimpleStreamListener to simplify this task; to get progress notifications, you can implement nsIProgressEventSink and set an interface requester as the notificationCallbacks of the channel that gives out such an event sink (this needs to be done before calling asyncOpen).

Note: In versions of Firefox prior to 3.0 alpha 7, nsIWebBrowserPersist cannot append to existing files (as opposed to overwriting them), and is therefore not usable for this task (see bug 129921).

Detecting when a file changed

If the file changed (that is, the entity ID does not match), then Necko will notify the stream listener with an NS_ERROR_ENTITY_CHANGED error code. The front-end may want to offer the user to get the new file from scratch in such a case.

Resuming not supported

In some cases, it's not possible to detect whether the server supports resuming until the user actually tries to resume. In such a case, the stream listener will get an NS_ERROR_NOT_RESUMABLE status. Here, too, the front-end may want to offer downloading the entire file from scratch.