Skip to content
Snippets Groups Projects
s60deploystep.cpp 17.3 KiB
Newer Older
/**************************************************************************
**
** This file is part of Qt Creator
**
** Copyright (c) 2010 Nokia Corporation and/or its subsidiary(-ies).
**
** Contact: Nokia Corporation (qt-info@nokia.com)
**
** Commercial Usage
**
** Licensees holding valid Qt Commercial licenses may use this file in
** accordance with the Qt Commercial License Agreement provided with the
** Software or, alternatively, in accordance with the terms contained in
** a written agreement between you and Nokia.
**
** GNU Lesser General Public License Usage
**
** Alternatively, this file may be used under the terms of the GNU Lesser
** General Public License version 2.1 as published by the Free Software
** Foundation and appearing in the file LICENSE.LGPL included in the
** packaging of this file.  Please review the following information to
** ensure the GNU Lesser General Public License version 2.1 requirements
** will be met: http://www.gnu.org/licenses/old-licenses/lgpl-2.1.html.
**
** If you are unsure which license is appropriate for your use, please
** contact the sales department at http://qt.nokia.com/contact.
**
**************************************************************************/

#include "s60deploystep.h"

#include "qt4buildconfiguration.h"
#include "s60devicerunconfiguration.h"
#include "symbiandevicemanager.h"
#include "s60runconfigbluetoothstarter.h"

#include <QtGui/QMessageBox>
#include <QtGui/QMainWindow>
#include <QtCore/QCoreApplication>
#include <QtCore/QTimer>
#include <QDateTime>
#include <QFile>
#include <QFileInfo>
#include <QDir>

#include <coreplugin/icore.h>
Tobias Hunger's avatar
Tobias Hunger committed
#include <projectexplorer/buildsteplist.h>
#include <projectexplorer/target.h>
#include <projectexplorer/ioutputparser.h>
Tobias Hunger's avatar
Tobias Hunger committed
#include <projectexplorer/projectexplorerconstants.h>
#include <qt4projectmanagerconstants.h>

using namespace ProjectExplorer;
using namespace Qt4ProjectManager::Internal;

namespace {
    const char * const S60_DEPLOY_STEP_ID = "Qt4ProjectManager.S60DeployStep";
}

static inline bool ensureDeleteFile(const QString &fileName, QString *errorMessage)
{
    QFile file(fileName);
    if (file.exists() && !file.remove()) {
        *errorMessage = S60DeployStep::tr("Unable to remove existing file '%1': %2").arg(fileName, file.errorString());
        return false;
    }
    return true;
}

static inline bool renameFile(const QString &sourceName, const QString &targetName,
                              QString *errorMessage)
{
    if (sourceName == targetName)
        return true;
    if (!ensureDeleteFile(targetName, errorMessage))
        return false;
    QFile source(sourceName);
    if (!source.rename(targetName)) {
        *errorMessage = S60DeployStep::tr("Unable to rename file '%1' to '%2': %3")
                        .arg(sourceName, targetName, source.errorString());
        return false;
    }
    return true;
}

// #pragma mark -- S60DeployStep

Tobias Hunger's avatar
Tobias Hunger committed
S60DeployStep::S60DeployStep(ProjectExplorer::BuildStepList *bc,
                             S60DeployStep *bs):
Pawel Polanski's avatar
Pawel Polanski committed
        BuildStep(bc, bs), m_timer(0),
        m_releaseDeviceAfterLauncherFinish(bs->m_releaseDeviceAfterLauncherFinish),
        m_handleDeviceRemoval(bs->m_handleDeviceRemoval),
Pawel Polanski's avatar
Pawel Polanski committed
        m_launcher(0), m_eventLoop(0)
Tobias Hunger's avatar
Tobias Hunger committed
S60DeployStep::S60DeployStep(ProjectExplorer::BuildStepList *bc):
        BuildStep(bc, QLatin1String(S60_DEPLOY_STEP_ID)), m_timer(0),
