blackberryapplicationrunner.cpp 14.8 KB
Newer Older
Tobias Nätterlund's avatar
Tobias Nätterlund committed
1
2
3
4
5
6
7
/**************************************************************************
**
** Copyright (C) 2011 - 2012 Research In Motion
**
** Contact: Research In Motion (blackberry-qt@qnx.com)
** Contact: KDAB (info@kdab.com)
**
hjk's avatar
hjk committed
8
** This file is part of Qt Creator.
Tobias Nätterlund's avatar
Tobias Nätterlund committed
9
**
hjk's avatar
hjk committed
10
11
12
13
14
15
16
** Commercial License Usage
** Licensees holding valid commercial Qt licenses may use this file in
** accordance with the commercial license agreement provided with the
** Software or, alternatively, in accordance with the terms contained in
** a written agreement between you and Digia.  For licensing terms and
** conditions see http://qt.digia.com/licensing.  For further information
** use the contact form at http://qt.digia.com/contact-us.
Tobias Nätterlund's avatar
Tobias Nätterlund committed
17
**
hjk's avatar
hjk committed
18
19
20
21
22
23
24
25
26
27
** 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.
**
** In addition, as a special exception, Digia gives you certain additional
** rights.  These rights are described in the Digia Qt LGPL Exception
Tobias Nätterlund's avatar
Tobias Nätterlund committed
28
29
** version 1.1, included in the file LGPL_EXCEPTION.txt in this package.
**
hjk's avatar
hjk committed
30
****************************************************************************/
Tobias Nätterlund's avatar
Tobias Nätterlund committed
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79

#include "blackberryapplicationrunner.h"

#include "blackberrydeployconfiguration.h"
#include "blackberryrunconfiguration.h"

#include <projectexplorer/target.h>
#include <qt4projectmanager/qt4buildconfiguration.h>
#include <ssh/sshremoteprocessrunner.h>
#include <utils/qtcassert.h>

#include <QTimer>
#include <QDir>

namespace {
const char DEPLOY_CMD[] = "blackberry-deploy";

qint64 parsePid(const QString &line)
{
    QTC_ASSERT(line.startsWith(QLatin1String("result::")), return -1);

    int pidIndex = -1;
    if (line.contains(QLatin1String("running"))) // "result::running,<pid>"
        pidIndex = 16;
    else // "result::<pid>"
        pidIndex = 8;

    bool ok;
    const qint64 pid = line.mid(pidIndex).toInt(&ok);
    if (!ok)
        return -1;
    return pid;
}

QString parseAppId(const QString &line)
{
    QTC_ASSERT(line.startsWith(QLatin1String("Info: Launching")), return QString());

    const int endOfId = line.indexOf(QLatin1String("..."));
    return line.mid(16, endOfId - 16);
}

bool parseRunningState(const QString &line)
{
    QTC_ASSERT(line.startsWith(QLatin1String("result::")), return false);
    return line.trimmed().mid(8) == QLatin1String("true");
}
}

80
using namespace ProjectExplorer;
Tobias Nätterlund's avatar
Tobias Nätterlund committed
81
82
83
84
85
86
using namespace Qnx;
using namespace Qnx::Internal;

BlackBerryApplicationRunner::BlackBerryApplicationRunner(bool debugMode, BlackBerryRunConfiguration *runConfiguration, QObject *parent)
    : QObject(parent)
    , m_debugMode(debugMode)
87
    , m_slog2infoFound(false)
Tobias Nätterlund's avatar
Tobias Nätterlund committed
88
89
90
91
92
93
94
    , m_pid(-1)
    , m_appId(QString())
    , m_running(false)
    , m_stopping(false)
    , m_launchProcess(0)
    , m_stopProcess(0)
    , m_tailProcess(0)
95
    , m_testSlog2Process(0)
Mehdi Fekari's avatar
Mehdi Fekari committed
96
    , m_launchDateTimeProcess(0)
