view ui/downloader.cpp @ 701:31c3d2bc9880

(Issue22) Fix painting problems with fixed size in windows style. We now use fusion style also on Windows for the combobox to let it be shown in the same way as we do on GNU/Linux.
author Andre Heinecke <andre.heinecke@intevation.de>
date Wed, 02 Jul 2014 11:26:42 +0200
parents cb044efdaf0d
children 13cf42dbe9bd
line wrap: on
line source
/* Copyright (C) 2014 by Bundesamt für Sicherheit in der Informationstechnik
 * Software engineering by Intevation GmbH
 *
 * This file is Free Software under the GNU GPL (v>=2)
 * and comes with ABSOLUTELY NO WARRANTY!
 * See LICENSE.txt for details.
 */
#include "downloader.h"

#ifndef DOWNLOAD_SERVER
#define DOWNLOAD_SERVER "https://www.intevation.de"
#endif

#include <QFile>
#include <QDir>
#include <QDebug>
#include <QStandardPaths>
#include <QLocale>
#include <QSaveFile>

#include <polarssl/net.h>
#include <polarssl/ssl.h>
#include <polarssl/entropy.h>
#include <polarssl/ctr_drbg.h>
#include <polarssl/error.h>
#include <polarssl/certs.h>

#define MAX_SW_SIZE 10485760
#define MAX_LIST_SIZE 1048576


Downloader::Downloader(QObject* parent, const QString& url,
                       const QByteArray& certificate,
                       const QDateTime& newestSW,
                       const QDateTime& newestList,
                       const QString& resourceSW,
                       const QString& resourceList,
                       bool downloadSW):
    QThread(parent),
    mLastModSW(newestSW),
    mLastModList(newestList),
    mResourceSW(resourceSW),
    mResourceList(resourceList),
    mDownloadSW(downloadSW),
    mSSLConnection(url, certificate)
{
}


Downloader::~Downloader() {
}

QString Downloader::getDataDirectory()
{
    QString candidate =
        QStandardPaths::writableLocation(QStandardPaths::DataLocation);

    if (candidate.isEmpty()) {
        qDebug() << "Could not find writeable locaction for me";
        return QString();
    }

    QDir cDir(candidate);

    if (!cDir.exists()) {
        if (!cDir.mkpath(candidate)) {
            qDebug() << "Could not create path to: " << candidate;
            return QString();
        }
    }
    return cDir.absolutePath();
}

QMap<QString, QString> Downloader::parseHeaders(QByteArray *data)
{
    int bodyStart = data->indexOf("\r\n\r\n");
    QMap<QString, QString> retval;
    QByteArray headers;
    QString response(*data);
    if (bodyStart == -1) {
        qDebug() << "Could not find header end.";
        emit error(tr("Invalid response"),
                SSLConnection::InvalidResponse);
        return retval;
    }

    /* Take the headers with one additional line break */
    headers = data->left(bodyStart + 2);
    /* Chop off the head */

    foreach (const QString& line, response.split("\r\n")) {
        int sepPos = -1;
        sepPos = line.indexOf(": ");
        if (sepPos == -1) {
            continue;
        }
        QString key = line.left(sepPos);
        QString value = line.right(line.size() - sepPos - 2);

        retval.insert(key, value);
    }

    *data = data->right(data->size() - bodyStart - 4);
    return retval;
}

QDateTime Downloader::getLastModifiedHeader(const QString &resource) {
    int ret = -1;
    QByteArray response;
    QLocale cLocale = QLocale::c();
    QMap<QString, QString> headers;
    QString headRequest =
        QString::fromLatin1("HEAD %1 HTTP/1.0\r\n\r\n").arg(resource);

    ret = mSSLConnection.write(headRequest.toUtf8());
    if (ret != 0) {
        emit error (tr("Connection lost"), SSLConnection::ConnectionLost);
        return QDateTime();
    }

    response = mSSLConnection.read(1024);

    qDebug() << "Response from server was: " << response;

    if (response.isNull()) {
        qDebug() << "No response";
        emit error (tr("Connection lost"), SSLConnection::ConnectionLost);
        return QDateTime();
    }

    headers = parseHeaders(&response);
    const QString lastModified = headers.value("Last-Modified");
    if (!lastModified.isEmpty()) {
        QDateTime candidate = cLocale.toDateTime(lastModified,
                "ddd, dd MMM yyyy HH:mm:ss' GMT'");
        if (candidate.isValid()) {
            return candidate;
        }
    }
    emit error (tr("Invalid response from the server"), SSLConnection::InvalidResponse);
    return QDateTime();
}

