view ui/createinstallerdialog.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 6c4fff146999
children 49168bcb02e2
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 "createinstallerdialog.h"
#include <QDebug>
#include <QTextEdit>
#include <QDir>
#include <QPushButton>
#include <QGroupBox>
#include <QHBoxLayout>
#include <QVBoxLayout>
#include <QLabel>
#include <QFileDialog>
#include <QSettings>
#include <QStyle>
#include <QApplication>
#include <QMessageBox>
#include <QTemporaryDir>

/* Static information used in codesigning */
#ifndef SIGN_HASH
#define SIGN_HASH "sha256"
#endif
#ifndef SIGN_URL
#define SIGN_URL "https://wald.intevation.org/projects/trustbridge/"
#endif
#ifndef SIGN_PUBLISHER
#define SIGN_PUBLISHER "TrustBridge Test with ümlaut"
#endif


CreateInstallerDialog::CreateInstallerDialog(QMainWindow *parent) :
    QDialog(parent),
    mProgress(this),
    mInstallerPath(),
    mCurrentWorkingDir(NULL)
{
    QSettings settings;
    setWindowTitle(tr("Create binary installer"));
    setupGUI();
    resize(500, 250);
    mCertFile->setText(settings.value("CodeSignCert", QString()).toString());
    mBinaryFolder->setText(settings.value("LastBinaryFolder", QString()).toString());
    mSaveFile->setText(settings.value("LastBinOutputFolder", QString()).toString());

    connect(&mNSISProc, SIGNAL(finished(int, QProcess::ExitStatus)),
            this, SLOT(processFinished(int, QProcess::ExitStatus)));
    connect(&mNSISProc, SIGNAL(error(QProcess::ProcessError)),
            this, SLOT(processError(QProcess::ProcessError)));
}

void CreateInstallerDialog::setupGUI()
{
    /* Top level layout / widgets */
    QVBoxLayout *topLayout = new QVBoxLayout;
    QVBoxLayout *headerLayout = new QVBoxLayout;
    QHBoxLayout *headerSubLayout = new QHBoxLayout;
    QHBoxLayout *centerLayout = new QHBoxLayout;
    QHBoxLayout *bottomLayout = new QHBoxLayout;
    QVBoxLayout *labelLayout = new QVBoxLayout;
    QVBoxLayout *fieldLayout = new QVBoxLayout;
    QVBoxLayout *buttonLayout = new QVBoxLayout;

    QLabel *header = new QLabel("<h3>" + tr("Create binary installer") + "</h3>");
    QLabel *description = new QLabel(
        tr("Create and sign a TrustBridge binary installer."));
    headerSubLayout->insertSpacing(0, 40);
    headerSubLayout->addWidget(description);
    QFrame *headerSeparator = new QFrame();
    headerSeparator->setFrameShape(QFrame::HLine);
    headerSeparator->setFrameShadow(QFrame::Sunken);
    headerLayout->addWidget(header);
    headerLayout->addLayout(headerSubLayout);
    headerLayout->addWidget(headerSeparator);
    headerLayout->insertSpacing(4, 10);

    QLabel *archiveLabel = new QLabel(tr("Select binary folder:"));
    QLabel *certLabel = new QLabel(tr("Select code signing certificate:"));
    QLabel *saveLabel = new QLabel(tr("Select output folder:"));
    labelLayout->addWidget(archiveLabel);
    labelLayout->addWidget(certLabel);
    labelLayout->addWidget(saveLabel);

    mBinaryFolder = new QLineEdit();
    mCertFile = new QLineEdit();
    mSaveFile = new QLineEdit();
    fieldLayout->addWidget(mBinaryFolder);
    fieldLayout->addWidget(mCertFile);
    fieldLayout->addWidget(mSaveFile);

    QPushButton *archiveSelect = new QPushButton("...");
    connect(archiveSelect, SIGNAL(clicked()), this, SLOT(openFolderSelect()));
    archiveSelect->setFixedWidth(30);
    QPushButton *certSelect = new QPushButton("...");
    connect(certSelect, SIGNAL(clicked()), this, SLOT(openCertificateSelect()));
    certSelect->setFixedWidth(30);
    QPushButton *saveSelect = new QPushButton("...");
    connect(saveSelect, SIGNAL(clicked()), this, SLOT(openSaveLocation()));
    saveSelect->setFixedWidth(30);
    buttonLayout->addWidget(archiveSelect);
    buttonLayout->addWidget(certSelect);
    buttonLayout->addWidget(saveSelect);

    centerLayout->addLayout(labelLayout);
    centerLayout->addLayout(fieldLayout);
    centerLayout->addLayout(buttonLayout);

    QPushButton *create = new QPushButton(tr("Create installer"));
    connect(create, SIGNAL(clicked()), this, SLOT(createInstaller()));
    QPushButton *cancel = new QPushButton(tr("Cancel"));
    connect(cancel, SIGNAL(clicked()), this, SLOT(close()));
    bottomLayout->insertStretch(0, 10);
    bottomLayout->addWidget(create);
    bottomLayout->addWidget(cancel);

    QFrame *bottomSeparator = new QFrame();
    bottomSeparator->setFrameShape(QFrame::HLine);
    bottomSeparator->setFrameShadow(QFrame::Sunken);

    topLayout->addLayout(headerLayout);
    topLayout->addLayout(centerLayout);
    topLayout->insertStretch(2, 10);
    centerLayout->insertSpacing(3, 10);
    topLayout->addWidget(bottomSeparator);
    topLayout->addLayout(bottomLayout);

    setLayout(topLayout);

    mProgress.setWindowModality(Qt::WindowModal);
    mProgress.setCancelButton(0);
    mProgress.setRange(0,0);
    mProgress.setMinimumDuration(0);

    return;
}

