maemodeploystep.cpp 33.6 KB
Newer Older
1
2
3
4
/**************************************************************************
**
** This file is part of Qt Creator
**
con's avatar
con committed
5
** Copyright (c) 2011 Nokia Corporation and/or its subsidiary(-ies).
6
7
8
**
** Contact: Nokia Corporation (qt-info@nokia.com)
**
con's avatar
con committed
9
** No Commercial Usage
10
**
con's avatar
con committed
11
12
13
14
** This file contains pre-release code and may not be distributed.
** You may use this file in accordance with the terms and conditions
** contained in the Technology Preview License Agreement accompanying
** this package.
15
16
17
18
19
20
21
22
23
24
**
** 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.
**
con's avatar
con committed
25
26
27
28
29
30
** In addition, as a special exception, Nokia gives you certain additional
** rights.  These rights are described in the Nokia Qt LGPL Exception
** version 1.1, included in the file LGPL_EXCEPTION.txt in this package.
**
** If you have questions regarding the use of this file, please contact
** Nokia at qt-info@nokia.com.
31
32
33
**
**************************************************************************/

34
35
#include "maemodeploystep.h"

36
#include "maemoconstants.h"
37
#include "maemodeploystepwidget.h"
38
#include "maemoglobal.h"
39
#include "maemopackagecreationstep.h"
40
#include "maemopertargetdeviceconfigurationlistmodel.h"
41
#include "maemoqemumanager.h"
ck's avatar
ck committed
42
#include "maemoremotemounter.h"
43
#include "maemorunconfiguration.h"
ck's avatar
ck committed
44
#include "maemotoolchain.h"
45
#include "maemousedportsgatherer.h"
46
#include "qt4maemotarget.h"
47

48
49
50
51
52
53
54
55
#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>

ck's avatar
ck committed
56
#include <qt4projectmanager/qt4buildconfiguration.h>
57
#include <qt4projectmanager/qt4projectmanagerconstants.h>
58
#include <qt4projectmanager/qt4target.h>
ck's avatar
ck committed
59

60
#include <QtCore/QCoreApplication>
ck's avatar
ck committed
61
#include <QtCore/QDir>
62
63
64
65
#include <QtCore/QEventLoop>
#include <QtCore/QFileInfo>
#include <QtCore/QTimer>

66
67
#define ASSERT_STATE(state) ASSERT_STATE_GENERIC(State, state, m_state)

68
using namespace Core;
69
70
71
72
using namespace ProjectExplorer;

