From ce5e47343853576ea9fe35e10037e87b6f1dd616 Mon Sep 17 00:00:00 2001
From: ck <qt-info@nokia.com>
Date: Tue, 13 Jul 2010 15:24:21 +0200
Subject: [PATCH] Maemo: Move deploying to dedicated deploy step.

Reviewed-by: kh1
---
 .../qt-maemo/maemodeploystep.cpp              | 418 +++++++++++++++++-
 .../qt-maemo/maemodeploystep.h                |  86 ++++
 .../qt-maemo/maemodeploystepwidget.cpp        |  25 +-
 .../qt-maemo/maemodeploystepwidget.h          |   6 +-
 .../qt-maemo/maemodeviceconfigurations.cpp    |   9 +-
 .../qt-maemo/maemodeviceconfigurations.h      |   1 +
 .../qt-maemo/maemorunconfiguration.cpp        |  72 +--
 .../qt-maemo/maemorunconfiguration.h          |  11 +-
 .../qt-maemo/maemoruncontrol.cpp              | 345 ++++-----------
 .../qt-maemo/maemoruncontrol.h                |  39 +-
 src/plugins/qt4projectmanager/qt4target.cpp   |   4 +-
 11 files changed, 651 insertions(+), 365 deletions(-)

diff --git a/src/plugins/qt4projectmanager/qt-maemo/maemodeploystep.cpp b/src/plugins/qt4projectmanager/qt-maemo/maemodeploystep.cpp
index c046d472c07..a003657fd01 100644
--- a/src/plugins/qt4projectmanager/qt-maemo/maemodeploystep.cpp
+++ b/src/plugins/qt4projectmanager/qt-maemo/maemodeploystep.cpp
@@ -1,7 +1,25 @@
 #include "maemodeploystep.h"
 
+#include "maemoconstants.h"
+#include "maemodeployables.h"
 #include "maemodeploystepwidget.h"
+#include "maemopackagecreationstep.h"
+#include "maemorunconfiguration.h"
 
