Quick Tip: Download a File in Qt

Someone asked me a while back how to simply download a file in Qt. I mean, Qt is so mature and so extensive that you think something like this would be one function call away–wrong. It actually takes a whole class to do it right! The idea is basically that you want to get a QNetworkRequest going for your URL in a reply, and from there it’s basics. I’m going to keep the explanations quick, since this is a quick tip, and just post mainly the bare-bones code for this.

Note that this is some code taken from a past QML project of mine, so note the “Handler” in the class name and the affluent use of signals/slots for everything.

class DownloadFileHandler : public QObject
{
    Q_OBJECT

public:
    DownloadFileHandler(const QUrl &downloadUrl, const QDir &storageLocation,
                        bool startImmediately = false);

    DownloadFileHandler() : QObject() {}

    inline void setFile(QFile *file) { pFile = file; }
    inline void setStorageLocation(const QDir &location) { mStorageLocation = location; }
    inline void setDownloadURL(const QUrl &url) { mDownloadUrl = url; }

    inline QFile* file() const { return pFile; }
    inline QDir storageLocation() const { return mStorageLocation; }
    inline QUrl downloadUrl() const { return mDownloadUrl; }

signals:
    void fileAlreadyExistsError(QString existingFileDisplayName);
    void fileOpeningError(QString errorMessage);
    void fileDownloadError(QString errorMessage);
    void fileDownloadStarted();
    void fileDownloadCancelled();
    void fileDownloadFinished();

public slots:
    void setDownloadURLSlot(const QString &url);

    void beginDownload();
    void cancelDownload();
    void downloadFinished();
    void downloadReadyRead();

private:
    QFile *pFile;
    QDir mStorageLocation;

    QUrl mDownloadUrl;

    QNetworkReply *pNetworkReply;
    QNetworkAccessManager mNetworkManager;
};

Oh, get over those inlined getters/setters. I hear Qt Creator 2.8 is getting some handy-dandy refactoring tools for C++ 😉 Now lets break the implementation down.

DownloadFileHandler::DownloadFileHandler(const QUrl &downloadUrl, const QDir &storageLocation, bool startImmediately)
: QObject(), mDownloadUrl(downloadUrl), mStorageLocation(storageLocation)
{
// Start the download process immediately, rather than waiting for a call
if (startImmediately)
beginDownload();
}

void DownloadFileHandler::beginDownload()
{
// Make our strings for the File
QString downloadFileName = QFileInfo(mDownloadUrl.path()).fileName();
qDebug() << "Downloading file with file name: " << downloadFileName;

QString absoluteFileAddress = mStorageLocation.path() + "/" + downloadFileName; // e.g. file name appended to the directory
qDebug() << "Downloaded file will be stored at: " << absoluteFileAddress;

pFile = new QFile(absoluteFileAddress);
qDebug() << "File being downloaded to: " + absoluteFileAddress;

// Check if we have the file open for writing
if (!pFile->open(QIODevice::WriteOnly))
{
// Send our message to the application
emit fileOpeningError(pFile->errorString());

// Delete file
if (pFile)
{
delete pFile;
pFile = NULL;
}

return; // Break, failed
}

// Get the data from our request on the URL
pNetworkReply = mNetworkManager.get(QNetworkRequest(mDownloadUrl));
emit fileDownloadStarted();

// Setup signals/slots
connect(pNetworkReply, SIGNAL(finished()), this, SLOT(downloadFinished()));
connect(pNetworkReply, SIGNAL(readyRead()), this, SLOT(downloadReadyRead()));
}

void DownloadFileHandler::cancelDownload()
{
pNetworkReply->abort();
emit fileDownloadCancelled();
}

void DownloadFileHandler::downloadFinished()
{
// Make sure everything is written before we continue
downloadReadyRead();
pFile->flush();
pFile->close();

// If there was an error, emit it so we can handle it and/or log it
if (pNetworkReply->error())
{
emit fileDownloadError(pNetworkReply->errorString());
}

pNetworkReply->deleteLater(); // Very important that we use deleteLater()!
pNetworkReply = NULL;

delete pFile;
pFile = NULL;

// Tell the application we are done
emit fileDownloadFinished();
}

void DownloadFileHandler::downloadReadyRead()
{
// If pFile exists, dump all reply data to disk to save memory
if (pFile)
{
pFile->write(pNetworkReply->readAll());
}
}

void DownloadFileHandler::setDownloadURLSlot(const QString &url)
{
this->setDownloadURL(QUrl(url));
}

Under 100 lines 😀 This is really a quick tip, so I won’t do as much explaining as normally. Basically, take a look at line 38. The idea behind the Network Access Manager is that you have requests and replies from those requests. We put in a request for the file and get a reply back. We can then query the reply for things like data, progress, etc. Perhaps we can add a progress bar in the future!

I hope you enjoyed the quick tip. Keep on coding!

Advertisements

Leave a Reply

Fill in your details below or click an icon to log in:

WordPress.com Logo

You are commenting using your WordPress.com account. Log Out / Change )

Twitter picture

You are commenting using your Twitter account. Log Out / Change )

Facebook photo

You are commenting using your Facebook account. Log Out / Change )

Google+ photo

You are commenting using your Google+ account. Log Out / Change )

Connecting to %s