void CreateInstallerDialog::openCertificateSelect()
{
    QSettings settings;
    QString certFile = QFileDialog::getOpenFileName(
        this, tr("Select certificate"),
        mCertFile->text().isEmpty() ? QDir::homePath() : mCertFile->text(),
        "*.pem *.der *.crt");
    settings.setValue("CodeSignCert", certFile);
    mCertFile->setText(certFile);
}

void CreateInstallerDialog::openFolderSelect()
{
    QSettings settings;
    QString archiveFolder = QFileDialog::getExistingDirectory(
        this, tr("Select binary folder"),
        mBinaryFolder->text().isEmpty() ? QDir::homePath() : mBinaryFolder->text());
    mBinaryFolder->setText(archiveFolder);
    settings.setValue("LastBinaryFolder", archiveFolder);
}

void CreateInstallerDialog::openSaveLocation()
{
    QSettings settings;
    QString saveFile = QFileDialog::getExistingDirectory(
        this, tr("Select target location"),
        mSaveFile->text().isEmpty() ? QDir::homePath() : mSaveFile->text());
    mSaveFile->setText(saveFile);
    settings.setValue("LastBinOutputFolder", saveFile);
}

void CreateInstallerDialog::showErrorMessage(const QString &msg)
{
    QMessageBox::warning(this, tr("Error!"), msg);
}

void CreateInstallerDialog::processFinished(int exitCode, QProcess::ExitStatus exitStatus)
{
    if (mCurrentWorkingDir) {
        delete mCurrentWorkingDir;
        mCurrentWorkingDir = NULL;
    }
    FinishedDialog *fin = new FinishedDialog(0, tr("Created installer in %1.")
            .arg(mSaveFile->text()), mNSISProc.readAll(), false);
    qDebug() << "Finished: " << mNSISProc.readAll();
    mProgress.setLabelText(tr("Signing installer package..."));
    if (!signFile(mInstallerPath)) {
        showErrorMessage(tr("Failed to sign installer package."));
        QFile::remove(mInstallerPath);
    }
    mProgress.cancel();
    fin->show();
    close();
}