bool Downloader::downloadFile(const QString &resource,
                              const QString &fileName,
                              size_t maxSize)
{
    int ret = -1;
    size_t bytesRead = 0;
    QString getRequest =
        QString::fromLatin1("GET %1 HTTP/1.0\r\n\r\n").arg(resource);

    QSaveFile outputFile(fileName);

    ret = mSSLConnection.write(getRequest.toUtf8());

    // Open / Create the file to write to.
    if (!outputFile.open(QIODevice::WriteOnly)) {
        qDebug() << "Failed to open file";
        return false;
    }

    if (ret != 0) {
        emit error(tr("Connection lost"), SSLConnection::ConnectionLost);
        return false;
    }

    bool inBody = false;
    QMap <QString, QString> headers;
    do {
        /* Read the response in 8KiB chunks */
        int responseSize = 0;
        QByteArray response = mSSLConnection.read(8192);
        if (response.isNull()) {
            qDebug() << "Error reading response";
            emit error(tr("Connection lost"), SSLConnection::ConnectionLost);
            return false;
        }
        responseSize = response.size();
        if (!inBody) {
            headers = parseHeaders(&response);
            inBody = true;
        }
        outputFile.write(response);
        bytesRead += responseSize;
        if (responseSize < 8192) {
            /* Nothing more to read */
            break;
        }
        /* TODO Emit progress */
    } while (bytesRead < maxSize);

    return outputFile.commit();
}

void Downloader::run() {
    int ret;
    QDateTime remoteModList;
    QDateTime remoteModSW;

    if (!mSSLConnection.initialized()) {
        emit error(tr("Failed to initialize SSL Module."), SSLConnection::ErrUnknown);
        return;
    }

    ret = mSSLConnection.connect();

    if (ret != 0) {
        emit error(tr("Failed to connect."),
                   mSSLConnection.getLastError());
        return;
    }

    emit progress(tr("Connected"), 1, -1);

    remoteModSW = getLastModifiedHeader(mResourceSW);
    emit lastModifiedDate(remoteModSW);

    if (!remoteModSW.isValid()) {
        qDebug() << "Could not parse headers for Software";
        return;
    }

    if (!mLastModSW.isValid() || remoteModSW > mLastModSW) {
        QString dataDirectory = getDataDirectory();

        if (dataDirectory.isEmpty()) {
            qDebug() << "Failed to get data directory";
            return;
        }

        QString fileName = dataDirectory.append("/SW-")
            .append(remoteModSW.toString("yyyyMMddHHmmss"))
            .append(".exe");

        qDebug() << "fileName: " << fileName;

        if (mDownloadSW) {
            if (!downloadFile(mResourceSW, fileName, MAX_SW_SIZE)) {
                return;
            }
        }

        emit newSoftwareAvailable(fileName, remoteModSW);
        return;
    }

    remoteModList = getLastModifiedHeader(mResourceList);
    if (!remoteModList.isValid()) {
        qDebug() << "Could not parse headers for List";
        return;
    }

    if (!mLastModList.isValid() || remoteModList > mLastModList) {
        QString dataDirectory = getDataDirectory();

        if (dataDirectory.isEmpty()) {
            qDebug() << "Failed to get data directory";
            return;
        }

        QString fileName = dataDirectory.append("/list-")
            .append(remoteModList.toString("yyyyMMddHHmmss"))
            .append(".txt");

        qDebug() << "fileName: " << fileName;

        if (!downloadFile(mResourceList, fileName, MAX_LIST_SIZE)) {
            return;
        }

        emit newListAvailable(fileName, remoteModList);
    }

    emit progress(tr("Closing"), 1, -1);
}

http://wald.intevation.org/projects/trustbridge/