Tobias Nätterlund's avatar
Tobias Nätterlund committed
97
98
99
100
101
    , m_runningStateTimer(new QTimer(this))
    , m_runningStateProcess(0)
{
    QTC_ASSERT(runConfiguration, return);

102
103
104
    Target *target = runConfiguration->target();
    BuildConfiguration *buildConfig = target->activeBuildConfiguration();
    m_environment = buildConfig->environment();
Tobias Nätterlund's avatar
Tobias Nätterlund committed
105
106
    m_deployCmd = m_environment.searchInPath(QLatin1String(DEPLOY_CMD));

107
108
109
    BlackBerryDeviceConfiguration::ConstPtr device = BlackBerryDeviceConfiguration::device(target->kit());
    m_deviceHost = device->sshParameters().host;
    m_password = device->sshParameters().password;
110
    m_barPackage = runConfiguration->barPackage();
Tobias Nätterlund's avatar
Tobias Nätterlund committed
111
112
113
114
115
116
117
118

    m_sshParams = device->sshParameters();
    // The BlackBerry device always uses key authentication
    m_sshParams.authenticationType = QSsh::SshConnectionParameters::AuthenticationByKey;

    m_runningStateTimer->setInterval(3000);
    m_runningStateTimer->setSingleShot(true);
    connect(m_runningStateTimer, SIGNAL(timeout()), this, SLOT(determineRunningState()));
119
    connect(this, SIGNAL(started()), this, SLOT(checkSlog2Info()));
Tobias Nätterlund's avatar
Tobias Nätterlund committed
120
121
122
123
124
125
126
127
}

void BlackBerryApplicationRunner::start()
{
    QStringList args;
    args << QLatin1String("-launchApp");
    if (m_debugMode)
        args << QLatin1String("-debugNative");
128
    args << QLatin1String("-device") << m_deviceHost;
Tobias Nätterlund's avatar
Tobias Nätterlund committed
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
    if (!m_password.isEmpty())
        args << QLatin1String("-password") << m_password;
    args << QDir::toNativeSeparators(m_barPackage);

    if (!m_launchProcess) {
        m_launchProcess = new QProcess(this);
        connect(m_launchProcess, SIGNAL(readyReadStandardError()), this, SLOT(readStandardError()));
        connect(m_launchProcess, SIGNAL(readyReadStandardOutput()), this, SLOT(readStandardOutput()));
        connect(m_launchProcess, SIGNAL(finished(int,QProcess::ExitStatus)),
                this, SLOT(startFinished(int,QProcess::ExitStatus)));

        m_launchProcess->setEnvironment(m_environment.toStringList());
    }

    m_launchProcess->start(m_deployCmd, args);
    m_runningStateTimer->start();
    m_running = true;
}

148
149
void BlackBerryApplicationRunner::checkSlog2Info()
{
150
    if (m_slog2infoFound) {
El Mehdi Fekari's avatar
El Mehdi Fekari committed
151
        readLaunchTime();
152
    } else if (!m_testSlog2Process) {
153
154
155
156
157
158
159
        m_testSlog2Process = new QSsh::SshRemoteProcessRunner(this);
        connect(m_testSlog2Process, SIGNAL(processClosed(int)),
                this, SLOT(handleSlog2InfoFound()));
        m_testSlog2Process->run("slog2info", m_sshParams);
    }
}

Tobias Nätterlund's avatar
Tobias Nätterlund committed
160
161
162
163
164
165
166
167
168
169
170
171
void BlackBerryApplicationRunner::startFinished(int exitCode, QProcess::ExitStatus exitStatus)
{
    if (exitCode == 0 && exitStatus == QProcess::NormalExit && m_pid > -1) {
        emit started();
    } else {
        m_running = false;
        m_runningStateTimer->stop();

        QTC_ASSERT(m_launchProcess, return);
        const QString errorString = (m_launchProcess->error() != QProcess::UnknownError)
                ? m_launchProcess->errorString() : tr("Launching application failed");
        emit startFailed(errorString);
172
        reset();
Tobias Nätterlund's avatar
Tobias Nätterlund committed
173
174
175
176
177
    }
}