+#include <coreplugin/ssh/sftpchannel.h>
+#include <coreplugin/ssh/sshconnection.h>
+#include <coreplugin/ssh/sshremoteprocess.h>
+
+#include <projectexplorer/buildconfiguration.h>
+#include <projectexplorer/projectexplorerconstants.h>
+#include <projectexplorer/target.h>
+
+#include <QtCore/QCryptographicHash>
+#include <QtCore/QEventLoop>
+#include <QtCore/QFileInfo>
+#include <QtCore/QTimer>
+
+using namespace Core;
 using namespace ProjectExplorer;
 
 namespace Qt4ProjectManager {
@@ -13,11 +31,19 @@ const QLatin1String MaemoDeployStep::Id("Qt4ProjectManager.MaemoDeployStep");
 MaemoDeployStep::MaemoDeployStep(ProjectExplorer::BuildConfiguration *bc)
     : BuildStep(bc, Id)
 {
+    ctor();
 }
 
 MaemoDeployStep::MaemoDeployStep(ProjectExplorer::BuildConfiguration *bc,
     MaemoDeployStep *other)
-    : BuildStep(bc, other)
+    : BuildStep(bc, other), m_lastDeployed(other->m_lastDeployed)
+{
+    ctor();
+}
+
+MaemoDeployStep::~MaemoDeployStep() {}
+
+void MaemoDeployStep::ctor()
 {
 }
 
@@ -28,12 +54,398 @@ bool MaemoDeployStep::init()
 
 void MaemoDeployStep::run(QFutureInterface<bool> &fi)
 {
-    fi.reportResult(true);
+    // Move to GUI thread for connection sharing with run control.
+    QTimer::singleShot(0, this, SLOT(start()));
+
+    MaemoDeployEventHandler eventHandler(this, fi);
 }
 
 BuildStepConfigWidget *MaemoDeployStep::createConfigWidget()
 {
-    return new MaemoDeployStepWidget;
+    return new MaemoDeployStepWidget(this);
+}
+
+QVariantMap MaemoDeployStep::toMap() const
+{
+    QVariantMap map(BuildStep::toMap());
+    addDeployTimesToMap(map);
+    return map;
+}
+
+void MaemoDeployStep::addDeployTimesToMap(QVariantMap &map) const
+{
+    QVariantList hostList;
+    QVariantList fileList;
+    QVariantList remotePathList;
+    QVariantList timeList;
+    typedef QHash<DeployablePerHost, QDateTime>::ConstIterator DepIt;
+    for (DepIt it = m_lastDeployed.begin(); it != m_lastDeployed.end(); ++it) {
+        fileList << it.key().first.localFilePath;
+        remotePathList << it.key().first.remoteDir;
+        hostList << it.key().second;
+        timeList << it.value();
+    }
+    map.insert(LastDeployedHostsKey, hostList);
+    map.insert(LastDeployedFilesKey, fileList);
+    map.insert(LastDeployedRemotePathsKey, remotePathList);
+    map.insert(LastDeployedTimesKey, timeList);
+}
+
+bool MaemoDeployStep::fromMap(const QVariantMap &map)
+{
+    if (!BuildStep::fromMap(map))
+        return false;
+    getDeployTimesFromMap(map);
+    return true;
+}
+
+void MaemoDeployStep::getDeployTimesFromMap(const QVariantMap &map)
+{
+    const QVariantList &hostList = map.value(LastDeployedHostsKey).toList();
+    const QVariantList &fileList = map.value(LastDeployedFilesKey).toList();
+    const QVariantList &remotePathList
+        = map.value(LastDeployedRemotePathsKey).toList();
+    const QVariantList &timeList = map.value(LastDeployedTimesKey).toList();
+    const int elemCount
+        = qMin(qMin(hostList.size(), fileList.size()),
+            qMin(remotePathList.size(), timeList.size()));
+    for (int i = 0; i < elemCount; ++i) {
+        const MaemoDeployable d(fileList.at(i).toString(),
+            remotePathList.at(i).toString());
+        m_lastDeployed.insert(DeployablePerHost(d, hostList.at(i).toString()),
+            timeList.at(i).toDateTime());
+    }
+}
+
+const MaemoPackageCreationStep *MaemoDeployStep::packagingStep() const
+{
+    const QList<ProjectExplorer::BuildStep *> &buildSteps
+        = buildConfiguration()->steps(ProjectExplorer::BuildStep::Deploy);
+    for (int i = buildSteps.count() - 1; i >= 0; --i) {
+        const MaemoPackageCreationStep * const pStep
+                = qobject_cast<MaemoPackageCreationStep *>(buildSteps.at(i));
+        if (pStep)
+            return pStep;
+    }
+    Q_ASSERT(!"Impossible: Maemo run configuration without packaging step.");
+    return 0;
+}
+
+void MaemoDeployStep::raiseError(const QString &errorString)
+{
+    emit addTask(Task(Task::Error, errorString, QString(), -1,
+        Constants::TASK_CATEGORY_BUILDSYSTEM));
+    stop();
+    emit error();
+}
+
+void MaemoDeployStep::writeOutput(const QString &text,
+    const QTextCharFormat &format)
+{
+    emit addOutput(text, format);
+}
+
+void MaemoDeployStep::stop()
+{
+    if (m_installer && m_installer->isRunning()) {
+        disconnect(m_installer.data(), 0, this, 0);
+    } else if (!m_uploadsInProgress.isEmpty() || !m_linksInProgress.isEmpty()) {
+        m_uploadsInProgress.clear();
+        m_linksInProgress.clear();
+        m_uploader->closeChannel();
+        disconnect(m_uploader.data(), 0, this, 0);
+    }
+    if (m_connection)
+        disconnect(m_connection.data(), 0, this, 0);
+    m_stopped = true;
+}
+
+QString MaemoDeployStep::uploadDir() const
+{
+    return homeDirOnDevice(m_connection->connectionParameters().uname);
+}
+
+bool MaemoDeployStep::currentlyNeedsDeployment(const QString &host,
+    const MaemoDeployable &deployable) const
+{
+    const QDateTime &lastDeployed
+        = m_lastDeployed.value(DeployablePerHost(deployable, host));
+    return !lastDeployed.isValid()
+        || QFileInfo(deployable.localFilePath).lastModified() > lastDeployed;
+}
+
+void MaemoDeployStep::setDeployed(const QString &host,
+    const MaemoDeployable &deployable)
+{
+    m_lastDeployed.insert(DeployablePerHost(deployable, host),
+        QDateTime::currentDateTime());
+}
+
+MaemoDeviceConfig MaemoDeployStep::deviceConfig() const
+{
+    // TODO: For lib template, get info from config widget
+    const RunConfiguration * const rc =
+        buildConfiguration()->target()->activeRunConfiguration();
+    return rc ? qobject_cast<const MaemoRunConfiguration *>(rc)->deviceConfig()
+        : MaemoDeviceConfig();
+}
+
+QString MaemoDeployStep::packageFileName() const
+{
+    return QFileInfo(packageFilePath()).fileName();
+}
+
+QString MaemoDeployStep::packageFilePath() const
+{
+    return packagingStep()->packageFilePath();
+}
+
+void MaemoDeployStep::start()
+{
+    m_stopped = false;
+    if (m_stopped)
+        return;
+
+    // TODO: Re-use if possible (disconnect + reconnect).
+    if (m_connection)
+        disconnect(m_connection.data(), 0, this, 0);
+    m_connection = SshConnection::create();
+
+    const MaemoDeviceConfig &devConfig = deviceConfig();
+    if (!devConfig.isValid()) {
+        raiseError(tr("Deployment failed: No valid device set."));
+        return;
+    }
+    connect(m_connection.data(), SIGNAL(connected()), this,
+        SLOT(handleConnected()));
+    connect(m_connection.data(), SIGNAL(error(SshError)), this,
+        SLOT(handleConnectionFailure()));
+    m_connection->connectToHost(devConfig.server);
+}
+
+void MaemoDeployStep::handleConnected()
+{
+    if (m_stopped)
+        return;
+
+    // TODO: If nothing to deploy, skip this step.
+    m_uploader = m_connection->createSftpChannel();
+    connect(m_uploader.data(), SIGNAL(initialized()), this,
+        SLOT(handleSftpChannelInitialized()));
+    connect(m_uploader.data(), SIGNAL(initializationFailed(QString)), this,
+        SLOT(handleSftpChannelInitializationFailed(QString)));
+    connect(m_uploader.data(), SIGNAL(finished(Core::SftpJobId, QString)),
+        this, SLOT(handleSftpJobFinished(Core::SftpJobId, QString)));
+    m_uploader->initialize();
+}
+
+void MaemoDeployStep::handleConnectionFailure()
+{
+    if (m_stopped)
+        return;
+
+    raiseError(tr("Could not connect to host: %1")
+        .arg(m_connection->errorString()));
+}
+
+void MaemoDeployStep::handleSftpChannelInitialized()
+{
+    if (m_stopped)
+        return;
+
+    m_uploadsInProgress.clear();
+    m_linksInProgress.clear();
+    m_needsInstall = false;
+    const MaemoPackageCreationStep * const pStep = packagingStep();
+    const QString hostName = m_connection->connectionParameters().host;
+    if (pStep->isPackagingEnabled()) {
+        const MaemoDeployable d(packageFilePath(), uploadDir());
+        if (currentlyNeedsDeployment(hostName, d)) {
+            if (!deploy(MaemoDeployable(d)))
+                return;
+            m_needsInstall = true;
+        } else {
+            m_needsInstall = false;
+        }
+    } else {
+        const MaemoDeployables * const deployables = pStep->deployables();
+        const int deployableCount = deployables->deployableCount();
+        for (int i = 0; i < deployableCount; ++i) {
+            const MaemoDeployable &d = deployables->deployableAt(i);
+            if (currentlyNeedsDeployment(hostName, d)
+                && !deploy(MaemoDeployable(d)))
+                return;
+        }
+        m_needsInstall = false;
+    }
+    if (m_uploadsInProgress.isEmpty())
+        emit done();
+}
+
+bool MaemoDeployStep::deploy(const MaemoDeployable &deployable)
+{
+    const QString fileName = QFileInfo(deployable.localFilePath).fileName();
+    const QString remoteFilePath = deployable.remoteDir + '/' + fileName;
+    const QString uploadFilePath = uploadDir() + '/' + fileName + '.'
+        + QCryptographicHash::hash(remoteFilePath.toUtf8(),
+              QCryptographicHash::Md5).toHex();
+    const SftpJobId job = m_uploader->uploadFile(deployable.localFilePath,
+        uploadFilePath, SftpOverwriteExisting);
+    if (job == SftpInvalidJob) {
+        raiseError(tr("Upload failed: Could not open file '%1'")
+            .arg(deployable.localFilePath));
+        return false;
+    }
+    writeOutput(tr("Started uploading file '%1'.").arg(deployable.localFilePath));
+    m_uploadsInProgress.insert(job, DeployInfo(deployable, uploadFilePath));
+    return true;
+}
+
+void MaemoDeployStep::handleSftpChannelInitializationFailed(const QString &error)
+{
+    if (m_stopped)
+        return;
+    raiseError(tr("Could not set up SFTP connection: %1").arg(error));
+}
+
+void MaemoDeployStep::handleSftpJobFinished(Core::SftpJobId job,
+    const QString &error)
+{
+    if (m_stopped)
+        return;
+
+    QMap<SftpJobId, DeployInfo>::Iterator it = m_uploadsInProgress.find(job);
+    if (it == m_uploadsInProgress.end()) {
+        qWarning("%s: Job %u not found in map.", Q_FUNC_INFO, job);
+        return;
+    }
+
+    const DeployInfo &deployInfo = it.value();
+    if (!error.isEmpty()) {
+        raiseError(tr("Failed to upload file %1: %2")
+            .arg(deployInfo.first.localFilePath, error));
+        return;
+    }
+
+    writeOutput(tr("Successfully uploaded file '%1'.")
+        .arg(deployInfo.first.localFilePath));
+    const QString remoteFilePath = deployInfo.first.remoteDir + '/'
+        + QFileInfo(deployInfo.first.localFilePath).fileName();
+    QByteArray linkCommand = remoteSudo().toUtf8() + " ln -sf "
+        + deployInfo.second.toUtf8() + ' ' + remoteFilePath.toUtf8();
+    SshRemoteProcess::Ptr linkProcess
+        = m_connection->createRemoteProcess(linkCommand);
+    connect(linkProcess.data(), SIGNAL(closed(int)), this,
+        SLOT(handleLinkProcessFinished(int)));
+    m_linksInProgress.insert(linkProcess, deployInfo.first);
+    linkProcess->start();
+    m_uploadsInProgress.erase(it);
+}
+
+void MaemoDeployStep::handleLinkProcessFinished(int exitStatus)
+{
+    if (m_stopped)
+        return;
+
+    SshRemoteProcess * const proc = static_cast<SshRemoteProcess *>(sender());
+
+    // TODO: List instead of map? We can't use it for lookup anyway.
+    QMap<SshRemoteProcess::Ptr, MaemoDeployable>::Iterator it;
+    for (it = m_linksInProgress.begin(); it != m_linksInProgress.end(); ++it) {
+        if (it.key().data() == proc)
+            break;
+    }
+    if (it == m_linksInProgress.end()) {
+        qWarning("%s: Remote process %p not found in process list.",
+            Q_FUNC_INFO, proc);
+        return;
+    }
+
+    const MaemoDeployable &deployable = it.value();
+    if (exitStatus != SshRemoteProcess::ExitedNormally
+        || proc->exitCode() != 0) {
+        raiseError(tr("Deployment failed for file '%1': "
+            "Could not create link '%2' on remote system.")
+            .arg(deployable.localFilePath, deployable.remoteDir + '/'
+                + QFileInfo(deployable.localFilePath).fileName()));
+        return;
+    }
+
+    setDeployed(m_connection->connectionParameters().host, it.value());
+    m_linksInProgress.erase(it);
+    if (m_linksInProgress.isEmpty() && m_uploadsInProgress.isEmpty()) {
+        if (m_needsInstall) {
+            writeOutput(tr("Installing package ..."));
+            const QByteArray cmd = remoteSudo().toUtf8() + " dpkg -i "
+                + packageFileName().toUtf8();
+            m_installer = m_connection->createRemoteProcess(cmd);
+            connect(m_installer.data(), SIGNAL(closed(int)), this,
+                SLOT(handleInstallationFinished(int)));
+            connect(m_installer.data(), SIGNAL(outputAvailable(QByteArray)),
+                this, SLOT(handleInstallerOutput(QByteArray)));
+            connect(m_installer.data(),
+                SIGNAL(errorOutputAvailable(QByteArray)), this,
+                SLOT(handleInstallerErrorOutput(QByteArray)));
+            m_installer->start();
+        } else {
+            emit done();
+        }
+    }
+}
+
+void MaemoDeployStep::handleInstallationFinished(int exitStatus)
+{
+    if (m_stopped)
+        return;
+
+    if (exitStatus != SshRemoteProcess::ExitedNormally
+        || m_installer->exitCode() != 0) {
+        raiseError(tr("Installing package failed."));
+    } else {
+        writeOutput(tr("Package installation finished."));
+        emit done();
+    }
+}
+
+void MaemoDeployStep::handleInstallerOutput(const QByteArray &output)
+{
+    writeOutput(QString::fromUtf8(output));
+}
+
+void MaemoDeployStep::handleInstallerErrorOutput(const QByteArray &output)
+{
+    QTextCharFormat format;
+    format.setForeground(QBrush(QColor("red")));
+    writeOutput(output, format);
+}
+
+
+MaemoDeployEventHandler::MaemoDeployEventHandler(MaemoDeployStep *deployStep,
+    QFutureInterface<bool> &future)
+    : m_deployStep(deployStep), m_future(future), m_eventLoop(new QEventLoop)
+{
+    connect(m_deployStep, SIGNAL(done()), this, SLOT(handleDeployingDone()));
+    connect(m_deployStep, SIGNAL(error()), this, SLOT(handleDeployingFailed()));
+    QTimer cancelChecker;
+    connect(&cancelChecker, SIGNAL(timeout()), this, SLOT(checkForCanceled()));
+    cancelChecker.start(500);
+    future.reportResult(m_eventLoop->exec() == 0);
+}
+
+void MaemoDeployEventHandler::handleDeployingDone()
+{
+    m_eventLoop->exit(0);
+}
+
+void MaemoDeployEventHandler::handleDeployingFailed()
+{
+    m_eventLoop->exit(1);
+}
+
+void MaemoDeployEventHandler::checkForCanceled()
+{
+    if (m_future.isCanceled())
+        handleDeployingFailed();
 }
 
 } // namespace Internal