Pawel Polanski's avatar
Pawel Polanski committed
        m_releaseDeviceAfterLauncherFinish(true),
        m_handleDeviceRemoval(true), m_launcher(0), m_eventLoop(0)
    ctor();
}

void S60DeployStep::ctor()
{
    //: Qt4 Deploystep display name
    setDefaultDisplayName(tr("Deploy"));
}

S60DeployStep::~S60DeployStep()
{
    delete m_timer;
    delete m_launcher;
    delete m_eventLoop;
}


bool S60DeployStep::init()
{
    Qt4BuildConfiguration *bc = static_cast<Qt4BuildConfiguration *>(buildConfiguration());
    S60DeployConfiguration* deployConfiguration = static_cast<S60DeployConfiguration *>(bc->target()->activeDeployConfiguration());
    if(!deployConfiguration)
        return false;
    m_serialPortName = deployConfiguration->serialPortName();
    m_serialPortFriendlyName = SymbianUtils::SymbianDeviceManager::instance()->friendlyNameForPort(m_serialPortName);
    m_packageFileNamesWithTarget = deployConfiguration->packageFileNamesWithTargetInfo();
    m_signedPackages = deployConfiguration->signedPackages();
    m_installationDrive = deployConfiguration->installationDrive();
    m_silentInstall = deployConfiguration->silentInstall();

    QString message;
    if (m_launcher) {
        trk::Launcher::releaseToDeviceManager(m_launcher);
        delete m_launcher;
        m_launcher = 0;
    }

    m_launcher = trk::Launcher::acquireFromDeviceManager(m_serialPortName, this, &message);
    if (!message.isEmpty() || !m_launcher) {
        if (m_launcher)
            trk::Launcher::releaseToDeviceManager(m_launcher);
        delete m_launcher;
        m_launcher = 0;
        appendMessage(message, true);
        return false;
    }
    // Prompt the user to start up the Blue tooth connection
    const trk::PromptStartCommunicationResult src =
            S60RunConfigBluetoothStarter::startCommunication(m_launcher->trkDevice(),
                                                             0, &message);
    if (src != trk::PromptStartCommunicationConnected) {
        if (!message.isEmpty())
            trk::Launcher::releaseToDeviceManager(m_launcher);
        delete m_launcher;
        m_launcher = 0;
        appendMessage(message, true);
        return false;
    }
    return true;
}

QVariantMap S60DeployStep::toMap() const
{
    return BuildStep::toMap();
}

bool S60DeployStep::fromMap(const QVariantMap &map)
{
    return BuildStep::fromMap(map);
}

void S60DeployStep::appendMessage(const QString &error, bool isError)
{
    emit addOutput(error, isError?ProjectExplorer::BuildStep::ErrorMessageOutput:
                                  ProjectExplorer::BuildStep::MessageOutput);
}

