Saturday, July 23, 2011

Simple download manager with pause support implementation

In one of my previous post, I described how a simple download manage with pause/ resume feature can be implemented. Here is post.

After that post I got few request for further explanation or working code sample. So in this post I will describe implementation in more details and provide full working code sample.

I lost older working code because of my laptop crash, so created another sample application for this post, so code might differ a little bit from my older post, but idea is same.

So here code the code.

Following is download function where download request is placed by client.
void DownloadManager::download( QUrl url )
{
    mDownloadSizeAtPause =0;
    mCurrentRequest = QNetworkRequest(url);
    mFile = new QFile("download.part");
    mFile->open(QIODevice::ReadWrite);

    download(mCurrentRequest);
}

Following function put download request to QNetworkAccessManager and setup signal slot connection.
void DownloadManager::download( QNetworkRequest& request )
{
    mCurrentReply = mManager->get(request);

    connect(mCurrentReply,SIGNAL(finished()),this,SLOT(finished()));
    connect(mCurrentReply,SIGNAL(downloadProgress(qint64,qint64)),this,SLOT(downloadProgress(qint64,qint64)));
    connect(mCurrentReply,SIGNAL(error(QNetworkReply::NetworkError)),this,SLOT(error(QNetworkReply::NetworkError)));
}

Now the interesting functions comes that enable pause/resume functionality to download manager. Following is pause function. If simply abort current request, disconnect the signal/slot so we don't get any error signal and mess with our error handling code and most importantly it write data to our IODevice here its file which is storing downloaded data.
void DownloadManager::pause()
{
    if( mCurrentReply == 0 ) {
        return;
    }
    disconnect(mCurrentReply,SIGNAL(finished()),this,SLOT(finished()));
    disconnect(mCurrentReply,SIGNAL(downloadProgress(qint64,qint64)),this,SLOT(downloadProgress(qint64,qint64)));
    disconnect(mCurrentReply,SIGNAL(error(QNetworkReply::NetworkError)),this,SLOT(error(QNetworkReply::NetworkError)));

    mCurrentReply->abort();
    mFile->write( mCurrentReply->readAll());
    mCurrentReply = 0;
}

Now the resume function. So most important thing here is "Range" header. It tell the server that I want to start download from this much bytes and not from beginning, because we already received this much data when we paused the request.

void DownloadManager::resume()
{
    mDownloadSizeAtPause = mFile->size();
    QByteArray rangeHeaderValue = "bytes=" + QByteArray::number(mDownloadSizeAtPause) + "-";
    mCurrentRequest.setRawHeader("Range",rangeHeaderValue);

    download(mCurrentRequest);
}

Another important function, if you want to show correct progress. Remember to add mDownloadSizeAtPause (number of bytes already downloded when we pause the request ) to bytesReceived and bytesTotal, function to show correct progress.
void DownloadManager::downloadProgress ( qint64 bytesReceived, qint64 bytesTotal )
{
    mFile->write( mCurrentReply->readAll() );
    int percentage = ((mDownloadSizeAtPause+bytesReceived) * 100 )/ (mDownloadSizeAtPause+bytesTotal);
    emit progress(percentage);
}

here is link to full working sample project. Thank you for visiting my blog. Let me know if you need more details.

9 comments:

  1. Thanks for posting this! Very useful. I will give it a try...

    ReplyDelete
  2. This comment has been removed by the author.

    ReplyDelete
  3. Here a problem i faced that...

    "mCurrentRequest.setRawHeader("Range",rangeHeaderValue);" didn't work in my code...

    i mean when i resume any download, it start download from the beginning of the file...

    ReplyDelete
    Replies
    1. This happens if server is not supporting resume feature.

      Delete
  4. Here i have faced another problem also...
    the "bytesTotal" received in "downloadProgress ( qint64 bytesReceived, qint64 bytesTotal )" function is always -1.is there another way to get the total size of the downloaded file.

    ReplyDelete
    Replies
    1. Qt documentation says, "If the number of bytes to be downloaded is not known, bytesTotal will be -1."

      http://harmattan-dev.nokia.com/docs/platform-api-reference/xml/daily-docs/libqt4/qnetworkreply.html#downloadProgress

      Its difficult to know the size if server is not indicating it.

      Delete
    2. Yes you can use reply->Size() it will return you a qbit64 size of your reply

      Delete
  5. Note the number of bytes returned by QIODevice.size() may not represent the actual bytes transmitted ,The reason for this is that there may be protocol overhead or the data may be compressed during the download.

    ReplyDelete
  6. I've taken your code as a base and added the ability to send a HEAD request to get the file size and check that the server supports range requests and handle the app being closed unexpectedly. Please find my code here:
    https://github.com/parsley72/QtDownloadManager

    ReplyDelete