diff --git a/src/plugins/qt4projectmanager/qt-maemo/maemodeploystep.h b/src/plugins/qt4projectmanager/qt-maemo/maemodeploystep.h
index 24d4e6c1294..e8a470ea162 100644
--- a/src/plugins/qt4projectmanager/qt-maemo/maemodeploystep.h
+++ b/src/plugins/qt4projectmanager/qt-maemo/maemodeploystep.h
@@ -1,10 +1,31 @@
 #ifndef MAEMODEPLOYSTEP_H
 #define MAEMODEPLOYSTEP_H
 
+#include "maemodeployable.h"
+#include "maemodeviceconfigurations.h"
+
+#include <coreplugin/ssh/sftpdefs.h>
 #include <projectexplorer/buildstep.h>
 
+#include <QtCore/QHash>
+#include <QtCore/QList>
+#include <QtCore/QMap>
+#include <QtCore/QPair>
+#include <QtCore/QSharedPointer>
+
+QT_BEGIN_NAMESPACE
+class QEventLoop;
+QT_END_NAMESPACE
+
+namespace Core {
+class SftpChannel;
+class SshConnection;
+class SshRemoteProcess;
+}
+
 namespace Qt4ProjectManager {
 namespace Internal {
+class MaemoPackageCreationStep;
 
 class MaemoDeployStep : public ProjectExplorer::BuildStep
 {
@@ -12,6 +33,27 @@ class MaemoDeployStep : public ProjectExplorer::BuildStep
     friend class MaemoDeployStepFactory;
 public:
     MaemoDeployStep(ProjectExplorer::BuildConfiguration *bc);
+    virtual ~MaemoDeployStep();
+    MaemoDeviceConfig deviceConfig() const;
+    bool currentlyNeedsDeployment(const QString &host,
+        const MaemoDeployable &deployable) const;
+    void setDeployed(const QString &host, const MaemoDeployable &deployable);
+
+signals:
+    void done();
+    void error();
+
+private slots:
+    void start();
+    void handleConnected();
+    void handleConnectionFailure();
+    void handleSftpChannelInitialized();
+    void handleSftpChannelInitializationFailed(const QString &error);
+    void handleSftpJobFinished(Core::SftpJobId job, const QString &error);
+    void handleLinkProcessFinished(int exitStatus);
+    void handleInstallationFinished(int exitStatus);
+    void handleInstallerOutput(const QByteArray &output);
+    void handleInstallerErrorOutput(const QByteArray &output);
 
 private:
     MaemoDeployStep(ProjectExplorer::BuildConfiguration *bc,
@@ -20,8 +62,52 @@ private:
     virtual void run(QFutureInterface<bool> &fi);
     virtual ProjectExplorer::BuildStepConfigWidget *createConfigWidget();
     virtual bool immutable() const { return true; }
+    virtual QVariantMap toMap() const;
+    virtual bool fromMap(const QVariantMap &map);
+
+    void ctor();
+    void stop();
+    void raiseError(const QString &error);
+    void writeOutput(const QString &text,
+        const QTextCharFormat &format = QTextCharFormat());
+    void addDeployTimesToMap(QVariantMap &map) const;
+    void getDeployTimesFromMap(const QVariantMap &map);
+    const MaemoPackageCreationStep *packagingStep() const;
+    bool deploy(const MaemoDeployable &deployable);
+    QString uploadDir() const;
+    QString packageFileName() const;
+    QString packageFilePath() const;
 
     static const QLatin1String Id;
+
+    QSharedPointer<Core::SshConnection> m_connection;
+    QSharedPointer<Core::SftpChannel> m_uploader;
+    QSharedPointer<Core::SshRemoteProcess> m_installer;
+    typedef QPair<MaemoDeployable, QString> DeployInfo;
+    QMap<Core::SftpJobId, DeployInfo> m_uploadsInProgress;
+    QMap<QSharedPointer<Core::SshRemoteProcess>, MaemoDeployable> m_linksInProgress;
+    bool m_needsInstall;
+    bool m_stopped;
+    typedef QPair<MaemoDeployable, QString> DeployablePerHost;
+    QHash<DeployablePerHost, QDateTime> m_lastDeployed;
+};
+
+class MaemoDeployEventHandler : public QObject
+{
+    Q_OBJECT
+public:
+    MaemoDeployEventHandler(MaemoDeployStep *deployStep,
+        QFutureInterface<bool> &future);
+
+private slots:
+    void handleDeployingDone();
+    void handleDeployingFailed();
+    void checkForCanceled();
+
+private:
+    const MaemoDeployStep * const m_deployStep;
+    const QFutureInterface<bool> m_future;
+    QEventLoop * const m_eventLoop;
 };
 
 } // namespace Internal
diff --git a/src/plugins/qt4projectmanager/qt-maemo/maemodeploystepwidget.cpp b/src/plugins/qt4projectmanager/qt-maemo/maemodeploystepwidget.cpp
index 70569eef479..41059e52ffd 100644
--- a/src/plugins/qt4projectmanager/qt-maemo/maemodeploystepwidget.cpp
+++ b/src/plugins/qt4projectmanager/qt-maemo/maemodeploystepwidget.cpp
@@ -1,12 +1,19 @@
 #include "maemodeploystepwidget.h"
 #include "ui_maemodeploystepwidget.h"
 
+#include "maemodeploystep.h"
+#include "maemorunconfiguration.h"
+
+#include <projectexplorer/buildconfiguration.h>
+#include <projectexplorer/target.h>
+
 namespace Qt4ProjectManager {
 namespace Internal {
 
-MaemoDeployStepWidget::MaemoDeployStepWidget() :
+MaemoDeployStepWidget::MaemoDeployStepWidget(MaemoDeployStep *step) :
     ProjectExplorer::BuildStepConfigWidget(),
-    ui(new Ui::MaemoDeployStepWidget)
+    ui(new Ui::MaemoDeployStepWidget),
+    m_step(step)
 {
     ui->setupUi(this);
 }
@@ -18,11 +25,23 @@ MaemoDeployStepWidget::~MaemoDeployStepWidget()
 
 void MaemoDeployStepWidget::init()
 {
+    const ProjectExplorer::RunConfiguration * const rc =
+        m_step->buildConfiguration()->target()->activeRunConfiguration();
+    if (rc) {
+        connect(qobject_cast<const MaemoRunConfiguration *>(rc),
+            SIGNAL(deviceConfigurationChanged(ProjectExplorer::Target *)),
+            this, SLOT(handleDeviceUpdate()));
+    }
+}
+
+void MaemoDeployStepWidget::handleDeviceUpdate()
+{
+    emit updateSummary();
 }
 
 QString MaemoDeployStepWidget::summaryText() const
 {
-    return tr("<b>Deploy to device</b> ");
+    return tr("<b>Deploy to device</b>: ") + m_step->deviceConfig().name;
 }
 
 QString MaemoDeployStepWidget::displayName() const
diff --git a/src/plugins/qt4projectmanager/qt-maemo/maemodeploystepwidget.h b/src/plugins/qt4projectmanager/qt-maemo/maemodeploystepwidget.h
index 85342b630b8..4ff469b621f 100644
--- a/src/plugins/qt4projectmanager/qt-maemo/maemodeploystepwidget.h
+++ b/src/plugins/qt4projectmanager/qt-maemo/maemodeploystepwidget.h
@@ -11,13 +11,14 @@ QT_END_NAMESPACE
 
 namespace Qt4ProjectManager {
 namespace Internal {
+class MaemoDeployStep;
 
 class MaemoDeployStepWidget : public ProjectExplorer::BuildStepConfigWidget
 {
     Q_OBJECT
 
 public:
-    MaemoDeployStepWidget();
+    MaemoDeployStepWidget(MaemoDeployStep *step);
     ~MaemoDeployStepWidget();
 
 private:
@@ -25,7 +26,10 @@ private:
     virtual QString summaryText() const;
     virtual QString displayName() const;
 
+    Q_SLOT void handleDeviceUpdate();
+
     Ui::MaemoDeployStepWidget *ui;
+    MaemoDeployStep * const m_step;
 };
 
 } // namespace Internal
diff --git a/src/plugins/qt4projectmanager/qt-maemo/maemodeviceconfigurations.cpp b/src/plugins/qt4projectmanager/qt-maemo/maemodeviceconfigurations.cpp
index d1d7d6645ef..18496019f6d 100644
--- a/src/plugins/qt4projectmanager/qt-maemo/maemodeviceconfigurations.cpp
+++ b/src/plugins/qt4projectmanager/qt-maemo/maemodeviceconfigurations.cpp
@@ -36,6 +36,7 @@
 
 #include <coreplugin/icore.h>
 
+#include <QtCore/QCoreApplication>
 #include <QtCore/QSettings>
 #include <QtCore/QStringBuilder>
 #include <QtGui/QDesktopServices>
@@ -54,6 +55,11 @@ QString homeDirOnDevice(const QString &uname)
         : QLatin1String("/home/") + uname;
 }
 
+QString remoteSudo()
+{
+    return QLatin1String("/usr/lib/mad-developer/devrootsh");
+}
+
 namespace {
     const QLatin1String SettingsGroup("MaemoDeviceConfigs");
     const QLatin1String IdCounterKey("IdCounter");
@@ -132,7 +138,8 @@ MaemoDeviceConfig::MaemoDeviceConfig(const QSettings &settings,
 }
 
 MaemoDeviceConfig::MaemoDeviceConfig()
-    : internalId(InvalidId)
+    : name(QCoreApplication::translate("MaemoDeviceConfig", "(Invalid device)")),
+      internalId(InvalidId)
 {
 }
 
diff --git a/src/plugins/qt4projectmanager/qt-maemo/maemodeviceconfigurations.h b/src/plugins/qt4projectmanager/qt-maemo/maemodeviceconfigurations.h
index 43ea377006b..6f8ad3a7b04 100644
--- a/src/plugins/qt4projectmanager/qt-maemo/maemodeviceconfigurations.h
+++ b/src/plugins/qt4projectmanager/qt-maemo/maemodeviceconfigurations.h
@@ -49,6 +49,7 @@ namespace Qt4ProjectManager {
 namespace Internal {
 
 QString homeDirOnDevice(const QString &uname);
+QString remoteSudo();
 
 class MaemoDeviceConfig
 {
diff --git a/src/plugins/qt4projectmanager/qt-maemo/maemorunconfiguration.cpp b/src/plugins/qt4projectmanager/qt-maemo/maemorunconfiguration.cpp
index 69bff242583..9a76be87c4b 100644
--- a/src/plugins/qt4projectmanager/qt-maemo/maemorunconfiguration.cpp
+++ b/src/plugins/qt4projectmanager/qt-maemo/maemorunconfiguration.cpp
@@ -29,6 +29,7 @@
 
 #include "maemorunconfiguration.h"
 
+#include "maemodeploystep.h"
 #include "maemopackagecreationstep.h"
 #include "maemorunconfigurationwidget.h"
 #include "maemotoolchain.h"
@@ -67,7 +68,6 @@ MaemoRunConfiguration::MaemoRunConfiguration(Qt4Target *parent,
     , m_gdbPath(source->m_gdbPath)
     , m_devConfig(source->m_devConfig)
     , m_arguments(source->m_arguments)
-    , m_lastDeployed(source->m_lastDeployed)
 {
     init();
 }
@@ -123,32 +123,11 @@ QVariantMap MaemoRunConfiguration::toMap() const
     QVariantMap map(RunConfiguration::toMap());
     map.insert(DeviceIdKey, m_devConfig.internalId);
     map.insert(ArgumentsKey, m_arguments);
-    addDeployTimesToMap(map);
     const QDir dir = QDir(target()->project()->projectDirectory());
     map.insert(ProFileKey, dir.relativeFilePath(m_proFilePath));
-
     return map;
 }
 
-void MaemoRunConfiguration::addDeployTimesToMap(QVariantMap &map) const
-{
-    QVariantList hostList;
-    QVariantList fileList;
-    QVariantList remotePathList;
-    QVariantList timeList;
-    typedef QHash<DeployablePerHost, QDateTime>::ConstIterator DepIt;
-    for (DepIt it = m_lastDeployed.begin(); it != m_lastDeployed.end(); ++it) {
-        hostList << it.key().first.localFilePath;
-        remotePathList << it.key().first.remoteDir;
-        fileList << it.key().second;
-        timeList << it.value();
-    }
-    map.insert(LastDeployedHostsKey, hostList);
-    map.insert(LastDeployedFilesKey, fileList);
-    map.insert(LastDeployedRemotePathsKey, remotePathList);
-    map.insert(LastDeployedTimesKey, timeList);
-}
-
 bool MaemoRunConfiguration::fromMap(const QVariantMap &map)
 {
     if (!RunConfiguration::fromMap(map))
@@ -157,47 +136,12 @@ bool MaemoRunConfiguration::fromMap(const QVariantMap &map)
     setDeviceConfig(MaemoDeviceConfigurations::instance().
         find(map.value(DeviceIdKey, 0).toInt()));
     m_arguments = map.value(ArgumentsKey).toStringList();
-    getDeployTimesFromMap(map);
     const QDir dir = QDir(target()->project()->projectDirectory());
     m_proFilePath = dir.filePath(map.value(ProFileKey).toString());
 
     return true;
 }
 
-void MaemoRunConfiguration::getDeployTimesFromMap(const QVariantMap &map)
-{
-    const QVariantList &hostList = map.value(LastDeployedHostsKey).toList();
-    const QVariantList &fileList = map.value(LastDeployedFilesKey).toList();
-    const QVariantList &remotePathList
-        = map.value(LastDeployedRemotePathsKey).toList();
-    const QVariantList &timeList = map.value(LastDeployedTimesKey).toList();
-    const int elemCount
-        = qMin(qMin(hostList.size(), fileList.size()),
-            qMin(remotePathList.size(), timeList.size()));
-    for (int i = 0; i < elemCount; ++i) {
-        const MaemoDeployable d(fileList.at(i).toString(),
-            remotePathList.at(i).toString());
-        m_lastDeployed.insert(DeployablePerHost(d, hostList.at(i).toString()),
-            timeList.at(i).toDateTime());
-    }
-}
-
-bool MaemoRunConfiguration::currentlyNeedsDeployment(const QString &host,
-    const MaemoDeployable &deployable) const
-{
-    const QDateTime &lastDeployed
-        = m_lastDeployed.value(DeployablePerHost(deployable, host));
-    return !lastDeployed.isValid()
-        || QFileInfo(deployable.localFilePath).lastModified() > lastDeployed;
-}
-
-void MaemoRunConfiguration::setDeployed(const QString &host,
-    const MaemoDeployable &deployable)
-{
-    m_lastDeployed.insert(DeployablePerHost(deployable, host),
-        QDateTime::currentDateTime());
-}
-
 void MaemoRunConfiguration::setDeviceConfig(const MaemoDeviceConfig &devConf)
 {
     m_devConfig = devConf;
@@ -239,6 +183,20 @@ const MaemoPackageCreationStep *MaemoRunConfiguration::packageStep() const
     return 0;
 }
 
+MaemoDeployStep *MaemoRunConfiguration::deployStep() const
+{
+    const QList<ProjectExplorer::BuildStep *> &buildSteps
+        = activeQt4BuildConfiguration()->steps(ProjectExplorer::BuildStep::Deploy);
+    for (int i = buildSteps.count() - 1; i >= 0; --i) {
+        MaemoDeployStep * const step
+            = qobject_cast<MaemoDeployStep*>(buildSteps.at(i));
+        if (step)
+            return step;
+    }
+    Q_ASSERT(!"Impossible: Maemo run configuration without deploy step.");
+    return 0;
+}
+
 QString MaemoRunConfiguration::maddeRoot() const
 {
     if (const MaemoToolChain *tc = toolchain())
diff --git a/src/plugins/qt4projectmanager/qt-maemo/maemorunconfiguration.h b/src/plugins/qt4projectmanager/qt-maemo/maemorunconfiguration.h
index 04b9f1cc3c3..6dd85e9e571 100644
--- a/src/plugins/qt4projectmanager/qt-maemo/maemorunconfiguration.h
+++ b/src/plugins/qt4projectmanager/qt-maemo/maemorunconfiguration.h
@@ -53,6 +53,7 @@ class Qt4BuildConfiguration;
 class Qt4ProFileNode;
 class Qt4Target;
 
+class MaemoDeployStep;
 class MaemoPackageCreationStep;
 class MaemoRunConfigurationFactory;
 
@@ -70,11 +71,8 @@ public:
     Qt4Target *qt4Target() const;
     Qt4BuildConfiguration *activeQt4BuildConfiguration() const;
 
-    bool currentlyNeedsDeployment(const QString &host,
-        const MaemoDeployable &deployable) const;
-    void setDeployed(const QString &host, const MaemoDeployable &deployable);
-
     const MaemoPackageCreationStep *packageStep() const;
+    MaemoDeployStep *deployStep() const;
 
     QString maddeRoot() const;
     QString executable() const;
@@ -107,17 +105,12 @@ private slots:
 private:
     void init();
     const MaemoToolChain *toolchain() const;
-    void addDeployTimesToMap(QVariantMap &map) const;
-    void getDeployTimesFromMap(const QVariantMap &map);
 
     QString m_proFilePath;
     mutable QString m_gdbPath;
 
     MaemoDeviceConfig m_devConfig;
     QStringList m_arguments;
-
-    typedef QPair<MaemoDeployable, QString> DeployablePerHost;
-    QHash<DeployablePerHost, QDateTime> m_lastDeployed;
 };
 
     } // namespace Internal
diff --git a/src/plugins/qt4projectmanager/qt-maemo/maemoruncontrol.cpp b/src/plugins/qt4projectmanager/qt-maemo/maemoruncontrol.cpp
index 4d5a3cbc57c..5dee500af98 100644
--- a/src/plugins/qt4projectmanager/qt-maemo/maemoruncontrol.cpp
+++ b/src/plugins/qt4projectmanager/qt-maemo/maemoruncontrol.cpp
@@ -35,11 +35,11 @@
 #include "maemoruncontrol.h"
 
 #include "maemodeployables.h"
+#include "maemodeploystep.h"
 #include "maemopackagecreationstep.h"
 #include "maemorunconfiguration.h"
 
 #include <coreplugin/icore.h>
-#include <coreplugin/progressmanager/progressmanager.h>
 #include <coreplugin/ssh/sftpchannel.h>
 #include <coreplugin/ssh/sshconnection.h>
 #include <coreplugin/ssh/sshremoteprocess.h>
@@ -47,15 +47,12 @@
 #include <debugger/debuggerplugin.h>
 #include <debugger/debuggerrunner.h>
 #include <extensionsystem/pluginmanager.h>
+#include <projectexplorer/projectexplorerconstants.h>
 #include <projectexplorer/toolchain.h>
+#include <qt4projectmanager/qt4buildconfiguration.h>
 #include <utils/qtcassert.h>
-#include <projectexplorer/projectexplorerconstants.h>
 
-#include <QtCore/QCryptographicHash>
-#include <QtCore/QDir>
 #include <QtCore/QFileInfo>
-#include <QtCore/QFuture>
-#include <QtCore/QProcess>
 #include <QtCore/QStringBuilder>
 
 #include <QtGui/QMessageBox>
@@ -137,236 +134,11 @@ void AbstractMaemoRunControl::stop()
     } else if (m_initialCleaner && m_initialCleaner->isRunning()) {
         disconnect(m_initialCleaner.data(), 0, this, 0);
         emit finished();
-    } else if (m_installer && m_installer->isRunning()) {
-        disconnect(m_installer.data(), 0, this, 0);
-        emit finished();
-    } else if (isDeploying()) {
-        m_uploadsInProgress.clear();
-        m_linksInProgress.clear();
-        disconnect(m_uploader.data(), 0, this, 0);
-        m_progress.reportCanceled();
-        m_progress.reportFinished();
-        emit finished();
     } else {
         stopInternal();
     }
 }
 
-void AbstractMaemoRunControl::startDeployment()
-{
-    QTC_ASSERT(m_runConfig, return);
-
-    m_uploader = m_connection->createSftpChannel();
-    connect(m_uploader.data(), SIGNAL(initialized()), this,
-        SLOT(handleSftpChannelInitialized()));
-    connect(m_uploader.data(), SIGNAL(initializationFailed(QString)), this,
-        SLOT(handleSftpChannelInitializationFailed(QString)));
-    connect(m_uploader.data(), SIGNAL(finished(Core::SftpJobId, QString)),
-        this, SLOT(handleSftpJobFinished(Core::SftpJobId, QString)));
-    m_uploader->initialize();
-}
-
-void AbstractMaemoRunControl::handleSftpChannelInitialized()
-{
-    if (m_stopped)
-        return;
-
-    m_uploadsInProgress.clear();
-    m_linksInProgress.clear();
-    m_needsInstall = false;
-    const QList<MaemoDeployable> &deployables = filesToDeploy();
-    foreach (const MaemoDeployable &d, deployables) {
-        const QString fileName = QFileInfo(d.localFilePath).fileName();
-        const QString remoteFilePath = d.remoteDir + '/' + fileName;
-        const QString uploadFilePath = uploadDir() + '/' + fileName + '.'
-            + QCryptographicHash::hash(remoteFilePath.toUtf8(),
-                  QCryptographicHash::Md5).toHex();
-        const SftpJobId job = m_uploader->uploadFile(d.localFilePath,
-            uploadFilePath, SftpOverwriteExisting);
-        if (job == SftpInvalidJob) {
-            handleError(tr("Upload failed: Could not open file '%1'")
-                .arg(d.localFilePath));
-            return;
-        }
-        emit appendMessage(this, tr("Started uploading file '%1'.")
-            .arg(d.localFilePath), false);
-        m_uploadsInProgress.insert(job, DeployInfo(d, uploadFilePath));
-    }
-
-    Core::ICore::instance()->progressManager()
-        ->addTask(m_progress.future(), tr("Deploying"),
-                  QLatin1String("Maemo.Deploy"));
-    if (!m_uploadsInProgress.isEmpty()) {
-        m_progress.setProgressRange(0, m_uploadsInProgress.count());
-        m_progress.setProgressValue(0);
-        m_progress.reportStarted();
-    } else {
-        m_progress.reportFinished();
-        startExecutionIfPossible();
-    }
-}
-
-void AbstractMaemoRunControl::handleSftpChannelInitializationFailed(const QString &error)
-{
-    if (m_stopped)
-        return;
-    handleError(tr("Could not set up SFTP connection: %1").arg(error));
-}
-
-void AbstractMaemoRunControl::handleSftpJobFinished(Core::SftpJobId job,
-    const QString &error)
-{
-    if (m_stopped)
-        return;
-
-    QMap<SftpJobId, DeployInfo>::Iterator it = m_uploadsInProgress.find(job);
-    if (it == m_uploadsInProgress.end()) {
-        qWarning("%s: Job %u not found in map.", Q_FUNC_INFO, job);
-        return;
-    }
-
-    const DeployInfo &deployInfo = it.value();
-    if (!error.isEmpty()) {
-        handleError(tr("Failed to upload file %1: %2")
-            .arg(deployInfo.first.localFilePath, error));
-        return;
-    }
-
-    m_progress.setProgressValue(m_progress.progressValue() + 1);
-    appendMessage(this, tr("Successfully uploaded file '%1'.")
-        .arg(deployInfo.first.localFilePath), false);
-
-    const QString remoteFilePath = deployInfo.first.remoteDir + '/'
-        + QFileInfo(deployInfo.first.localFilePath).fileName();
-    QByteArray linkCommand = remoteSudo().toUtf8() + " ln -sf "
-        + deployInfo.second.toUtf8() + ' ' + remoteFilePath.toUtf8();
-    SshRemoteProcess::Ptr linkProcess
-        = m_connection->createRemoteProcess(linkCommand);
-    connect(linkProcess.data(), SIGNAL(closed(int)), this,
-        SLOT(handleLinkProcessFinished(int)));
-    m_linksInProgress.insert(linkProcess, deployInfo.first);
-    linkProcess->start();
-    m_uploadsInProgress.erase(it);
-}
-
-void AbstractMaemoRunControl::handleLinkProcessFinished(int exitStatus)
-{
-    if (m_stopped)
-        return;
-
-    SshRemoteProcess * const proc = static_cast<SshRemoteProcess *>(sender());
-
-    // TODO: List instead of map? We can't use it for lookup anyway.
-    QMap<SshRemoteProcess::Ptr, MaemoDeployable>::Iterator it;
-    for (it = m_linksInProgress.begin(); it != m_linksInProgress.end(); ++it) {
-        if (it.key().data() == proc)
-            break;
-    }
-    if (it == m_linksInProgress.end()) {
-        qWarning("%s: Remote process %p not found in process list.",
-            Q_FUNC_INFO, proc);
-        return;
-    }
-
-    const MaemoDeployable &deployable = it.value();
-    if (exitStatus != SshRemoteProcess::ExitedNormally
-        || proc->exitCode() != 0) {
-        handleError(tr("Deployment failed for file '%1': "
-            "Could not create link '%2' on remote system.")
-            .arg(deployable.localFilePath, deployable.remoteDir + '/'
-                + QFileInfo(deployable.localFilePath).fileName()));
-        return;
-    }
-
-    m_runConfig->setDeployed(m_devConfig.server.host, it.value());
-    m_linksInProgress.erase(it);
-    if (m_linksInProgress.isEmpty() && m_uploadsInProgress.isEmpty()) {
-        if (m_needsInstall) {
-            emit appendMessage(this, tr("Installing package ..."), false);
-            const QByteArray cmd = remoteSudo().toUtf8() + " dpkg -i "
-                + packageFileName().toUtf8();
-            m_installer = m_connection->createRemoteProcess(cmd);
-            connect(m_installer.data(), SIGNAL(closed(int)), this,
-                SLOT(handleInstallationFinished(int)));
-            connect(m_installer.data(), SIGNAL(outputAvailable(QByteArray)),
-                this, SLOT(handleRemoteOutput(QByteArray)));
-            connect(m_installer.data(),
-                SIGNAL(errorOutputAvailable(QByteArray)), this,
-                SLOT(handleRemoteErrorOutput(QByteArray)));
-            m_installer->start();
-        } else {
-            handleDeploymentFinished();
-        }
-    }
-}
-
-void AbstractMaemoRunControl::handleInstallationFinished(int exitStatus)
-{
-    if (m_stopped)
-        return;
-
-    if (exitStatus != SshRemoteProcess::ExitedNormally
-        || m_installer->exitCode() != 0) {
-        handleError(tr("Installing package failed."));
-    } else {
-        emit appendMessage(this, tr("Package installation finished."), false);
-        handleDeploymentFinished();
-    }
-}
-
-void AbstractMaemoRunControl::handleDeploymentFinished()
-{
-    emit appendMessage(this, tr("Deployment finished."), false);
-    m_progress.reportFinished();
-    startExecutionIfPossible();
-}
-
-QList<MaemoDeployable> AbstractMaemoRunControl::filesToDeploy()
-{
-    QList<MaemoDeployable> deployableList;
-    if (m_runConfig->packageStep()->isPackagingEnabled()) {
-        const MaemoDeployable d(packageFilePath(), uploadDir());
-        m_needsInstall = addDeployableIfNeeded(deployableList, d);
-    } else {
-        const MaemoDeployables * const deployables
-            = m_runConfig->packageStep()->deployables();
-        const int deployableCount = deployables->deployableCount();
-        for (int i = 0; i < deployableCount; ++i) {
-            const MaemoDeployable &d = deployables->deployableAt(i);
-            addDeployableIfNeeded(deployableList, d);
-        }
-        m_needsInstall = false;
-    }
-    return deployableList;
-}
-
-bool AbstractMaemoRunControl::addDeployableIfNeeded(QList<MaemoDeployable> &deployables,
-    const MaemoDeployable &deployable)
-{
-    if (m_runConfig->currentlyNeedsDeployment(m_devConfig.server.host,
-        deployable)) {
-        deployables << deployable;
-        return true;
-    } else {
-        return false;
-    }
-}
-
-bool AbstractMaemoRunControl::isDeploying() const
-{
-    return !m_uploadsInProgress.isEmpty() || !m_linksInProgress.isEmpty();
-}
-
-QString AbstractMaemoRunControl::packageFileName() const
-{
-    return QFileInfo(packageFilePath()).fileName();
-}
-
-QString AbstractMaemoRunControl::packageFilePath() const
-{
-    return m_runConfig->packageStep()->packageFilePath();
-}
-
 QString AbstractMaemoRunControl::executableFilePathOnTarget() const
 {
     const MaemoDeployables * const deployables
@@ -374,11 +146,6 @@ QString AbstractMaemoRunControl::executableFilePathOnTarget() const
     return deployables->remoteExecutableFilePath(m_runConfig->executable());
 }
 
-bool AbstractMaemoRunControl::isCleaning() const
-{
-    return m_initialCleaner && m_initialCleaner->isRunning();
-}
-
 void AbstractMaemoRunControl::startExecutionIfPossible()
 {
     if (executableFilePathOnTarget().isEmpty()) {
@@ -436,7 +203,7 @@ void AbstractMaemoRunControl::handleRemoteErrorOutput(const QByteArray &output)
 
 bool AbstractMaemoRunControl::isRunning() const
 {
-    return isDeploying() || (m_runner && m_runner->isRunning());
+    return m_runner && m_runner->isRunning();
 }
 
 void AbstractMaemoRunControl::stopRunning(bool forDebugging)
@@ -488,7 +255,7 @@ void AbstractMaemoRunControl::handleInitialCleanupFinished(int exitStatus)
             .arg(m_initialCleaner->errorString()));
     } else {
         emit appendMessage(this, tr("Initial cleanup done."), false);
-        startDeployment();
+        startExecutionIfPossible();
     }
 }
 
@@ -507,11 +274,6 @@ const QString AbstractMaemoRunControl::uploadDir() const
     return homeDirOnDevice(m_devConfig.server.uname);
 }
 
-QString AbstractMaemoRunControl::remoteSudo() const
-{
-    return QLatin1String("/usr/lib/mad-developer/devrootsh");
-}
-
 const QString AbstractMaemoRunControl::targetCmdLinePrefix() const
 {
     return QString::fromLocal8Bit("%1 chmod a+x %2 && source /etc/profile && DISPLAY=:0.0 ")
@@ -565,6 +327,7 @@ MaemoDebugRunControl::MaemoDebugRunControl(RunConfiguration *runConfiguration)
     : AbstractMaemoRunControl(runConfiguration, ProjectExplorer::Constants::DEBUGMODE)
     , m_debuggerRunControl(0)
     , m_startParams(new DebuggerStartParameters)
+    , m_uploadJob(SftpInvalidJob)
 {
 #ifdef USE_GDBSERVER
     m_startParams->startMode = AttachToRemote;
@@ -607,27 +370,102 @@ QString MaemoDebugRunControl::remoteCall() const
 }
 
 void MaemoDebugRunControl::startExecution()
+{
+    const QString &dumperLib = m_runConfig->dumperLib();
+    if (!dumperLib.isEmpty()
+        && m_runConfig->deployStep()->currentlyNeedsDeployment(m_devConfig.server.host,
+            MaemoDeployable(dumperLib, uploadDir()))) {
+        m_uploader = m_connection->createSftpChannel();
+        connect(m_uploader.data(), SIGNAL(initialized()), this,
+            SLOT(handleSftpChannelInitialized()));
+        connect(m_uploader.data(), SIGNAL(initializationFailed(QString)), this,
+            SLOT(handleSftpChannelInitializationFailed(QString)));
+        connect(m_uploader.data(), SIGNAL(finished(Core::SftpJobId, QString)),
+            this, SLOT(handleSftpJobFinished(Core::SftpJobId, QString)));
+        m_uploader->initialize();
+    } else {
+        startDebugging();
+    }
+}
+
+void MaemoDebugRunControl::handleSftpChannelInitialized()
+{
+    if (m_stopped)
+        return;
+
+    const QString dumperLib = m_runConfig->dumperLib();
+    const QString fileName = QFileInfo(dumperLib).fileName();
+    const QString remoteFilePath = uploadDir() + '/' + fileName;
+    m_uploadJob = m_uploader->uploadFile(dumperLib, remoteFilePath,
+        SftpOverwriteExisting);
+    if (m_uploadJob == SftpInvalidJob) {
+        handleError(tr("Upload failed: Could not open file '%1'")
+            .arg(dumperLib));
+    } else {
+        emit appendMessage(this,
+            tr("Started uploading debugging helpers ('%1').").arg(dumperLib),
+            false);
+    }
+}
+
+void MaemoDebugRunControl::handleSftpChannelInitializationFailed(const QString &error)
+{
+    handleError(error);
+}
+
+void MaemoDebugRunControl::handleSftpJobFinished(Core::SftpJobId job,
+    const QString &error)
+{
+    if (m_stopped)
+        return;
+
+    if (job != m_uploadJob) {
+        qWarning("Warning: Unknown debugging helpers upload job %d finished.", job);
+        return;
+    }
+
+    if (!error.isEmpty()) {
+        handleError(tr("Error: Could not upload debugging helpers."));
+    } else {
+        m_runConfig->deployStep()->setDeployed(m_devConfig.server.host,
+            MaemoDeployable(m_runConfig->dumperLib(), uploadDir()));
+        emit appendMessage(this,
+            tr("Finished uploading debugging helpers."), false);
+        startDebugging();
+    }
+}
+
+void MaemoDebugRunControl::startDebugging()
 {
 #ifdef USE_GDBSERVER
     AbstractMaemoRunControl::startExecution();
 #else
-    startDebugging();
+    DebuggerPlugin::startDebugger(m_debuggerRunControl);
 #endif
 }
 
-void MaemoDebugRunControl::startDebugging()
+bool MaemoDebugRunControl::isDeploying() const
 {
-    DebuggerPlugin::startDebugger(m_debuggerRunControl);
+    return m_uploader && m_uploadJob != SftpInvalidJob;
 }
 
 void MaemoDebugRunControl::stopInternal()
 {
-    m_debuggerRunControl->engine()->quitDebugger();
+    if (isDeploying()) {
+        disconnect(m_uploader.data(), 0, this, 0);
+        m_uploader->closeChannel();
+        m_uploadJob = SftpInvalidJob;
+        emit finished();
+    } else if (m_debuggerRunControl && m_debuggerRunControl->engine()) {
+        m_debuggerRunControl->engine()->quitDebugger();
+    } else {
+        emit finished();
+    }
 }
 
 bool MaemoDebugRunControl::isRunning() const
 {
-    return AbstractMaemoRunControl::isRunning()
+    return isDeploying() || AbstractMaemoRunControl::isRunning()
         || m_debuggerRunControl->state() != DebuggerNotReady;
 }
 
@@ -642,7 +480,7 @@ void MaemoDebugRunControl::debuggingFinished()
 
 void MaemoDebugRunControl::handleRemoteProcessStarted()
 {
-    startDebugging();
+    DebuggerPlugin::startDebugger(m_debuggerRunControl);
 }
 
 void MaemoDebugRunControl::debuggerOutput(const QString &output)
@@ -659,14 +497,5 @@ QString MaemoDebugRunControl::gdbServerPort() const
         // but we will make sure we use the right port from the information file.
 }
 
-QList<MaemoDeployable> MaemoDebugRunControl::filesToDeploy()
-{
-    QList<MaemoDeployable> deployables
-        = AbstractMaemoRunControl::filesToDeploy();
-    const MaemoDeployable d(m_runConfig->dumperLib(), uploadDir());
-    addDeployableIfNeeded(deployables, d);
-    return deployables;
-}
-
 } // namespace Internal
 } // namespace Qt4ProjectManager
diff --git a/src/plugins/qt4projectmanager/qt-maemo/maemoruncontrol.h b/src/plugins/qt4projectmanager/qt-maemo/maemoruncontrol.h
index a5ede3fde26..a9af1e60719 100644
--- a/src/plugins/qt4projectmanager/qt-maemo/maemoruncontrol.h
+++ b/src/plugins/qt4projectmanager/qt-maemo/maemoruncontrol.h
@@ -36,14 +36,10 @@
 #define MAEMORUNCONTROL_H
 
 #include "maemodeviceconfigurations.h"
-#include "maemodeployable.h"
 
 #include <coreplugin/ssh/sftpdefs.h>
 #include <projectexplorer/runconfiguration.h>
 
-#include <QtCore/QFutureInterface>
-#include <QtCore/QMap>
-#include <QtCore/QScopedPointer>
 #include <QtCore/QString>
 
 QT_BEGIN_NAMESPACE
@@ -78,7 +74,6 @@ protected:
     virtual void start();
     virtual void stop();
 
-    void startDeployment();
     void stopRunning(bool forDebugging);
     virtual void startExecution();
     void handleError(const QString &errString);
@@ -87,22 +82,12 @@ protected:
     const QString targetCmdLinePrefix() const;
     QString targetCmdLineSuffix() const;
     const QString uploadDir() const;
-    QString packageFileName() const;
-    QString packageFilePath() const;
     QString executableFilePathOnTarget() const;
-    virtual QList<MaemoDeployable> filesToDeploy();
-    bool addDeployableIfNeeded(QList<MaemoDeployable> &deployables,
-        const MaemoDeployable &deployable);
 
 private slots:
     void handleConnected();
     void handleConnectionFailure();
     void handleInitialCleanupFinished(int exitStatus);
-    void handleSftpChannelInitialized();
-    void handleSftpChannelInitializationFailed(const QString &error);
-    void handleSftpJobFinished(Core::SftpJobId job, const QString &error);
-    void handleLinkProcessFinished(int exitStatus);
-    void handleInstallationFinished(int exitStatus);
     virtual void handleRemoteProcessStarted() {}
     void handleRemoteProcessFinished(int exitStatus);
     void handleRemoteOutput(const QByteArray &output);
@@ -111,6 +96,8 @@ private slots:
 protected:
     MaemoRunConfiguration *m_runConfig; // TODO this pointer can be invalid
     const MaemoDeviceConfig m_devConfig;
+    QSharedPointer<Core::SshConnection> m_connection;
+    bool m_stopped;
 
 private:
     virtual void stopInternal()=0;
@@ -120,25 +107,10 @@ private:
     void cancelActions();
     template<class SshChannel> void closeSshChannel(SshChannel &channel);
     void startExecutionIfPossible();
-    bool isCleaning() const;
-    bool isDeploying() const;
-    QString remoteSudo() const;
-    QString uploadFilePath(const MaemoDeployable &deployable) const;
-    void handleDeploymentFinished();
 
-    QFutureInterface<void> m_progress;
-    QSharedPointer<Core::SshConnection> m_connection;
-    QSharedPointer<Core::SftpChannel> m_uploader;
-    QSharedPointer<Core::SshRemoteProcess> m_installer;
     QSharedPointer<Core::SshRemoteProcess> m_runner;
     QSharedPointer<Core::SshRemoteProcess> m_stopper;
     QSharedPointer<Core::SshRemoteProcess> m_initialCleaner;
-
-    typedef QPair<MaemoDeployable, QString> DeployInfo;
-    QMap<Core::SftpJobId, DeployInfo> m_uploadsInProgress;
-    QMap<QSharedPointer<Core::SshRemoteProcess>, MaemoDeployable> m_linksInProgress;
-    bool m_needsInstall;
-    bool m_stopped;
 };
 
 class MaemoRunControl : public AbstractMaemoRunControl