ProjectExplorer::RunControl::StopResult BlackBerryApplicationRunner::stop()
{
178
179
180
    if (m_stopping)
        return ProjectExplorer::RunControl::AsynchronousStop;

Tobias Nätterlund's avatar
Tobias Nätterlund committed
181
182
    m_stopping = true;

183
184
185
186
187
188
    if (m_testSlog2Process && m_testSlog2Process->isProcessRunning()) {
        m_testSlog2Process->cancel();
        delete m_testSlog2Process;
        m_testSlog2Process = 0;
    }

Tobias Nätterlund's avatar
Tobias Nätterlund committed
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
    QStringList args;
    args << QLatin1String("-terminateApp");
    args << QLatin1String("-device") << m_deviceHost;
    if (!m_password.isEmpty())
        args << QLatin1String("-password") << m_password;
    args << m_barPackage;

    if (!m_stopProcess) {
        m_stopProcess = new QProcess(this);
        connect(m_stopProcess, SIGNAL(readyReadStandardError()), this, SLOT(readStandardError()));
        connect(m_stopProcess, SIGNAL(readyReadStandardOutput()), this, SLOT(readStandardOutput()));
        connect(m_stopProcess, SIGNAL(finished(int,QProcess::ExitStatus)),
                this, SLOT(stopFinished(int,QProcess::ExitStatus)));

        m_stopProcess->setEnvironment(m_environment.toStringList());
    }

    m_stopProcess->start(m_deployCmd, args);
    return ProjectExplorer::RunControl::AsynchronousStop;
}

bool BlackBerryApplicationRunner::isRunning() const
{
212
    return m_running;
Tobias Nätterlund's avatar
Tobias Nätterlund committed
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
}

qint64 BlackBerryApplicationRunner::pid() const
{
    return m_pid;
}

void BlackBerryApplicationRunner::stopFinished(int exitCode, QProcess::ExitStatus exitStatus)
{
    Q_UNUSED(exitCode);
    Q_UNUSED(exitStatus);

    reset();
}

void BlackBerryApplicationRunner::readStandardOutput()
{
    QProcess *process = qobject_cast<QProcess *>(sender());
    process->setReadChannel(QProcess::StandardOutput);
    while (process->canReadLine()) {
        QString line = QString::fromLocal8Bit(process->readLine());
        emit output(line, Utils::StdOutFormat);

236
        if (line.startsWith(QLatin1String("result::")))
Tobias Nätterlund's avatar
Tobias Nätterlund committed
237
            m_pid = parsePid(line);
238
        else if (line.startsWith(QLatin1String("Info: Launching")))
Tobias Nätterlund's avatar
Tobias Nätterlund committed
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
            m_appId = parseAppId(line);
    }
}

void BlackBerryApplicationRunner::readStandardError()
{
    QProcess *process = qobject_cast<QProcess *>(sender());
    process->setReadChannel(QProcess::StandardError);
    while (process->canReadLine()) {
        const QString line = QString::fromLocal8Bit(process->readLine());
        emit output(line, Utils::StdErrFormat);
    }
}

void BlackBerryApplicationRunner::killTailProcess()
{
    QSsh::SshRemoteProcessRunner *slayProcess = new QSsh::SshRemoteProcessRunner(this);
    connect(slayProcess, SIGNAL(processClosed(int)), this, SIGNAL(finished()));

258
    if (m_slog2infoFound)
259
        slayProcess->run("slay slog2info", m_sshParams);
260
    else
261
        slayProcess->run("slay tail", m_sshParams);
Tobias Nätterlund's avatar
Tobias Nätterlund committed
262
263
264
265
266
267
268
269
270
271
272

    // Not supported by OpenSSH server
    //m_tailProcess->sendSignalToProcess(Utils::SshRemoteProcess::KillSignal);
    m_tailProcess->cancel();

    delete m_tailProcess;
    m_tailProcess = 0;
}