namespace Qt4ProjectManager {
namespace Internal {
73
namespace { const int DefaultMountPort = 1050; }
74
75
76

const QLatin1String MaemoDeployStep::Id("Qt4ProjectManager.MaemoDeployStep");

Tobias Hunger's avatar
Tobias Hunger committed
77
MaemoDeployStep::MaemoDeployStep(ProjectExplorer::BuildStepList *parent)
78
    : BuildStep(parent, Id)
79
{
80
    ctor();
81
82
}

Tobias Hunger's avatar
Tobias Hunger committed
83
MaemoDeployStep::MaemoDeployStep(ProjectExplorer::BuildStepList *parent,
84
    MaemoDeployStep *other)
85
    : BuildStep(parent, other), m_lastDeployed(other->m_lastDeployed)
86
87
88
89
{
    ctor();
}

90
MaemoDeployStep::~MaemoDeployStep() { }
91
92

void MaemoDeployStep::ctor()
93
{
94
    //: MaemoDeployStep default display name
95
96
97
98
    if (target()->id() == QLatin1String(Constants::MAEMO5_DEVICE_TARGET_ID))
        setDefaultDisplayName(tr("Deploy to Maemo5 device"));
    else if (target()->id() == QLatin1String(Constants::HARMATTAN_DEVICE_TARGET_ID))
        setDefaultDisplayName(tr("Deploy to Harmattan device"));
Christian Kandeler's avatar
Christian Kandeler committed
99
100
    else if (target()->id() == QLatin1String(Constants::MEEGO_DEVICE_TARGET_ID))
        setDefaultDisplayName(tr("Deploy to Meego device"));
101

102
103
104
105
106
107
    // A MaemoDeployables object is only dependent on the active build
    // configuration and therefore can (and should) be shared among all
    // deploy steps.
    const QList<DeployConfiguration *> &deployConfigs
        = target()->deployConfigurations();
    if (deployConfigs.isEmpty()) {
108
        const AbstractQt4MaemoTarget * const qt4Target = qobject_cast<AbstractQt4MaemoTarget *>(target());
109
110
        Q_ASSERT(qt4Target);
        m_deployables = QSharedPointer<MaemoDeployables>(new MaemoDeployables(qt4Target));
111
112
113
114
115
116
    } else {
        const MaemoDeployStep *const other
            = MaemoGlobal::buildStep<MaemoDeployStep>(deployConfigs.first());
        m_deployables = other->deployables();
    }

117
    m_state = Inactive;
118
    m_deviceConfig = maemotarget()->deviceConfigurationsModel()->defaultDeviceConfig();
ck's avatar
ck committed
119
    m_needsInstall = false;
Christian Kandeler's avatar
Christian Kandeler committed
120
121
122
123
124
125
126
    m_sysrootInstaller = new QProcess(this);
    connect(m_sysrootInstaller, SIGNAL(finished(int,QProcess::ExitStatus)),
        this, SLOT(handleSysrootInstallerFinished()));
    connect(m_sysrootInstaller, SIGNAL(readyReadStandardOutput()), this,
        SLOT(handleSysrootInstallerOutput()));
    connect(m_sysrootInstaller, SIGNAL(readyReadStandardError()), this,
        SLOT(handleSysrootInstallerErrorOutput()));
127
    m_mounter = new MaemoRemoteMounter(this);
ck's avatar
ck committed
128
129
130
131
132
133
    connect(m_mounter, SIGNAL(mounted()), this, SLOT(handleMounted()));
    connect(m_mounter, SIGNAL(unmounted()), this, SLOT(handleUnmounted()));
    connect(m_mounter, SIGNAL(error(QString)), this,
        SLOT(handleMountError(QString)));
    connect(m_mounter, SIGNAL(reportProgress(QString)), this,
        SLOT(handleProgressReport(QString)));
134
135
    connect(m_mounter, SIGNAL(debugOutput(QString)), this,
        SLOT(handleMountDebugOutput(QString)));
136
137
138
139
140
    m_portsGatherer = new MaemoUsedPortsGatherer(this);
    connect(m_portsGatherer, SIGNAL(error(QString)), this,
        SLOT(handlePortsGathererError(QString)));
    connect(m_portsGatherer, SIGNAL(portListReady()), this,
        SLOT(handlePortListReady()));
141
    connect(maemotarget()->deviceConfigurationsModel(), SIGNAL(updated()),
142
        SLOT(handleDeviceConfigurationsUpdated()));
143
144
145
146
147
148
149
150
151
}

bool MaemoDeployStep::init()
{
    return true;
}

void MaemoDeployStep::run(QFutureInterface<bool> &fi)
{
152
153
154
155
    // Move to GUI thread for connection sharing with run control.
    QTimer::singleShot(0, this, SLOT(start()));

    MaemoDeployEventHandler eventHandler(this, fi);
156
157
158
159
}

BuildStepConfigWidget *MaemoDeployStep::createConfigWidget()
{
160
161
162
163
164
165
166
    return new MaemoDeployStepWidget(this);
}

QVariantMap MaemoDeployStep::toMap() const
{
    QVariantMap map(BuildStep::toMap());
    addDeployTimesToMap(map);
Christian Kandeler's avatar
Christian Kandeler committed
167
    map.insert(DeployToSysrootKey, m_deployToSysroot);
168
169
    map.insert(DeviceIdKey,
        MaemoDeviceConfigurations::instance()->internalId(m_deviceConfig));
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
    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);
197
    setDeviceConfig(map.value(DeviceIdKey, MaemoDeviceConfig::InvalidId).toULongLong());
Christian Kandeler's avatar
Christian Kandeler committed
198
    m_deployToSysroot = map.value(DeployToSysrootKey, true).toBool();
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
    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
{
ck's avatar
ck committed
222
    const MaemoPackageCreationStep * const step
223
        = MaemoGlobal::buildStep<MaemoPackageCreationStep>(target()->activeDeployConfiguration());
224
225
    Q_ASSERT_X(step, Q_FUNC_INFO,
        "Impossible: Maemo build configuration without packaging step.");
ck's avatar
ck committed
226
    return step;
227
228
229
230
231
}

void MaemoDeployStep::raiseError(const QString &errorString)
{
    emit addTask(Task(Task::Error, errorString, QString(), -1,
232
        ProjectExplorer::Constants::TASK_CATEGORY_BUILDSYSTEM));
233
    m_hasError = true;
234
235
236
    emit error();
}

Christian Kandeler's avatar
Christian Kandeler committed
237
void MaemoDeployStep::writeOutput(const QString &text, OutputFormat format)
238
239
240
241
242
243
{
    emit addOutput(text, format);
}

void MaemoDeployStep::stop()
{
244
    if (m_state == StopRequested || m_state == Inactive)
245
246
        return;

247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
    const State oldState = m_state;
    setState(StopRequested);
    switch (oldState) {
    case InstallingToSysroot:
        if (m_needsInstall)
            m_sysrootInstaller->terminate();
        break;
    case Connecting:
        m_connection->disconnectFromHost();
        setState(Inactive);
        break;
    case InstallingToDevice:
    case CopyingFile: {
        const QByteArray programToKill = oldState == CopyingFile
            ? " cp " : "dpkg";
        const QByteArray killCommand
            = MaemoGlobal::remoteSudo().toUtf8() + " pkill -f ";
        const QByteArray cmdLine = killCommand + programToKill + "; sleep 1; "
            + killCommand + "-9 " + programToKill;
266
267
268
        SshRemoteProcess::Ptr killProc
            = m_connection->createRemoteProcess(cmdLine);
        killProc->start();
269
270
271
272
273
274
275
276
277
278
279
280
281
282
        break;
    }
    case Uploading:
        m_uploader->closeChannel();
        break;
    case UnmountingOldDirs:
    case UnmountingCurrentDirs:
    case UnmountingCurrentMounts:
    case GatheringPorts:
    case Mounting:
    case InitializingSftp:
        break; // Nothing to do here.
    default:
        Q_ASSERT_X(false, Q_FUNC_INFO, "Missing switch case.");
283
284
285
286
287
    }
}

QString MaemoDeployStep::uploadDir() const
{
288
    return MaemoGlobal::homeDirOnDevice(m_connection->connectionParameters().uname);
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
}

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());
}