void CreateInstallerDialog::processError(QProcess::ProcessError error)
{
    if (mCurrentWorkingDir) {
        delete mCurrentWorkingDir;
        mCurrentWorkingDir = NULL;
    }
    qDebug() << "Error: " << mNSISProc.readAll();
    mProgress.cancel();
}

void CreateInstallerDialog::createInstaller()
{
    mProgress.setLabelText(tr("Creating installer package..."));
    QDir binDir(mBinaryFolder->text());
    QDir outDir(mSaveFile->text());
    if (mBinaryFolder->text().isEmpty() || !binDir.exists()) {
        showErrorMessage(tr("Please select an existing input folder."));
        return;
    }
    if (mCertFile->text().isEmpty()) {
        showErrorMessage(tr("Please select a codesigning certificate."));
        return;
    }
    if (mSaveFile->text().isEmpty() || !outDir.exists()) {
        showErrorMessage(tr("Please select a output folder."));
        return;
    }
    QSettings options(binDir.filePath("meta.ini"), QSettings::IniFormat);
    options.sync();
    QStringList keys = options.allKeys();
    if (options.status() != QSettings::NoError || keys.size() < 1) {
        showErrorMessage(tr("Folder %1 does not appear to contain a meta.ini")
                .arg(binDir.path()));
        return;
    }

    QTemporaryDir *signedFilesDir = codesignBinaries(binDir.path() + "/windows");

    if (!signedFilesDir) {
        /* Error messages should have been shown by the codesign function */
        return;
    }

    mProgress.setLabelText(tr("Creating NSIS package..."));

    /* Copy windows directory contents to tmpdir */
    QStringList arguments;
    mNSISProc.setProgram("makensis");
    mNSISProc.setProcessChannelMode(QProcess::MergedChannels);
    mNSISProc.setWorkingDirectory(outDir.path());
#ifdef Q_OS_WIN
    arguments << QString::fromLatin1("/Dfiles_dir=") + signedFilesDir->path().replace("/", "\\");
    arguments << "/Dpath_sep=\\";
    foreach (const QString &key, keys) {
        QString value = options.value(key, QString()).toString();
        if (key == "setupname") {
            value = value.arg(outDir.path().replace("/", "\\") + "\\");
            mInstallerPath = value;
        }
        arguments << QString::fromLatin1("/D%1=%2").arg(key, value);
    }
#else
    arguments << QString::fromLatin1("-Dfiles_dir=") + signedFilesDir->path();
    arguments << "-Dpath_sep=/";
    foreach (const QString &key, keys) {
        QString value = options.value(key, QString()).toString();
        if (key == "setupname") {
            value = value.arg(outDir.path() + "/");
            mInstallerPath = value;
        }
        arguments << QString::fromLatin1("-D%1=%2").arg(key, value);
    }
#endif

    arguments << binDir.path() + "/trustbridge.nsi";

    qDebug() << "Starting makensis with arguments: " << arguments;
    mNSISProc.setArguments(arguments);
    mNSISProc.start();
    mProgress.show();

    if (!mNSISProc.waitForStarted() ||
        mNSISProc.state() == QProcess::NotRunning) {
        showErrorMessage(tr("Failed to start makensis.\n"
            "Please ensure that makensis is installed and in your PATH variable."));
    }
}