void BlackBerryApplicationRunner::tailApplicationLog()
{
Mehdi Fekari's avatar
Mehdi Fekari committed
273
274
275
276
277
    QSsh::SshRemoteProcessRunner *process = qobject_cast<QSsh::SshRemoteProcessRunner *>(sender());
    QTC_ASSERT(process, return);

    m_launchDateTime = QDateTime::fromString(QString::fromLatin1(process->readAllStandardOutput()).trimmed(),
                                             QString::fromLatin1("dd HH:mm:ss"));
Tobias Nätterlund's avatar
Tobias Nätterlund committed
278

279
    if (m_stopping || (m_tailProcess && m_tailProcess->isProcessRunning()))
Tobias Nätterlund's avatar
Tobias Nätterlund committed
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
        return;

    QTC_CHECK(!m_appId.isEmpty());

    if (!m_tailProcess) {
        m_tailProcess = new QSsh::SshRemoteProcessRunner(this);

        connect(m_tailProcess, SIGNAL(readyReadStandardOutput()),
                this, SLOT(handleTailOutput()));
        connect(m_tailProcess, SIGNAL(readyReadStandardError()),
                this, SLOT(handleTailError()));
        connect(m_tailProcess, SIGNAL(connectionError()),
                this, SLOT(handleTailConnectionError()));
    }

295
296
    QString command;
    if (m_slog2infoFound) {
Mehdi Fekari's avatar
Mehdi Fekari committed
297
        command = QString::fromLatin1("slog2info -w -b ") + m_appId;
298
299
300
301
    } else {
        command = QLatin1String("tail -c +1 -f /accounts/1000/appdata/") + m_appId
                + QLatin1String("/logs/log");
    }
Tobias Nätterlund's avatar
Tobias Nätterlund committed
302
303
304
    m_tailProcess->run(command.toLatin1(), m_sshParams);
}

305
306
307
308
309
310
311
312
313
314
void BlackBerryApplicationRunner::handleSlog2InfoFound()
{
    QSsh::SshRemoteProcessRunner *process = qobject_cast<QSsh::SshRemoteProcessRunner *>(sender());
    QTC_ASSERT(process, return);

    m_slog2infoFound = (process->processExitCode() == 0);

    tailApplicationLog();
}

Mehdi Fekari's avatar
Mehdi Fekari committed
315
316
317
318
319
320
321
322
323
void BlackBerryApplicationRunner::readLaunchTime()
{
    m_launchDateTimeProcess = new QSsh::SshRemoteProcessRunner(this);
    connect(m_launchDateTimeProcess, SIGNAL(processClosed(int)),
            this, SLOT(tailApplicationLog()));

    m_launchDateTimeProcess->run("date +\"%d %H:%M:%S\"", m_sshParams);
}

Tobias Nätterlund's avatar
Tobias Nätterlund committed
324
325
326
327
328
329
void BlackBerryApplicationRunner::handleTailOutput()
{
    QSsh::SshRemoteProcessRunner *process = qobject_cast<QSsh::SshRemoteProcessRunner *>(sender());
    QTC_ASSERT(process, return);

    const QString message = QString::fromLatin1(process->readAllStandardOutput());
330
331
332
    if (m_slog2infoFound) {
        const QStringList multiLine = message.split(QLatin1Char('\n'));
        Q_FOREACH (const QString &line, multiLine) {
Mehdi Fekari's avatar
Mehdi Fekari committed
333
334
335
            QDateTime dateTime = QDateTime::fromString(line.split(m_appId).first().mid(4).trimmed(),
                                                       QString::fromLatin1("dd HH:mm:ss.zzz"));
            if (dateTime >= m_launchDateTime) {
336
337
338
                QStringList validLineBeginnings;
                validLineBeginnings << QLatin1String("qt-msg      0  ")
                                    << QLatin1String("qt-msg*     0  ")
339
340
                                    << QLatin1String("default*  9000  ")
                                    << QLatin1String("default   9000  ")
341
342
343
344
345
346
347
348
349
                                    << QLatin1String("                           0  ");
                Q_FOREACH (const QString &beginning, validLineBeginnings) {
                    if (showQtMessage(beginning, line))
                        break;
                }
            }
        }
        return;
    }
Tobias Nätterlund's avatar
Tobias Nätterlund committed
350
351
352
    emit output(message, Utils::StdOutFormat);
}