307
void MaemoDeployStep::handleDeviceConfigurationsUpdated()
308
{
309
310
311
312
313
    setDeviceConfig(MaemoDeviceConfigurations::instance()->internalId(m_deviceConfig));
}

void MaemoDeployStep::setDeviceConfig(MaemoDeviceConfig::Id internalId)
{
314
    m_deviceConfig = maemotarget()->deviceConfigurationsModel()->find(internalId);
315
316
317
318
319
    emit deviceConfigChanged();
}

void MaemoDeployStep::setDeviceConfig(int i)
{
320
    m_deviceConfig = maemotarget()->deviceConfigurationsModel()->deviceAt(i);
321
    emit deviceConfigChanged();
322
323
}

324
325
void MaemoDeployStep::start()
{
326
327
328
    if (m_state != Inactive) {
        raiseError(tr("Cannot deploy: Still cleaning up from last time."));
        emit done();
329
        return;
ck's avatar
ck committed
330
    }
331

332
333
    m_cachedDeviceConfig = m_deviceConfig;
    if (!m_cachedDeviceConfig) {
334
        raiseError(tr("Deployment failed: No valid device set."));
335
        emit done();
336
337
        return;
    }
338

339
    Q_ASSERT(!m_currentDeviceDeployAction);
ck's avatar
ck committed
340
341
    Q_ASSERT(!m_needsInstall);
    Q_ASSERT(m_filesToCopy.isEmpty());
342
    m_installerStderr.clear();
343
    m_hasError = false;
ck's avatar
ck committed
344
    const MaemoPackageCreationStep * const pStep = packagingStep();
345
    const QString hostName = m_cachedDeviceConfig->sshParameters().host;
ck's avatar
ck committed
346
347
348
349
350
351
352
353
354
355
356
357
    if (pStep->isPackagingEnabled()) {
        const MaemoDeployable d(pStep->packageFilePath(), QString());
        if (currentlyNeedsDeployment(hostName, d))
            m_needsInstall = true;
    } else {
        const int deployableCount = m_deployables->deployableCount();
        for (int i = 0; i < deployableCount; ++i) {
            const MaemoDeployable &d = m_deployables->deployableAt(i);
            if (currentlyNeedsDeployment(hostName, d))
                m_filesToCopy << d;
        }
    }
358

ck's avatar
ck committed
359
    if (m_needsInstall || !m_filesToCopy.isEmpty()) {
360
361
362
363
364
365
366
367
368
369
370
371
        if (m_cachedDeviceConfig->type() == MaemoDeviceConfig::Simulator
                && !MaemoQemuManager::instance().qemuIsRunning()) {
            MaemoQemuManager::instance().startRuntime();
            raiseError(tr("Deployment failed: Qemu was not running. "
                "It has now been started up for you, but it will take "
                "a bit of time until it is ready."));
            m_needsInstall = false;
            m_filesToCopy.clear();
            emit done();
            return;
        }

372
373
374
375
        if (m_deployToSysroot)
            installToSysroot();
        else
            connectToDevice();
ck's avatar
ck committed
376
377
378
379
    } else {
        writeOutput(tr("All files up to date, no installation necessary."));
        emit done();
    }
380
381
382
383
}

void MaemoDeployStep::handleConnectionFailure()
{
384
385
386
387
    if (m_state == Inactive)
        return;

    const QString errorMsg = m_state == Connecting
388
        ? MaemoGlobal::failedToConnectToServerMessage(m_connection, m_cachedDeviceConfig)
389
390
391
        : tr("Connection error: %1").arg(m_connection->errorString());
    raiseError(errorMsg);
    setState(Inactive);
392
393
394
395
}

void MaemoDeployStep::handleSftpChannelInitialized()
{
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
    ASSERT_STATE(QList<State>() << InitializingSftp << StopRequested);

    switch (m_state) {
    case InitializingSftp: {
        const QString filePath = packagingStep()->packageFilePath();
        const QString filePathNative = QDir::toNativeSeparators(filePath);
        const QString fileName = QFileInfo(filePath).fileName();
        const QString remoteFilePath = uploadDir() + QLatin1Char('/') + fileName;
        const SftpJobId job = m_uploader->uploadFile(filePath,
            remoteFilePath, SftpOverwriteExisting);
        if (job == SftpInvalidJob) {
            raiseError(tr("Upload failed: Could not open file '%1'")
                .arg(filePathNative));
            setState(Inactive);
        } else {
            setState(Uploading);
            writeOutput(tr("Started uploading file '%1'.").arg(filePathNative));
        }
        break;
415
    }
416
417
418
419
420
    case StopRequested:
        setState(Inactive);
        break;
    default:
        break;
421
422
423
424
425
    }
}

