view ui/installwrapper.cpp @ 388:a507e5f1b999

Emit a signal if no errors happened during installation.
author Andre Heinecke <andre.heinecke@intevation.de>
date Tue, 15 Apr 2014 19:03:56 +0200
parents 32309299bec9
children 17e1c8f37d72
line wrap: on
line source
#include "installwrapper.h"

#include <QFileInfo>
#include <QProcess>
#include <QTemporaryFile>
#include <QApplication>
#include <QDir>
#include <QDebug>

#include "logging.h"

#define INSTALL_TIMEOUT 3600000 /* Wait up to an hour */

InstallWrapper::InstallWrapper(QObject* parent,
        const QString& path, const QStringList& choices):
    QThread(parent),
    mCertListFile(path),
    mChoices(choices)
{
}

QFileInfo getCinstProcInfo() {
    QFileInfo fi(QCoreApplication::applicationFilePath());
    QDir myDir = fi.absoluteDir();
    QString instProcName = "cinst";
    if (!fi.suffix().isEmpty()) {
        instProcName += "." + fi.suffix();
    }
    return QFileInfo(myDir.absoluteFilePath(instProcName));
}

bool InstallWrapper::writeChoices(QTemporaryFile* choicesFile) const
{
    if (!choicesFile->open()) {
        return false;
    }

    foreach (const QString &b64data, mChoices) {
       if (!choicesFile->write(b64data.toLatin1())) {
           return false;
       }
       if (!choicesFile->write("\n")) {
           return false;
       }
    }

    choicesFile->close();

    return true;
}

void InstallWrapper::run()
{
    /* TODO: We need errorcodes here so that we can see if a user
     * cancled the UAC elevation */
    QTemporaryFile choicesFile;
    QFileInfo cinstProcInfo = getCinstProcInfo();

    QString cinstFileName = QDir::toNativeSeparators(
            getCinstProcInfo().absoluteFilePath());

    if (!cinstProcInfo.isExecutable()) {
        emit error(tr("Could not find certificate installation process."));
        return;
    }

    if (!writeChoices(&choicesFile)) {
        emit error(tr("Failed to write temporary file."));
        return;
    }

#ifdef WIN32
    /* QProcess on Windows uses CreateProcess but we have to
     * use the runas shell command to get the UAC prompt if necessary.
     * So we have to handle the process ourself. Starting with
     * shell execute also means that we can not have stdout and stderr
     * redirection. This is the reason we use command line parameters
     * and not a pipe for communication. In debug mode the installer
     * also makes use of output debug string. */
    DWORD retval = 0;
    SHELLEXECUTEINFOW shExecInfo;

    memset (&shExecInfo, 0, sizeof(SHELLEXECUTEINFOW));

    /* Windows needs each parameter packed in " */
    QString parameters = "\"list=" + mCertListFile +
        "\" \"choices=" + choicesFile.fileName() + "\"";

    shExecInfo.cbSize = sizeof(SHELLEXECUTEINFOW);
    shExecInfo.fMask = SEE_MASK_NOCLOSEPROCESS;
    shExecInfo.lpVerb = L"runas";
    shExecInfo.lpFile = reinterpret_cast<LPCWSTR> (cinstFileName.utf16());
    shExecInfo.lpParameters =  reinterpret_cast<LPCWSTR> (parameters.utf16());

    qDebug() << "Starting process " << cinstFileName <<" params: " << parameters;

    if (!ShellExecuteExW(&shExecInfo)) {
        char* errmsg = getLastErrorMsg();
        QString qerrmsg = QString::fromUtf8(errmsg);
        free(errmsg);
        emit error(tr("Error executing process: %1").arg(qerrmsg));
        return;
    }

    retval = WaitForSingleObject(shExecInfo.hProcess, INSTALL_TIMEOUT);

    if (retval != WAIT_OBJECT_0) {
        if (retval == WAIT_FAILED) {
            char* errmsg = getLastErrorMsg();
            QString qerrmsg = QString::fromUtf8(errmsg);
            free(errmsg);
            emit error (tr("Error monitoring process: %1").arg(qerrmsg));
            return;
        } else {
            emit error (tr("Certificate installation timed out."));
            return;
        }
    }

    if (GetExitCodeProcess(shExecInfo.hProcess, &retval)) {
        if (retval == STILL_ACTIVE) {
            qDebug() << "Process still running, huh..";
        }
    } else {
        char* errmsg = getLastErrorMsg();
        QString qerrmsg = QString::fromUtf8(errmsg);
        free(errmsg);
        emit error (tr("Failed to check process status: %1").arg(qerrmsg));
        CloseHandle(shExecInfo.hProcess);
        return;
    }
    CloseHandle(shExecInfo.hProcess);

    if (retval != 0) {
        /* TODO make this nicer */
        emit error (tr("The process failed with return code. %1").arg(retval));
        return;
    }
#else /* WIN32 */
    QProcess installerProcess;
    installerProcess.setProgram(cinstProcInfo.absoluteFilePath());
    QStringList parameters;

    choicesFile.setAutoRemove(false);
    parameters << "list=" + mCertListFile << "choices=" + choicesFile.fileName();
    installerProcess.setArguments(parameters);


    qDebug() << "Starting process " << cinstFileName <<" params: " << parameters;
    installerProcess.start();
    if (!installerProcess.waitForStarted() ||
        installerProcess.state() == QProcess::NotRunning) {
        emit error (tr("Failed to start installer process."));
        return;
    }

    installerProcess.waitForFinished();

    if (installerProcess.exitStatus() == QProcess::CrashExit) {
        /* Woops */
        emit error (tr("Failed to complete installation."));
        return;
    } else if (installerProcess.exitStatus() != QProcess::NormalExit) {
        /* Can not Happen. there are only those two values but maybe
         * qt changed.. */
        emit error (tr("Failed to complete installation."));
        return;
    }

    if (installerProcess.exitCode() == 0) {
        qDebug() << "output: " << installerProcess.readAllStandardOutput();
    } else {
        /* TODO handle errors defined by errorcodes.h */
        qDebug() << "Installer Process returned: " << installerProcess.exitCode();
        qDebug() << "output: " << installerProcess.readAllStandardOutput();
        return;
    }
#endif
    emit installationSuccessful();
}

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