bool S60DeployStep::processPackageName(QString &errorMessage)
{
    for (int i = 0; i < m_signedPackages.count(); ++i) {
        QFileInfo packageInfo(m_signedPackages.at(i));
        // support for 4.6.1 and pre, where make sis creates 'targetname_armX_udeb.sis' instead of 'targetname.sis'
        QFileInfo packageWithTargetInfo(m_packageFileNamesWithTarget.at(i));
        // does the 4.6.1 version exist?
        if (packageWithTargetInfo.exists() && packageWithTargetInfo.isFile()) {
            // is the 4.6.1 version newer? (to guard against behavior change Qt Creator 1.3 --> 2.0)
            if (!packageInfo.exists() || packageInfo.lastModified() < packageWithTargetInfo.lastModified()) { //TODO change the QtCore
                // the 'targetname_armX_udeb.sis' crap exists and is new, rename it
                appendMessage(tr("Renaming new package '%1' to '%2'")
                              .arg(QDir::toNativeSeparators(m_packageFileNamesWithTarget.at(i)),
                                   QDir::toNativeSeparators(m_signedPackages.at(i))), false);
                return renameFile(m_packageFileNamesWithTarget.at(i), m_signedPackages.at(i), &errorMessage);
            } else {
                // the 'targetname_armX_udeb.sis' crap exists but is old, remove it
                appendMessage(tr("Removing old package '%1'")
                              .arg(QDir::toNativeSeparators(m_packageFileNamesWithTarget.at(i))),
                ensureDeleteFile(m_packageFileNamesWithTarget.at(i), &errorMessage);
        if (!packageInfo.exists() || !packageInfo.isFile()) {
            errorMessage = tr("'%1': Package file not found").arg(m_signedPackages.at(i));
            return false;
        }
    }
    return true;
}

void S60DeployStep::start()
{
    if (m_serialPortName.isEmpty()) {
        appendMessage(tr("There is no device plugged in."), true);
        emit finished();
        return;
    }

    QString errorMessage;

    // make sure we have the right name of the sis package
    if (processPackageName(errorMessage)) {
        startDeployment();
    } else {
        errorMessage = tr("Failed to find package %1").arg(errorMessage);
        appendMessage(errorMessage, true);
        stop();
        emit finished();
    }
}

void S60DeployStep::stop()
{
    if (m_launcher)
        m_launcher->terminate();
    emit finished();
}

void S60DeployStep::setupConnections()
{
    connect(SymbianUtils::SymbianDeviceManager::instance(), SIGNAL(deviceRemoved(SymbianUtils::SymbianDevice)),
            this, SLOT(deviceRemoved(SymbianUtils::SymbianDevice)));
    connect(m_launcher, SIGNAL(finished()), this, SLOT(launcherFinished()));

    connect(m_launcher, SIGNAL(canNotConnect(QString)), this, SLOT(connectFailed(QString)));
    connect(m_launcher, SIGNAL(copyingStarted(QString)), this, SLOT(printCopyingNotice(QString)));
    connect(m_launcher, SIGNAL(canNotCreateFile(QString,QString)), this, SLOT(createFileFailed(QString,QString)));
    connect(m_launcher, SIGNAL(canNotWriteFile(QString,QString)), this, SLOT(writeFileFailed(QString,QString)));
    connect(m_launcher, SIGNAL(canNotCloseFile(QString,QString)), this, SLOT(closeFileFailed(QString,QString)));
    connect(m_launcher, SIGNAL(installingStarted(QString)), this, SLOT(printInstallingNotice(QString)));
    connect(m_launcher, SIGNAL(canNotInstall(QString,QString)), this, SLOT(installFailed(QString,QString)));
    connect(m_launcher, SIGNAL(installingFinished()), this, SLOT(printInstallingFinished()));
    connect(m_launcher, SIGNAL(stateChanged(int)), this, SLOT(slotLauncherStateChanged(int)));
}

void S60DeployStep::startDeployment()
{
    Q_ASSERT(m_launcher);

    QStringList copyDst;
    foreach (const QString &signedPackage, m_signedPackages)
        copyDst << QString::fromLatin1("%1:\\Data\\%2").arg(m_installationDrive).arg(QFileInfo(signedPackage).fileName());
    m_launcher->setCopyFileNames(m_signedPackages, copyDst);
    m_launcher->setInstallFileNames(copyDst);
    m_launcher->setInstallationDrive(m_installationDrive);
    m_launcher->setInstallationMode(m_silentInstall?trk::Launcher::InstallationModeSilentAndUser:
                                                    trk::Launcher::InstallationModeUser);
    m_launcher->addStartupActions(trk::Launcher::ActionCopyInstall);

    // TODO readd information about packages? msgListFile(m_signedPackage)
    appendMessage(tr("Deploying application to '%2'...").arg(m_serialPortFriendlyName), false);
    QString errorMessage;
    if (!m_launcher->startServer(&errorMessage)) {
        errorMessage = tr("Could not connect to phone on port '%1': %2\n"
                          "Check if the phone is connected and App TRK is running.").arg(m_serialPortName, errorMessage);
        appendMessage(errorMessage, true);
        stop();
        emit finished();
    }
}

void S60DeployStep::run(QFutureInterface<bool> &fi)
{
    m_futureInterface = &fi;
    connect(this, SIGNAL(finished()),
            this, SLOT(launcherFinished()));
    connect(this, SIGNAL(finishNow()),
            this, SLOT(launcherFinished()), Qt::DirectConnection);

    start();
    m_timer = new QTimer();
    connect(m_timer, SIGNAL(timeout()), this, SLOT(checkForCancel()), Qt::DirectConnection);
    m_timer->start(500);
    m_eventLoop = new QEventLoop();
    m_eventLoop->exec();
    m_timer->stop();
    delete m_timer;
    m_timer = 0;

    delete m_eventLoop;
    m_eventLoop = 0;
    fi.reportResult(m_deployResult);
    m_futureInterface = 0;
}

void S60DeployStep::setReleaseDeviceAfterLauncherFinish(bool v)
{
    m_releaseDeviceAfterLauncherFinish = v;
}

void S60DeployStep::slotLauncherStateChanged(int s)
{
    if (s == trk::Launcher::WaitingForTrk) {
        QMessageBox *mb = S60DeviceRunControl::createTrkWaitingMessageBox(m_launcher->trkServerName(),
                                                                              Core::ICore::instance()->mainWindow());
        connect(m_launcher, SIGNAL(stateChanged(int)), mb, SLOT(close()));
        connect(mb, SIGNAL(finished(int)), this, SLOT(slotWaitingForTrkClosed()));
        mb->open();
    }
}

void S60DeployStep::slotWaitingForTrkClosed()
{
    if (m_launcher && m_launcher->state() == trk::Launcher::WaitingForTrk) {
        stop();
        appendMessage(tr("Canceled."), true);
        emit finished();
    }
}

void S60DeployStep::createFileFailed(const QString &filename, const QString &errorMessage)
{
    appendMessage(tr("Could not create file %1 on device: %2").arg(filename, errorMessage), true);
void S60DeployStep::writeFileFailed(const QString &filename, const QString &errorMessage)
{
    appendMessage(tr("Could not write to file %1 on device: %2").arg(filename, errorMessage), true);
void S60DeployStep::closeFileFailed(const QString &filename, const QString &errorMessage)
{
    const QString msg = tr("Could not close file %1 on device: %2. It will be closed when App TRK is closed.");
    appendMessage( msg.arg(filename, errorMessage), true);
void S60DeployStep::connectFailed(const QString &errorMessage)
{
    appendMessage(tr("Could not connect to App TRK on device: %1. Restarting App TRK might help.").arg(errorMessage), true);
void S60DeployStep::printCopyingNotice(const QString &fileName)
    appendMessage(tr("Copying \"%1\"...").arg(fileName), false);
void S60DeployStep::printInstallingNotice(const QString &packageName)
    appendMessage(tr("Installing package \"%1\" on drive %2:...").arg(packageName).arg(m_installationDrive), false);
}

void S60DeployStep::printInstallingFinished()
{
    appendMessage(tr("Installation has finished"), false);
}

void S60DeployStep::installFailed(const QString &filename, const QString &errorMessage)
{
    appendMessage(tr("Could not install from package %1 on device: %2").arg(filename, errorMessage), true);
}

void S60DeployStep::checkForCancel()
{
    if (m_futureInterface->isCanceled() && m_timer->isActive()) {
        m_timer->stop();
        stop();
        appendMessage(tr("Canceled."), true);
        emit finishNow();
    }
}

void S60DeployStep::launcherFinished()
{
    if (m_releaseDeviceAfterLauncherFinish && m_launcher) {
        m_handleDeviceRemoval = false;
        trk::Launcher::releaseToDeviceManager(m_launcher);
    }
    if(m_launcher)
        m_launcher->deleteLater();
    m_launcher = 0;
    if(m_eventLoop)
}

void S60DeployStep::deviceRemoved(const SymbianUtils::SymbianDevice &d)
{
    if (m_handleDeviceRemoval && d.portName() == m_serialPortName) {
        appendMessage(tr("The device '%1' has been disconnected").arg(d.friendlyName()), true);
        emit finished();
    }
}

// #pragma mark -- S60DeployStepWidget

BuildStepConfigWidget *S60DeployStep::createConfigWidget()
{
    return new S60DeployStepWidget();
}

void S60DeployStepWidget::init()
{
}

QString S60DeployStepWidget::summaryText() const
{
Tobias Hunger's avatar
Tobias Hunger committed
    return QString("<b>%1</b>").arg(displayName());
}

QString S60DeployStepWidget::displayName() const
{
Tobias Hunger's avatar
Tobias Hunger committed
    return tr("Deploy SIS Package");
}

// #pragma mark -- S60DeployStepFactory

S60DeployStepFactory::S60DeployStepFactory(QObject *parent) :
        ProjectExplorer::IBuildStepFactory(parent)
{
}

S60DeployStepFactory::~S60DeployStepFactory()
{
}

Tobias Hunger's avatar
Tobias Hunger committed
bool S60DeployStepFactory::canCreate(ProjectExplorer::BuildStepList *parent, const QString &id) const
Tobias Hunger's avatar
Tobias Hunger committed
    if (parent->id() != QLatin1String(ProjectExplorer::Constants::BUILDSTEPS_DEPLOY))
        return false;
    if (parent->target()->id() != QLatin1String(Constants::S60_DEVICE_TARGET_ID))
        return false;
    return (id == QLatin1String(S60_DEPLOY_STEP_ID));
}

Tobias Hunger's avatar
Tobias Hunger committed
ProjectExplorer::BuildStep *S60DeployStepFactory::create(ProjectExplorer::BuildStepList *parent, const QString &id)
Tobias Hunger's avatar
Tobias Hunger committed
    if (!canCreate(parent, id))
Tobias Hunger's avatar
Tobias Hunger committed
    return new S60DeployStep(parent);
Tobias Hunger's avatar
Tobias Hunger committed
bool S60DeployStepFactory::canClone(ProjectExplorer::BuildStepList *parent, ProjectExplorer::BuildStep *source) const
Tobias Hunger's avatar
Tobias Hunger committed
    if (!canCreate(parent, source->id()))
        return false;
    if (!qobject_cast<S60DeployStep *>(source))
        return false;
    return true;
Tobias Hunger's avatar
Tobias Hunger committed
ProjectExplorer::BuildStep *S60DeployStepFactory::clone(ProjectExplorer::BuildStepList *parent, ProjectExplorer::BuildStep *source)
Tobias Hunger's avatar
Tobias Hunger committed
    if (!canClone(parent, source))
        return 0;
    return new S60DeployStep(parent, static_cast<S60DeployStep *>(source));
}

Tobias Hunger's avatar
Tobias Hunger committed
bool S60DeployStepFactory::canRestore(ProjectExplorer::BuildStepList *parent, const QVariantMap &map) const
{
    QString id(ProjectExplorer::idFromMap(map));
Tobias Hunger's avatar
Tobias Hunger committed
    return canCreate(parent, id);
Tobias Hunger's avatar
Tobias Hunger committed
ProjectExplorer::BuildStep *S60DeployStepFactory::restore(ProjectExplorer::BuildStepList *parent, const QVariantMap &map)
Tobias Hunger's avatar
Tobias Hunger committed
    if (!canRestore(parent, map))
        return 0;
    S60DeployStep *bs = new S60DeployStep(parent);
    if (bs->fromMap(map))
        return bs;
    delete bs;
    return 0;
}

Tobias Hunger's avatar
Tobias Hunger committed
QStringList S60DeployStepFactory::availableCreationIds(ProjectExplorer::BuildStepList *parent) const
Tobias Hunger's avatar
Tobias Hunger committed
    if (parent->id() == QLatin1String(ProjectExplorer::Constants::BUILDSTEPS_DEPLOY)
        && parent->target()->id() == QLatin1String(Constants::S60_DEVICE_TARGET_ID))
        return QStringList() << QLatin1String(S60_DEPLOY_STEP_ID);
    return QStringList();
}

QString S60DeployStepFactory::displayNameForId(const QString &id) const
{
    if (id == QLatin1String(S60_DEPLOY_STEP_ID))
        return tr("Deploy SIS Package");
    return QString();
}