void MaemoDeployStep::handleSftpChannelInitializationFailed(const QString &error)
{
426
427
428
429
430
431
432
433
434
435
    ASSERT_STATE(QList<State>() << InitializingSftp << StopRequested);

    switch (m_state) {
    case InitializingSftp:
    case StopRequested:
        raiseError(tr("Could not set up SFTP connection: %1").arg(error));
        setState(Inactive);
        break;
    default:
        break;
436
    }
437
438
}

439
void MaemoDeployStep::handleSftpJobFinished(Core::SftpJobId,
440
441
    const QString &error)
{
442
    ASSERT_STATE(QList<State>() << Uploading << StopRequested);
443

444
445
    const QString filePathNative
        = QDir::toNativeSeparators(packagingStep()->packageFilePath());
446
447
    if (!error.isEmpty()) {
        raiseError(tr("Failed to upload file %1: %2")
448
            .arg(filePathNative, error));
449
450
451
452
453
454
455
        if (m_state == Uploading)
            setState(Inactive);
    } else if (m_state == Uploading) {
        writeOutput(tr("Successfully uploaded file '%1'.")
            .arg(filePathNative));
        const QString remoteFilePath
            = uploadDir() + QLatin1Char('/') + QFileInfo(filePathNative).fileName();
Christian Kandeler's avatar
Christian Kandeler committed
456
        runPackageInstaller(remoteFilePath);
457
    }
458
}
459

460
461
462
463
void MaemoDeployStep::handleSftpChannelClosed()
{
    ASSERT_STATE(StopRequested);
    setState(Inactive);
464
}
ck's avatar
ck committed
465
466
467

void MaemoDeployStep::handleMounted()
{
468
    ASSERT_STATE(QList<State>() << Mounting << StopRequested << Inactive);
ck's avatar
ck committed
469

470
471
472
473
474
    switch (m_state) {
    case Mounting:
        if (m_needsInstall) {
            const QString remoteFilePath = deployMountPoint() + QLatin1Char('/')
                + QFileInfo(packagingStep()->packageFilePath()).fileName();
Christian Kandeler's avatar
Christian Kandeler committed
475
            runPackageInstaller(remoteFilePath);
476
477
478
479
480
481
482
483
484
485
486
        } else {
            setState(CopyingFile);
            copyNextFileToDevice();
        }
        break;
    case StopRequested:
        unmount();
        break;
    case Inactive:
    default:
        break;
ck's avatar
ck committed
487
488
489
490
491
    }
}

void MaemoDeployStep::handleUnmounted()
{
492
493
    ASSERT_STATE(QList<State>() << UnmountingOldDirs << UnmountingCurrentDirs
        << UnmountingCurrentMounts << StopRequested << Inactive);
ck's avatar
ck committed
494

495
496
497
498
499
    switch (m_state) {
    case StopRequested:
        m_mounter->resetMountSpecifications();
        setState(Inactive);
        break;
500
501
    case UnmountingOldDirs:
        if (maemotarget()->allowsRemoteMounts())
502
503
504
505
            setupMount();
        else
            prepareSftpConnection();
        break;
506
507
    case UnmountingCurrentDirs:
        setState(GatheringPorts);
508
        m_portsGatherer->start(m_connection, m_cachedDeviceConfig->freePorts());
509
        break;
510
    case UnmountingCurrentMounts:
511
512
513
514
        if (m_hasError)
            writeOutput(tr("Deployment failed."), ErrorMessageOutput);
        else
            writeOutput(tr("Deployment finished."));
515
516
517
518
        setState(Inactive);
        break;
    case Inactive:
    default:
519
        break;
Christian Kandeler's avatar
Christian Kandeler committed
520
    }
521
522
523
524
}

void MaemoDeployStep::handleMountError(const QString &errorMsg)
{
525
526
527
528
529
530
531
    ASSERT_STATE(QList<State>() << UnmountingOldDirs << UnmountingCurrentDirs
        << UnmountingCurrentMounts << Mounting << StopRequested << Inactive);

    switch (m_state) {
    case UnmountingOldDirs:
    case UnmountingCurrentDirs:
    case UnmountingCurrentMounts:
532
    case Mounting:
533
    case StopRequested:
534
        raiseError(errorMsg);
535
536
537
538
539
540
        setState(Inactive);
        break;
    case Inactive:
    default:
        break;
    }
541
}
Christian Kandeler's avatar
Christian Kandeler committed
542

543
544
void MaemoDeployStep::handleMountDebugOutput(const QString &output)
{
545
546
547
548
549
550
551
    ASSERT_STATE(QList<State>() << UnmountingOldDirs << UnmountingCurrentDirs
        << UnmountingCurrentMounts << Mounting << StopRequested << Inactive);

    switch (m_state) {
    case UnmountingOldDirs:
    case UnmountingCurrentDirs:
    case UnmountingCurrentMounts:
552
    case Mounting:
553
    case StopRequested:
554
        writeOutput(output, ErrorOutput);
555
556
557
558
559
        break;
    case Inactive:
    default:
        break;
    }
560
561
}