bool CreateInstallerDialog::signFile(QString filePath) {
    QProcess signProc;
    signProc.setProcessChannelMode(QProcess::MergedChannels);
    signProc.setProgram("osslsigncode");
    QStringList arguments;

    QSettings mySettings;

    QString publisher = mySettings.value("sign_publisher", SIGN_PUBLISHER).toString();
    QString url = mySettings.value("sign_url", SIGN_URL).toString();
    QString hash = mySettings.value("sign_hash", SIGN_HASH).toString();

    arguments << "sign" << "-certs" << mCertFile->text() << "-key"
              << mCertFile->text() << "-n" << publisher << "-i" <<
              url << "-h" << hash << "-in" << filePath << "-out" << filePath + ".signed";

    qDebug() << "Starting osslsigncode with arguments: " << arguments;
    signProc.setArguments(arguments);
    signProc.start();

    if (!signProc.waitForFinished(30000)) {
        qDebug() << "Signing takes longer then 30 seconds. Aborting.";
        return false;
    }

    if (signProc.exitStatus() != QProcess::NormalExit ||
        signProc.exitCode() != 0) {
        qDebug() << "Error process returned: " << signProc.exitCode();
        qDebug() << "Output: " << signProc.readAllStandardOutput();
        return false;
    }

    if (!QFile::remove(filePath)) {
        qDebug() << "Failed to remove file.";
        return false;
    }
    if (!QFile::copy(filePath + ".signed", filePath)) {
        qDebug() << "Failed to copy signed file in place.";
        return false;
    }
    if (!QFile::remove(filePath + ".signed")) {
        qDebug() << "Failed to remove signed.";
        return false;
    }
    return true;
}


QTemporaryDir *CreateInstallerDialog::codesignBinaries(const QDir& sourceDir) {
    QTemporaryDir* target = new QTemporaryDir();
    /* Copy all files from the source dir to a temporary location */
    mProgress.setLabelText(tr("Signing binaries..."));

    mProgress.show();
    foreach (const QFileInfo& entry, sourceDir.entryInfoList()) {
        QString targetPath = target->path() + QString::fromLatin1("/") + entry.fileName();
        if (entry.fileName() == "." || entry.fileName() == "..") {
            continue;
        }
        if (!QFile::copy(entry.absoluteFilePath(), targetPath)) {
            qDebug() << "Failed to copy: " << entry.absoluteFilePath() << " to: " << targetPath;
            showErrorMessage(tr("Failed to copy binaries to temporary location."));
            mProgress.cancel();
            return NULL;
        }
        if (entry.suffix() == "exe") {
            if (!signFile(targetPath)) {
                showErrorMessage(tr("Failed to sign binaries with osslsigncode.\n"
                            "Please check that %1 is a valid code signing certificate and that"
                            "osslsigncode can be found in the PATH.").arg(mCertFile->text()));
                mProgress.cancel();
                return NULL;
            }
        }
    }
    mProgress.cancel();
    return target;
}

FinishedDialog::FinishedDialog(QDialog *parent,
        QString msg, QString details, bool isErr):
    QDialog(parent)
{
    QVBoxLayout *topLayout = new QVBoxLayout;
    QHBoxLayout *buttonLayout = new QHBoxLayout;
    QLabel *msgLabel = new QLabel;
    QTextEdit *detailsWindow = new QTextEdit;

    detailsWindow->insertPlainText(details);
    detailsWindow->setReadOnly(true);
    detailsWindow->hide();

    if (!isErr) {
        setWindowTitle(tr("Success!"));
        msgLabel->setPixmap(QApplication::style()->standardIcon(
                    QStyle::SP_MessageBoxInformation).pixmap(16, 16));
    } else {
        setWindowTitle(tr("Error!"));
        msgLabel->setPixmap(QApplication::style()->standardIcon(
                    QStyle::SP_MessageBoxCritical).pixmap(16, 16));
    }
    msgLabel->setText(msg);

    topLayout->addWidget(msgLabel);
    topLayout->addWidget(detailsWindow);
    QPushButton *detailsBtn = new QPushButton(tr("Details"));
    connect(detailsBtn, SIGNAL(clicked()), detailsWindow, SLOT(show()));
    buttonLayout->addWidget(detailsBtn);

    QPushButton *okBtn = new QPushButton(tr("OK"));
    okBtn->setIcon(QApplication::style()->standardIcon(QStyle::SP_DialogOkButton));
    connect(okBtn, SIGNAL(clicked()), this, SLOT(close()));
    buttonLayout->insertStretch(0, 100);
    buttonLayout->addWidget(okBtn);

    topLayout->addLayout(buttonLayout);
    setLayout(topLayout);
}

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