353
354
355
356
357
358
359
360
361
362
363
bool BlackBerryApplicationRunner::showQtMessage(const QString& pattern, const QString& line)
{
    const int index = line.indexOf(pattern);
    if (index != -1) {
        const QString str = line.right(line.length()-index-pattern.length()) + QLatin1Char('\n');
        emit output(str, Utils::StdOutFormat);
        return true;
    }
    return false;
}

Tobias Nätterlund's avatar
Tobias Nätterlund committed
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
void BlackBerryApplicationRunner::handleTailError()
{
    QSsh::SshRemoteProcessRunner *process = qobject_cast<QSsh::SshRemoteProcessRunner *>(sender());
    QTC_ASSERT(process, return);

    const QString message = QString::fromLatin1(process->readAllStandardError());
    emit output(message, Utils::StdErrFormat);
}

void BlackBerryApplicationRunner::handleTailConnectionError()
{
    emit output(tr("Cannot show debug output. Error: %1").arg(m_tailProcess->lastConnectionErrorString()),
                Utils::StdErrFormat);
}

void BlackBerryApplicationRunner::startRunningStateTimer()
{
    if (m_running)
        m_runningStateTimer->start();
}

void BlackBerryApplicationRunner::determineRunningState()
{
    QStringList args;
    args << QLatin1String("-isAppRunning");
389
    args << QLatin1String("-device") << m_deviceHost;
Tobias Nätterlund's avatar
Tobias Nätterlund committed
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
    if (!m_password.isEmpty())
        args << QLatin1String("-password") << m_password;
    args << m_barPackage;

    if (!m_runningStateProcess) {
        m_runningStateProcess = new QProcess(this);

        connect(m_runningStateProcess, SIGNAL(readyReadStandardOutput()), this, SLOT(readRunningStateStandardOutput()));
        connect(m_runningStateProcess, SIGNAL(finished(int,QProcess::ExitStatus)), this, SLOT(startRunningStateTimer()));
    }

    m_runningStateProcess->setEnvironment(m_environment.toStringList());

    m_runningStateProcess->start(m_deployCmd, args);
}

void BlackBerryApplicationRunner::readRunningStateStandardOutput()
{
    QProcess *process = qobject_cast<QProcess *>(sender());
    process->setReadChannel(QProcess::StandardOutput);
    while (process->canReadLine()) {
        const QString line = QString::fromLocal8Bit(process->readLine());
        if (line.startsWith(QLatin1String("result"))) {
            m_running = parseRunningState(line);
            break;
        }
    }

    if (!m_running)
        reset();
}

void BlackBerryApplicationRunner::reset()
{
    m_pid = -1;
425
    m_appId.clear();
Tobias Nätterlund's avatar
Tobias Nätterlund committed
426
427
428
429
    m_running = false;
    m_stopping = false;

    m_runningStateTimer->stop();
430
431
432
433
434
    if (m_runningStateProcess) {
        m_runningStateProcess->terminate();
        if (!m_runningStateProcess->waitForFinished(1000))
            m_runningStateProcess->kill();
    }
Tobias Nätterlund's avatar
Tobias Nätterlund committed
435
436
437
438
439
440

    if (m_tailProcess && m_tailProcess->isProcessRunning())
        killTailProcess();
    else
        emit finished();
}