562
563
void MaemoDeployStep::setupMount()
{
564
565
566
    ASSERT_STATE(UnmountingOldDirs);
    setState(UnmountingCurrentDirs);

Christian Kandeler's avatar
Christian Kandeler committed
567
568
    Q_ASSERT(m_needsInstall || !m_filesToCopy.isEmpty());
    m_mounter->resetMountSpecifications();
569
    m_mounter->setBuildConfiguration(qt4BuildConfiguration());
Christian Kandeler's avatar
Christian Kandeler committed
570
571
572
573
    if (m_needsInstall) {
        const QString localDir
            = QFileInfo(packagingStep()->packageFilePath()).absolutePath();
        const MaemoMountSpecification mountSpec(localDir, deployMountPoint());
574
        m_mounter->addMountSpecification(mountSpec, true);
Christian Kandeler's avatar
Christian Kandeler committed
575
    } else {
ck's avatar
ck committed
576
#ifdef Q_OS_WIN
Christian Kandeler's avatar
Christian Kandeler committed
577
        bool drivesToMount[26];
578
        qFill(drivesToMount, drivesToMount + sizeof drivesToMount / sizeof drivesToMount[0], false);
Christian Kandeler's avatar
Christian Kandeler committed
579
580
581
582
583
584
585
        for (int i = 0; i < m_filesToCopy.count(); ++i) {
            const QString localDir
                = QFileInfo(m_filesToCopy.at(i).localFilePath).canonicalPath();
            const char driveLetter = localDir.at(0).toLower().toLatin1();
            if (driveLetter < 'a' || driveLetter > 'z') {
                qWarning("Weird: drive letter is '%c'.", driveLetter);
                continue;
ck's avatar
ck committed
586
            }
Christian Kandeler's avatar
Christian Kandeler committed
587
588
589
590
591
592
593
594
595

            const int index = driveLetter - 'a';
            if (drivesToMount[index])
                continue;

            const QString mountPoint = deployMountPoint() + QLatin1Char('/')
                + QLatin1Char(driveLetter);
            const MaemoMountSpecification mountSpec(localDir.left(3),
                mountPoint);
596
            m_mounter->addMountSpecification(mountSpec, true);
Christian Kandeler's avatar
Christian Kandeler committed
597
            drivesToMount[index] = true;
ck's avatar
ck committed
598
        }
Christian Kandeler's avatar
Christian Kandeler committed
599
#else
600
601
        m_mounter->addMountSpecification(MaemoMountSpecification(QLatin1String("/"),
            deployMountPoint()), true);
Christian Kandeler's avatar
Christian Kandeler committed
602
#endif
ck's avatar
ck committed
603
    }
604
    unmount();
ck's avatar
ck committed
605
606
}

607
void MaemoDeployStep::prepareSftpConnection()
ck's avatar
ck committed
608
{
609
    setState(InitializingSftp);
610
611
612
613
614
615
616
    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)));
617
618
    connect(m_uploader.data(), SIGNAL(closed()), this,
        SLOT(handleSftpChannelClosed()));
619
    m_uploader->initialize();
ck's avatar
ck committed
620
621
}

Christian Kandeler's avatar
Christian Kandeler committed
622
623
void MaemoDeployStep::installToSysroot()
{
624
625
626
    ASSERT_STATE(Inactive);
    setState(InstallingToSysroot);

627
628
    if (m_needsInstall) {
        writeOutput(tr("Installing package to sysroot ..."));
629
        const QtVersion * const qtVersion = qt4BuildConfiguration()->qtVersion();
630
631
        const QString command = QLatin1String(
            packagingStep()->debBasedMaemoTarget() ? "xdpkg" : "xrpm");
632
633
634
635
        QStringList args = QStringList() << command << QLatin1String("-i");
        if (packagingStep()->debBasedMaemoTarget())
            args << QLatin1String("--no-force-downgrade");
        args << packagingStep()->packageFilePath();
636
        MaemoGlobal::callMadAdmin(*m_sysrootInstaller, args, qtVersion, true);
637
638
639
640
641
642
643
644
        if (!m_sysrootInstaller->waitForStarted()) {
            writeOutput(tr("Installation to sysroot failed, continuing anyway."),
                ErrorMessageOutput);
            connectToDevice();
        }
    } else {
        writeOutput(tr("Copying files to sysroot ..."));
        Q_ASSERT(!m_filesToCopy.isEmpty());
645
        QDir sysRootDir(toolChain()->sysroot());
646
647
        foreach (const MaemoDeployable &d, m_filesToCopy) {
            const QLatin1Char sep('/');
648
            const QString targetFilePath = toolChain()->sysroot() + sep
649
                + d.remoteDir + sep + QFileInfo(d.localFilePath).fileName();
650
651
652
            sysRootDir.mkpath(d.remoteDir.mid(1));
            QFile::remove(targetFilePath);
            if (!QFile::copy(d.localFilePath, targetFilePath)) {
653
654
655
656
657
658
659
                writeOutput(tr("Sysroot installation failed: "
                    "Could not copy '%1' to '%2'. Continuing anyway.")
                    .arg(QDir::toNativeSeparators(d.localFilePath),
                         QDir::toNativeSeparators(targetFilePath)),
                    ErrorMessageOutput);
            }
            QCoreApplication::processEvents();
660
661
            if (m_state == StopRequested) {
                setState(Inactive);
662
663
664
665
                return;
            }
        }
        connectToDevice();
Christian Kandeler's avatar
Christian Kandeler committed
666
667
668
669
670
    }
}