@@ -165,18 +137,23 @@ private slots:
     virtual void handleRemoteProcessStarted();
     void debuggerOutput(const QString &output);
     void debuggingFinished();
+    void handleSftpChannelInitialized();
+    void handleSftpChannelInitializationFailed(const QString &error);
+    void handleSftpJobFinished(Core::SftpJobId job, const QString &error);
 
 private:
     virtual void stopInternal();
     virtual void startExecution();
     virtual QString remoteCall() const;
-    virtual QList<MaemoDeployable> filesToDeploy();
 
     QString gdbServerPort() const;
     void startDebugging();
+    bool isDeploying() const;
 
     Debugger::DebuggerRunControl *m_debuggerRunControl;
     QSharedPointer<Debugger::DebuggerStartParameters> m_startParams;
+    QSharedPointer<Core::SftpChannel> m_uploader;
+    Core::SftpJobId m_uploadJob;
 };
 
 } // namespace Internal
diff --git a/src/plugins/qt4projectmanager/qt4target.cpp b/src/plugins/qt4projectmanager/qt4target.cpp
index e63228fbc87..4f262dbea03 100644
--- a/src/plugins/qt4projectmanager/qt4target.cpp
+++ b/src/plugins/qt4projectmanager/qt4target.cpp
@@ -288,8 +288,8 @@ Qt4BuildConfiguration *Qt4Target::addQt4BuildConfiguration(QString displayName,
     } else if (id() == Constants::MAEMO_DEVICE_TARGET_ID) {
         bc->insertStep(ProjectExplorer::BuildStep::Deploy, 2,
             new MaemoPackageCreationStep(bc));
-//        bc->insertStep(ProjectExplorer::BuildStep::Deploy, 2,
-//            new MaemoDeployStep(bc));
+        bc->insertStep(ProjectExplorer::BuildStep::Deploy, 3,
+            new MaemoDeployStep(bc));
     }
 
     MakeStep* cleanStep = new MakeStep(bc);
-- 
GitLab