I'm trying add auto-update capabilities to an application I'm developing. I've based this functionality off the Qt HTTP Example (and by based I mean I copied this example exactly and then went from there). It's downloading a ZIP file and then extracting its contents to patch the application.
Occasionally, when downloading, the connection will fail, and the download stops. To be a little more user friendly, I figured I'd add auto-restart capabilities to the downloader, where it will attempt to restart the download once if it fails.
Here are the highlights of my code - the method names match the method names in the example:
void Autopatcher::httpReadyRead()
{
//file is a QFile that is opened when the download starts
if (file) {
QByteArray qba = reply->readAll();
//keep track of how many bytes have been written to the file
bytesWritten += qba.size();
file->write(qba);
}
}
void Autopatcher::startRequest(QUrl url)
{
//doResume is set in httpFinished() if an error occurred
if (doResume) {
QNetworkRequest req(url);
//bytesWritten is incremented in httpReadyRead()
QByteArray rangeHeaderValue = "bytes=" + QByteArray::number(bytesWritten) + "-";
req.setRawHeader("Range",rangeHeaderValue);
reply = qnam.get(req);
} else {
reply = qnam.get(QNetworkRequest(url));
}
//slot connections omitted for brevity
}
//connect(reply, SIGNAL(error(QNetworkReply::NetworkError)), this, SLOT(fileGetError(QNetworkReply::NetworkError)));
void Autopatcher::fileGetError(QNetworkReply::NetworkError error) {
httpRequestAborted = true;
}
void Autopatcher::httpFinished() {
//If an error occurred
if (reply->error()) {
//If we haven't retried yet
if (!retried) {
//Try to resume the download
doResume=true;
//downloadFile() is a method that handles some administrative tasks
//like opening the file if doResume=false
//and calling startRequest() with the appropriate URL
QTimer::singleShot(5000,this,SLOT(downloadFile()));
}
//If we have retried already
else {
//Give up :(
if (file) {
file->close();
file->remove();
delete file;
file = 0;
}
}
//If no error, then we were successful!
} else {
if (file) {
file->close();
delete file;
file = 0;
}
//Apply the patch
doPatch();
}
reply->deleteLater();
reply = 0;
}
Now, if the download completes normally with no interruptions, it works just fine. The ZIP extracts perfectly. However, if the connection fails and the application restarts the download, it does finish downloading, and I can see all the contents of the ZIP file in 7-zip, but I cannot extract them (7-zip said something along the lines of "tried to move the pointer before the start of the file).
I'm assuming that I've made a simple off-by-one error somewhere, like in the HTTP Range header. I've seen an example of how to pause & resume downloads at this blog, but he writes the contents of the stream to file at pause, whereas I stream them into the file in httpReadyRead
. I don't know if that's causing a problem.
For testing, I've been using Sysinternals TCPView to sever the TCP connection during download. I'm not sure how to debug this further, so let me know if more information would be useful!