void MaemoDeployStep::handleSysrootInstallerFinished()
{
671
672
673
674
    ASSERT_STATE(QList<State>() << InstallingToSysroot << StopRequested);

    if (m_state == StopRequested) {
        setState(Inactive);
675
676
677
        return;
    }

Christian Kandeler's avatar
Christian Kandeler committed
678
679
680
681
682
    if (m_sysrootInstaller->error() != QProcess::UnknownError
        || m_sysrootInstaller->exitCode() != 0) {
        writeOutput(tr("Installation to sysroot failed, continuing anyway."),
            ErrorMessageOutput);
    }
683
    connectToDevice();
Christian Kandeler's avatar
Christian Kandeler committed
684
685
}

686
687
void MaemoDeployStep::connectToDevice()
{
688
689
690
    ASSERT_STATE(QList<State>() << Inactive << InstallingToSysroot);
    setState(Connecting);

691
692
    const bool canReUse = m_connection
        && m_connection->state() == SshConnection::Connected
693
        && m_connection->connectionParameters() == m_cachedDeviceConfig->sshParameters();
694
695
696
697
    if (!canReUse)
        m_connection = SshConnection::create();
    connect(m_connection.data(), SIGNAL(connected()), this,
        SLOT(handleConnected()));
698
    connect(m_connection.data(), SIGNAL(error(Core::SshError)), this,
699
700
        SLOT(handleConnectionFailure()));
    if (canReUse) {
701
        handleConnected();
702
703
    } else {
        writeOutput(tr("Connecting to device..."));
704
        m_connection->connectToHost(m_cachedDeviceConfig->sshParameters());
705
706
707
708
709
    }
}

void MaemoDeployStep::handleConnected()
{
710
    ASSERT_STATE(QList<State>() << Connecting << StopRequested);
711

712
713
    if (m_state == Connecting)
        unmountOldDirs();
714
715
716
717
}

void MaemoDeployStep::unmountOldDirs()
{
718
    setState(UnmountingOldDirs);
719
    m_mounter->setConnection(m_connection);
720
    unmount();
721
722
}

Christian Kandeler's avatar
Christian Kandeler committed
723
void MaemoDeployStep::runPackageInstaller(const QString &packageFilePath)
Christian Kandeler's avatar
Christian Kandeler committed
724
{
725
726
727
728
    ASSERT_STATE(QList<State>() << Mounting << Uploading);
    const bool removeAfterInstall = m_state == Uploading;
    setState(InstallingToDevice);

Christian Kandeler's avatar
Christian Kandeler committed
729
    writeOutput(tr("Installing package to device..."));
730
731
    const QByteArray installCommand = packagingStep()->debBasedMaemoTarget()
        ? "dpkg -i --no-force-downgrade" : "rpm -Uhv";
Christian Kandeler's avatar
Christian Kandeler committed
732
733
    QByteArray cmd = MaemoGlobal::remoteSudo().toUtf8() + ' '
        + installCommand + ' ' + packageFilePath.toUtf8();
734
    if (removeAfterInstall)
735
        cmd += " && (rm " + packageFilePath.toUtf8() + " || :)";
Christian Kandeler's avatar
Christian Kandeler committed
736
737
738
739
740
741
742
743
744
745
746
    m_deviceInstaller = m_connection->createRemoteProcess(cmd);
    connect(m_deviceInstaller.data(), SIGNAL(closed(int)), this,
        SLOT(handleInstallationFinished(int)));
    connect(m_deviceInstaller.data(), SIGNAL(outputAvailable(QByteArray)),
        this, SLOT(handleDeviceInstallerOutput(QByteArray)));
    connect(m_deviceInstaller.data(),
        SIGNAL(errorOutputAvailable(QByteArray)), this,
        SLOT(handleDeviceInstallerErrorOutput(QByteArray)));
    m_deviceInstaller->start();
}

ck's avatar
ck committed
747
748
void MaemoDeployStep::handleProgressReport(const QString &progressMsg)
{
749
750
751
752
753
754
755
    ASSERT_STATE(QList<State>() << UnmountingOldDirs << UnmountingCurrentDirs
        << UnmountingCurrentMounts << Mounting << StopRequested << Inactive);

    switch (m_state) {
    case UnmountingOldDirs:
    case UnmountingCurrentDirs:
    case UnmountingCurrentMounts:
756
    case Mounting:
757
758
759
760
761
762
763
    case StopRequested:
        writeOutput(progressMsg);
        break;
    case Inactive:
    default:
        break;
    }
ck's avatar
ck committed
764
765
}

766
void MaemoDeployStep::copyNextFileToDevice()
ck's avatar
ck committed
767
{
768
    ASSERT_STATE(CopyingFile);
ck's avatar
ck committed
769
    Q_ASSERT(!m_filesToCopy.isEmpty());
770
    Q_ASSERT(!m_currentDeviceDeployAction);
ck's avatar
ck committed
771
772
773
774
775
776
777
778
779
780
781
782
783
784
785
786
    const MaemoDeployable d = m_filesToCopy.takeFirst();
    QString sourceFilePath = deployMountPoint();
#ifdef Q_OS_WIN
    const QString localFilePath = QDir::fromNativeSeparators(d.localFilePath);
    sourceFilePath += QLatin1Char('/') + localFilePath.at(0).toLower()
        + localFilePath.mid(2);
#else
    sourceFilePath += d.localFilePath;
#endif

    QString command = QString::fromLatin1("%1 cp -r %2 %3")
        .arg(MaemoGlobal::remoteSudo(), sourceFilePath,
            d.remoteDir + QLatin1Char('/'));
    SshRemoteProcess::Ptr copyProcess
        = m_connection->createRemoteProcess(command.toUtf8());
    connect(copyProcess.data(), SIGNAL(errorOutputAvailable(QByteArray)),
Christian Kandeler's avatar
Christian Kandeler committed
787
        this, SLOT(handleDeviceInstallerErrorOutput(QByteArray)));
ck's avatar
ck committed
788
789
    connect(copyProcess.data(), SIGNAL(closed(int)), this,
        SLOT(handleCopyProcessFinished(int)));
790
    m_currentDeviceDeployAction.reset(new DeviceDeployAction(d, copyProcess));
ck's avatar
ck committed
791
792
793
794
795
796
797
    writeOutput(tr("Copying file '%1' to path '%2' on the device...")
        .arg(d.localFilePath, d.remoteDir));
    copyProcess->start();
}

void MaemoDeployStep::handleCopyProcessFinished(int exitStatus)
{
798
    ASSERT_STATE(QList<State>() << CopyingFile << StopRequested << Inactive);
ck's avatar
ck committed
799

800
801
802
803
804
805
806
807
808
809
810
    switch (m_state) {
    case CopyingFile: {
        Q_ASSERT(m_currentDeviceDeployAction);
        const QString localFilePath
            = m_currentDeviceDeployAction->first.localFilePath;
        if (exitStatus != SshRemoteProcess::ExitedNormally
                || m_currentDeviceDeployAction->second->exitCode() != 0) {
            raiseError(tr("Copying file '%1' failed.").arg(localFilePath));
            m_currentDeviceDeployAction.reset(0);
            setState(UnmountingCurrentMounts);
            unmount();
ck's avatar
ck committed
811
        } else {
812
813
814
            writeOutput(tr("Successfully copied file '%1'.").arg(localFilePath));
            setDeployed(m_connection->connectionParameters().host,
                m_currentDeviceDeployAction->first);
815
            m_currentDeviceDeployAction.reset(0);
816
817
818
819
820
821
822
            if (m_filesToCopy.isEmpty()) {
                writeOutput(tr("All files copied."));
                setState(UnmountingCurrentMounts);
                unmount();
            } else {
                copyNextFileToDevice();
            }
823
        }
824
825
826
827
828
829
830
831
        break;
    }
    case StopRequested:
        unmount();
        break;
    case Inactive:
    default:
        break;
ck's avatar
ck committed
832
833
834
835
836
    }
}

QString MaemoDeployStep::deployMountPoint() const
{
837
    return MaemoGlobal::homeDirOnDevice(m_cachedDeviceConfig->sshParameters().uname)
838
        + QLatin1String("/deployMountPoint_") + packagingStep()->projectName();
ck's avatar
ck committed
839
}
Christian Kandeler's avatar
Christian Kandeler committed
840

841
const AbstractMaemoToolChain *MaemoDeployStep::toolChain() const
Christian Kandeler's avatar
Christian Kandeler committed
842
{
843
    return static_cast<AbstractMaemoToolChain *>(qt4BuildConfiguration()->toolChain());
Christian Kandeler's avatar
Christian Kandeler committed
844
845
}

846
847
const AbstractQt4MaemoTarget *MaemoDeployStep::maemotarget() const
{
848
    return static_cast<AbstractQt4MaemoTarget *>(qt4BuildConfiguration()->target());
849
850
}

Christian Kandeler's avatar
Christian Kandeler committed
851
852
void MaemoDeployStep::handleSysrootInstallerOutput()
{
853
854
855
856
857
    ASSERT_STATE(QList<State>() << InstallingToSysroot << StopRequested);

    switch (m_state) {
    case InstallingToSysroot:
    case StopRequested:
Christian Kandeler's avatar
Christian Kandeler committed
858
859
        writeOutput(QString::fromLocal8Bit(m_sysrootInstaller->readAllStandardOutput()),
            NormalOutput);
860
861
862
        break;
    default:
        break;
Christian Kandeler's avatar
Christian Kandeler committed
863
864
865
866
867
    }
}

void MaemoDeployStep::handleSysrootInstallerErrorOutput()
{
868
869
870
871
872
    ASSERT_STATE(QList<State>() << InstallingToSysroot << StopRequested);

    switch (m_state) {
    case InstallingToSysroot:
    case StopRequested:
Christian Kandeler's avatar
Christian Kandeler committed
873
874
        writeOutput(QString::fromLocal8Bit(m_sysrootInstaller->readAllStandardError()),
            BuildStep::ErrorOutput);
875
876
877
        break;
    default:
        break;
Christian Kandeler's avatar
Christian Kandeler committed
878
879
    }
}
880
881
882

void MaemoDeployStep::handleInstallationFinished(int exitStatus)
{
883
884
    ASSERT_STATE(QList<State>() << InstallingToDevice << StopRequested
        << Inactive);
ck's avatar
ck committed
885

886
887
888
889
890
    switch (m_state) {
    case InstallingToDevice:
        if (exitStatus != SshRemoteProcess::ExitedNormally
            || m_deviceInstaller->exitCode() != 0) {
            raiseError(tr("Installing package failed."));
891
892
893
        } else if (m_installerStderr.contains("Will not downgrade")) {
            raiseError(tr("Installation failed: "
                "You tried to downgrade a package, which is not allowed."));
894
895
896
897
898
899
900
901
902
903
904
905
906
907
908
        } else {
            m_needsInstall = false;
            setDeployed(m_connection->connectionParameters().host,
                MaemoDeployable(packagingStep()->packageFilePath(), QString()));
            writeOutput(tr("Package installed."));
        }
        setState(UnmountingCurrentMounts);
        unmount();
        break;
    case StopRequested:
        unmount();
        break;
    case Inactive:
    default:
        break;
ck's avatar
ck committed
909
    }
910
911
}

912
913
void MaemoDeployStep::handlePortsGathererError(const QString &errorMsg)
{
914
915
916
917
918
919
    ASSERT_STATE(QList<State>() << GatheringPorts << StopRequested << Inactive);

    if (m_state != Inactive) {
        raiseError(errorMsg);
        setState(Inactive);
    }
920
921
922
923
}

void MaemoDeployStep::handlePortListReady()
{
924
925
926
927
    ASSERT_STATE(QList<State>() << GatheringPorts << StopRequested);

    if (m_state == GatheringPorts) {
        setState(Mounting);
928
        m_freePorts = m_cachedDeviceConfig->freePorts();
929
930
931
932
933
934
935
936
937
938
939
940
941
942
943
944
945
946
947
948
949
950
951
952
953
954
955
956
957
958
959
960
961
        m_mounter->mount(&m_freePorts, m_portsGatherer);
    } else {
        setState(Inactive);
    }
}

void MaemoDeployStep::setState(State newState)
{
    if (newState == m_state)
        return;
    m_state = newState;
    if (m_state == Inactive) {
        m_needsInstall = false;
        m_filesToCopy.clear();
        m_currentDeviceDeployAction.reset(0);
        if (m_connection)
            disconnect(m_connection.data(), 0, this, 0);
        if (m_uploader) {
            disconnect(m_uploader.data(), 0, this, 0);
            m_uploader->closeChannel();
        }
        if (m_deviceInstaller)
            disconnect(m_deviceInstaller.data(), 0, this, 0);
        emit done();
    }
}

void MaemoDeployStep::unmount()
{
    if (m_mounter->hasValidMountSpecifications())
        m_mounter->unmount();
    else
        handleUnmounted();
962
963
}

Christian Kandeler's avatar
Christian Kandeler committed
964
void MaemoDeployStep::handleDeviceInstallerOutput(const QByteArray &output)
965
{
966
967
968
969
970
971
972
973
974
975
    ASSERT_STATE(QList<State>() << InstallingToDevice << StopRequested);

    switch (m_state) {
    case InstallingToDevice:
    case StopRequested:
        writeOutput(QString::fromUtf8(output), NormalOutput);
        break;
    default:
        break;
    }
976
977
}

Christian Kandeler's avatar
Christian Kandeler committed
978
void MaemoDeployStep::handleDeviceInstallerErrorOutput(const QByteArray &output)
979
{
980
981
982
983
984
    ASSERT_STATE(QList<State>() << InstallingToDevice << StopRequested);

    switch (m_state) {
    case InstallingToDevice:
    case StopRequested:
985
        m_installerStderr += output;
986
987
988
989
990
        writeOutput(QString::fromUtf8(output), ErrorOutput);
        break;
    default:
        break;
    }
991
992
}

993
994
995
996
997
998
const Qt4BuildConfiguration *MaemoDeployStep::qt4BuildConfiguration() const
{
    return static_cast<Qt4BuildConfiguration *>(buildConfiguration());
}


999
1000
MaemoDeployEventHandler::MaemoDeployEventHandler(MaemoDeployStep *deployStep,
    QFutureInterface<bool> &future)
1001
1002
    : m_deployStep(deployStep), m_future(future), m_eventLoop(new QEventLoop),
      m_error(false)
1003
1004
1005
1006
1007
1008
1009
1010
1011
1012
1013
{
    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()
{
1014
    m_eventLoop->exit(m_error ? 1 : 0);
1015
1016
1017
1018
}

void MaemoDeployEventHandler::handleDeployingFailed()
{
1019
    m_error = true;
1020
1021
1022
1023
}

void MaemoDeployEventHandler::checkForCanceled()
{
1024
1025
1026
1027
1028
    if (!m_error && m_future.isCanceled()) {
        QMetaObject::invokeMethod(m_deployStep, "stop");
        m_error = true;
        handleDeployingDone();
    }
1029
1030
1031
1032
}

} // namespace Internal
} // namespace Qt4ProjectManager