25cd18bb6b3fa0ac84e49e7bf2287e0052a3df48
[profile/ivi/qtdeclarative.git] / src / qml / debugger / qqmldebugserver.cpp
1 /****************************************************************************
2 **
3 ** Copyright (C) 2012 Nokia Corporation and/or its subsidiary(-ies).
4 ** Contact: http://www.qt-project.org/
5 **
6 ** This file is part of the QtQml module of the Qt Toolkit.
7 **
8 ** $QT_BEGIN_LICENSE:LGPL$
9 ** GNU Lesser General Public License Usage
10 ** This file may be used under the terms of the GNU Lesser General Public
11 ** License version 2.1 as published by the Free Software Foundation and
12 ** appearing in the file LICENSE.LGPL included in the packaging of this
13 ** file. Please review the following information to ensure the GNU Lesser
14 ** General Public License version 2.1 requirements will be met:
15 ** http://www.gnu.org/licenses/old-licenses/lgpl-2.1.html.
16 **
17 ** In addition, as a special exception, Nokia gives you certain additional
18 ** rights. These rights are described in the Nokia Qt LGPL Exception
19 ** version 1.1, included in the file LGPL_EXCEPTION.txt in this package.
20 **
21 ** GNU General Public License Usage
22 ** Alternatively, this file may be used under the terms of the GNU General
23 ** Public License version 3.0 as published by the Free Software Foundation
24 ** and appearing in the file LICENSE.GPL included in the packaging of this
25 ** file. Please review the following information to ensure the GNU General
26 ** Public License version 3.0 requirements will be met:
27 ** http://www.gnu.org/copyleft/gpl.html.
28 **
29 ** Other Usage
30 ** Alternatively, this file may be used in accordance with the terms and
31 ** conditions contained in a signed written agreement between you and Nokia.
32 **
33 **
34 **
35 **
36 **
37 **
38 ** $QT_END_LICENSE$
39 **
40 ****************************************************************************/
41
42 #include "qqmldebugserver_p.h"
43 #include "qqmldebugservice_p.h"
44 #include "qqmldebugservice_p_p.h"
45 #include <private/qqmlengine_p.h>
46 #include <private/qqmlglobal_p.h>
47
48 #include <QtCore/QAtomicInt>
49 #include <QtCore/QDir>
50 #include <QtCore/QPluginLoader>
51 #include <QtCore/QStringList>
52 #include <QtCore/qwaitcondition.h>
53
54 #include <private/qobject_p.h>
55 #include <private/qcoreapplication_p.h>
56
57 QT_BEGIN_NAMESPACE
58
59 /*
60   QQmlDebug Protocol (Version 1):
61
62   handshake:
63     1. Client sends
64          "QDeclarativeDebugServer" 0 version pluginNames
65        version: an int representing the highest protocol version the client knows
66        pluginNames: plugins available on client side
67     2. Server sends
68          "QDeclarativeDebugClient" 0 version pluginNames pluginVersions
69        version: an int representing the highest protocol version the client & server know
70        pluginNames: plugins available on server side. plugins both in the client and server message are enabled.
71   client plugin advertisement
72     1. Client sends
73          "QDeclarativeDebugServer" 1 pluginNames
74   server plugin advertisement
75     1. Server sends
76          "QDeclarativeDebugClient" 1 pluginNames pluginVersions
77   plugin communication:
78        Everything send with a header different to "QDeclarativeDebugServer" is sent to the appropriate plugin.
79   */
80
81 const int protocolVersion = 1;
82
83 // print detailed information about loading of plugins
84 DEFINE_BOOL_CONFIG_OPTION(qmlDebugVerbose, QML_DEBUGGER_VERBOSE)
85
86 class QQmlDebugServerThread;
87
88 class QQmlDebugServerPrivate : public QObjectPrivate
89 {
90     Q_DECLARE_PUBLIC(QQmlDebugServer)
91 public:
92     QQmlDebugServerPrivate();
93
94     void advertisePlugins();
95     QQmlDebugServerConnection *loadConnectionPlugin(const QString &pluginName);
96
97     QQmlDebugServerConnection *connection;
98     QHash<QString, QQmlDebugService *> plugins;
99     mutable QReadWriteLock pluginsLock;
100     QStringList clientPlugins;
101     bool gotHello;
102
103     QMutex messageArrivedMutex;
104     QWaitCondition messageArrivedCondition;
105     QStringList waitingForMessageNames;
106     QQmlDebugServerThread *thread;
107     QPluginLoader loader;
108     QAtomicInt changeServiceStateCalls;
109
110 private:
111     // private slots
112     void _q_changeServiceState(const QString &serviceName,
113                                QQmlDebugService::State newState);
114     void _q_sendMessages(const QList<QByteArray> &messages);
115 };
116
117 class QQmlDebugServerThread : public QThread
118 {
119 public:
120     void setPluginName(const QString &pluginName) {
121         m_pluginName = pluginName;
122     }
123
124     void setPort(int port, bool block, QString &hostAddress) {
125         m_port = port;
126         m_block = block;
127         m_hostAddress = hostAddress;
128     }
129
130     void run();
131
132 private:
133     QString m_pluginName;
134     int m_port;
135     bool m_block;
136     QString m_hostAddress;
137 };
138
139 QQmlDebugServerPrivate::QQmlDebugServerPrivate() :
140     connection(0),
141     gotHello(false),
142     thread(0)
143 {
144     // used in _q_sendMessages
145     qRegisterMetaType<QList<QByteArray> >("QList<QByteArray>");
146     // used in _q_changeServiceState
147     qRegisterMetaType<QQmlDebugService::State>("QQmlDebugService::State");
148 }
149
150 void QQmlDebugServerPrivate::advertisePlugins()
151 {
152     Q_Q(QQmlDebugServer);
153
154     if (!gotHello)
155         return;
156
157     QByteArray message;
158     {
159         QDataStream out(&message, QIODevice::WriteOnly);
160         QStringList pluginNames;
161         QList<float> pluginVersions;
162         foreach (QQmlDebugService *service, plugins.values()) {
163             pluginNames << service->name();
164             pluginVersions << service->version();
165         }
166         out << QString(QStringLiteral("QDeclarativeDebugClient")) << 1 << pluginNames << pluginVersions;
167     }
168
169     QMetaObject::invokeMethod(q, "_q_sendMessages", Qt::QueuedConnection, Q_ARG(QList<QByteArray>, QList<QByteArray>() << message));
170 }
171
172 QQmlDebugServerConnection *QQmlDebugServerPrivate::loadConnectionPlugin(
173         const QString &pluginName)
174 {
175 #ifndef QT_NO_LIBRARY
176     QStringList pluginCandidates;
177     const QStringList paths = QCoreApplication::libraryPaths();
178     foreach (const QString &libPath, paths) {
179         const QDir dir(libPath + QLatin1String("/qmltooling"));
180         if (dir.exists()) {
181             QStringList plugins(dir.entryList(QDir::Files));
182             foreach (const QString &pluginPath, plugins) {
183                 if (QFileInfo(pluginPath).fileName().contains(pluginName))
184                     pluginCandidates << dir.absoluteFilePath(pluginPath);
185             }
186         }
187     }
188
189     foreach (const QString &pluginPath, pluginCandidates) {
190         if (qmlDebugVerbose())
191             qDebug() << "QML Debugger: Trying to load plugin " << pluginPath << "...";
192
193         loader.setFileName(pluginPath);
194         if (!loader.load()) {
195             if (qmlDebugVerbose())
196                 qDebug() << "QML Debugger: Error while loading: " << loader.errorString();
197             continue;
198         }
199         if (QObject *instance = loader.instance())
200             connection = qobject_cast<QQmlDebugServerConnection*>(instance);
201
202         if (connection) {
203             if (qmlDebugVerbose())
204                 qDebug() << "QML Debugger: Plugin successfully loaded.";
205
206             return connection;
207         }
208
209         if (qmlDebugVerbose())
210             qDebug() << "QML Debugger: Plugin does not implement interface QQmlDebugServerConnection.";
211
212         loader.unload();
213     }
214 #endif
215     return 0;
216 }
217
218 void QQmlDebugServerThread::run()
219 {
220     QQmlDebugServer *server = QQmlDebugServer::instance();
221     QQmlDebugServerConnection *connection
222             = server->d_func()->loadConnectionPlugin(m_pluginName);
223     if (connection) {
224         connection->setServer(QQmlDebugServer::instance());
225         connection->setPort(m_port, m_block, m_hostAddress);
226     } else {
227         QCoreApplicationPrivate *appD = static_cast<QCoreApplicationPrivate*>(QObjectPrivate::get(qApp));
228         qWarning() << QString(QLatin1String("QML Debugger: Ignoring \"-qmljsdebugger=%1\". "
229                                             "Remote debugger plugin has not been found.")).arg(appD->qmljsDebugArgumentsString());
230     }
231
232     exec();
233
234     // make sure events still waiting are processed
235     QEventLoop eventLoop;
236     eventLoop.processEvents(QEventLoop::AllEvents);
237 }
238
239 bool QQmlDebugServer::hasDebuggingClient() const
240 {
241     Q_D(const QQmlDebugServer);
242     return d->connection
243             && d->connection->isConnected()
244             && d->gotHello;
245 }
246
247 static QQmlDebugServer *qQmlDebugServer = 0;
248
249
250 static void cleanup()
251 {
252     delete qQmlDebugServer;
253     qQmlDebugServer = 0;
254 }
255
256 QQmlDebugServer *QQmlDebugServer::instance()
257 {
258     static bool commandLineTested = false;
259
260     if (!commandLineTested) {
261         commandLineTested = true;
262
263         QCoreApplicationPrivate *appD = static_cast<QCoreApplicationPrivate*>(QObjectPrivate::get(qApp));
264 #ifndef QT_QML_NO_DEBUGGER
265         // ### remove port definition when protocol is changed
266         int port = 0;
267         bool block = false;
268         bool ok = false;
269         QString hostAddress;
270
271         // format: qmljsdebugger=port:3768[,host:<ip address>][,block] OR qmljsdebugger=ost[,block]
272         if (!appD->qmljsDebugArgumentsString().isEmpty()) {
273             if (!QQmlEnginePrivate::qml_debugging_enabled) {
274                 qWarning() << QString(QLatin1String(
275                                   "QML Debugger: Ignoring \"-qmljsdebugger=%1\". "
276                                   "Debugging has not been enabled.")).arg(
277                                   appD->qmljsDebugArgumentsString());
278                 return 0;
279             }
280
281             QString pluginName;
282             QStringList lstjsDebugArguments = appD->qmljsDebugArgumentsString()
283                                                                     .split(QLatin1Char(','));
284             foreach (const QString &strArgument, lstjsDebugArguments) {
285                 if (strArgument.startsWith(QLatin1String("port:"))) {
286                     port = strArgument.mid(5).toInt(&ok);
287                     pluginName = QLatin1String("qmldbg_tcp");
288                 } else if (strArgument.startsWith(QLatin1String("host:"))) {
289                     hostAddress = strArgument.mid(5);
290                 } else if (strArgument == QLatin1String("block")) {
291                     block = true;
292                 } else {
293                     qWarning() << QString::fromLatin1("QML Debugger: Invalid argument '%1' "
294                                                       "detected. Ignoring the same.")
295                                                        .arg(strArgument);
296                 }
297             }
298
299             if (ok) {
300                 qQmlDebugServer = new QQmlDebugServer();
301                 QQmlDebugServerThread *thread = new QQmlDebugServerThread;
302                 qQmlDebugServer->d_func()->thread = thread;
303                 qQmlDebugServer->moveToThread(thread);
304                 thread->setPluginName(pluginName);
305                 thread->setPort(port, block, hostAddress);
306                 thread->start();
307
308                 if (block) {
309                     QQmlDebugServerPrivate *d = qQmlDebugServer->d_func();
310                     d->messageArrivedMutex.lock();
311                     d->messageArrivedCondition.wait(&d->messageArrivedMutex);
312                     d->messageArrivedMutex.unlock();
313                 }
314
315             } else {
316                 qWarning() << QString(QLatin1String(
317                                   "QML Debugger: Ignoring \"-qmljsdebugger=%1\". "
318                                   "Format is -qmljsdebugger=port:<port>[,block]")).arg(
319                                   appD->qmljsDebugArgumentsString());
320             }
321         }
322 #else
323         if (!appD->qmljsDebugArgumentsString().isEmpty()) {
324             qWarning() << QString(QLatin1String(
325                          "QML Debugger: Ignoring \"-qmljsdebugger=%1\". "
326                          "QtQml is not configured for debugging.")).arg(
327                          appD->qmljsDebugArgumentsString());
328         }
329 #endif
330     }
331
332     return qQmlDebugServer;
333 }
334
335 QQmlDebugServer::QQmlDebugServer()
336     : QObject(*(new QQmlDebugServerPrivate))
337 {
338     qAddPostRoutine(cleanup);
339 }
340
341 // called from GUI thread!
342 QQmlDebugServer::~QQmlDebugServer()
343 {
344     Q_D(QQmlDebugServer);
345
346     QReadLocker(&d->pluginsLock);
347     {
348         foreach (QQmlDebugService *service, d->plugins.values()) {
349             d->changeServiceStateCalls.ref();
350             QMetaObject::invokeMethod(this, "_q_changeServiceState", Qt::QueuedConnection,
351                                       Q_ARG(QString, service->name()),
352                                       Q_ARG(QQmlDebugService::State, QQmlDebugService::NotConnected));
353         }
354     }
355
356     // Wait for changeServiceState calls to finish
357     // (while running an event loop because some services
358     // might again use slots to execute stuff in the GUI thread)
359     QEventLoop loop;
360     while (!d->changeServiceStateCalls.testAndSetOrdered(0, 0))
361         loop.processEvents();
362
363     if (d->thread) {
364         d->thread->exit();
365         d->thread->wait();
366         delete d->thread;
367     }
368     delete d->connection;
369 }
370
371 void QQmlDebugServer::receiveMessage(const QByteArray &message)
372 {
373     // to be executed in debugger thread
374     Q_ASSERT(QThread::currentThread() == thread());
375
376     Q_D(QQmlDebugServer);
377
378     QDataStream in(message);
379
380     QString name;
381
382     in >> name;
383     if (name == QLatin1String("QDeclarativeDebugServer")) {
384         int op = -1;
385         in >> op;
386         if (op == 0) {
387             int version;
388             in >> version >> d->clientPlugins;
389
390             // Send the hello answer immediately, since it needs to arrive before
391             // the plugins below start sending messages.
392             QByteArray helloAnswer;
393             {
394                 QDataStream out(&helloAnswer, QIODevice::WriteOnly);
395                 QStringList pluginNames;
396                 QList<float> pluginVersions;
397                 foreach (QQmlDebugService *service, d->plugins.values()) {
398                     pluginNames << service->name();
399                     pluginVersions << service->version();
400                 }
401
402                 out << QString(QStringLiteral("QDeclarativeDebugClient")) << 0 << protocolVersion << pluginNames << pluginVersions;
403             }
404             d->connection->send(QList<QByteArray>() << helloAnswer);
405
406             d->gotHello = true;
407
408             QReadLocker(&d->pluginsLock);
409             QHash<QString, QQmlDebugService*>::ConstIterator iter = d->plugins.constBegin();
410             for (; iter != d->plugins.constEnd(); ++iter) {
411                 QQmlDebugService::State newState = QQmlDebugService::Unavailable;
412                 if (d->clientPlugins.contains(iter.key()))
413                     newState = QQmlDebugService::Enabled;
414                 d->changeServiceStateCalls.ref();
415                 d->_q_changeServiceState(iter.value()->name(), newState);
416             }
417
418             d->messageArrivedCondition.wakeAll();
419
420         } else if (op == 1) {
421
422             // Service Discovery
423             QStringList oldClientPlugins = d->clientPlugins;
424             in >> d->clientPlugins;
425
426             QReadLocker(&d->pluginsLock);
427             QHash<QString, QQmlDebugService*>::ConstIterator iter = d->plugins.constBegin();
428             for (; iter != d->plugins.constEnd(); ++iter) {
429                 const QString pluginName = iter.key();
430                 QQmlDebugService::State newState = QQmlDebugService::Unavailable;
431                 if (d->clientPlugins.contains(pluginName))
432                     newState = QQmlDebugService::Enabled;
433
434                 if (oldClientPlugins.contains(pluginName)
435                         != d->clientPlugins.contains(pluginName)) {
436                     d->changeServiceStateCalls.ref();
437                     d->_q_changeServiceState(iter.value()->name(), newState);
438                 }
439             }
440
441         } else {
442             qWarning("QML Debugger: Invalid control message %d.", op);
443             d->connection->disconnect();
444             return;
445         }
446
447     } else {
448         if (d->gotHello) {
449             QByteArray message;
450             in >> message;
451
452             QReadLocker(&d->pluginsLock);
453             QHash<QString, QQmlDebugService *>::Iterator iter = d->plugins.find(name);
454             if (iter == d->plugins.end()) {
455                 qWarning() << "QML Debugger: Message received for missing plugin" << name << ".";
456             } else {
457                 (*iter)->messageReceived(message);
458
459                 if (d->waitingForMessageNames.removeOne(name))
460                     d->messageArrivedCondition.wakeAll();
461             }
462         } else {
463             qWarning("QML Debugger: Invalid hello message.");
464         }
465
466     }
467 }
468
469 void QQmlDebugServerPrivate::_q_changeServiceState(const QString &serviceName,
470                                                    QQmlDebugService::State newState)
471 {
472     // to be executed in debugger thread
473     Q_ASSERT(QThread::currentThread() == q_func()->thread());
474
475     QQmlDebugService *service = 0;
476     {
477         QReadLocker lock(&pluginsLock);
478         service = plugins.value(serviceName);
479     }
480
481     if (service && (service->d_func()->state != newState)) {
482         service->stateAboutToBeChanged(newState);
483         service->d_func()->state = newState;
484         if (newState == QQmlDebugService::NotConnected)
485             service->d_func()->server = 0;
486         service->stateChanged(newState);
487     }
488
489     changeServiceStateCalls.deref();
490 }
491
492 void QQmlDebugServerPrivate::_q_sendMessages(const QList<QByteArray> &messages)
493 {
494     // to be executed in debugger thread
495     Q_ASSERT(QThread::currentThread() == q_func()->thread());
496
497     if (connection)
498         connection->send(messages);
499 }
500
501 QList<QQmlDebugService*> QQmlDebugServer::services() const
502 {
503     Q_D(const QQmlDebugServer);
504     QReadLocker(&d->pluginsLock);
505     return d->plugins.values();
506 }
507
508 QStringList QQmlDebugServer::serviceNames() const
509 {
510     Q_D(const QQmlDebugServer);
511     QReadLocker(&d->pluginsLock);
512     return d->plugins.keys();
513 }
514
515 bool QQmlDebugServer::addService(QQmlDebugService *service)
516 {
517     Q_D(QQmlDebugServer);
518
519     // to be executed in GUI thread
520     Q_ASSERT(QThread::currentThread() == QCoreApplication::instance()->thread());
521
522     {
523         QWriteLocker(&d->pluginsLock);
524         if (!service || d->plugins.contains(service->name()))
525             return false;
526         d->plugins.insert(service->name(), service);
527     }
528     {
529         QReadLocker(&d->pluginsLock);
530         d->advertisePlugins();
531         QQmlDebugService::State newState = QQmlDebugService::Unavailable;
532         if (d->clientPlugins.contains(service->name()))
533             newState = QQmlDebugService::Enabled;
534         service->d_func()->state = newState;
535     }
536     return true;
537 }
538
539 bool QQmlDebugServer::removeService(QQmlDebugService *service)
540 {
541     Q_D(QQmlDebugServer);
542
543     // to be executed in GUI thread
544     Q_ASSERT(QThread::currentThread() == QCoreApplication::instance()->thread());
545
546     {
547         QWriteLocker(&d->pluginsLock);
548         QQmlDebugService::State newState = QQmlDebugService::NotConnected;
549
550         d->changeServiceStateCalls.ref();
551         QMetaObject::invokeMethod(this, "_q_changeServiceState", Qt::QueuedConnection,
552                                   Q_ARG(QString, service->name()),
553                                   Q_ARG(QQmlDebugService::State, newState));
554
555         if (!service || !d->plugins.contains(service->name()))
556             return false;
557         d->plugins.remove(service->name());
558
559         d->advertisePlugins();
560     }
561
562     return true;
563 }
564
565 void QQmlDebugServer::sendMessages(QQmlDebugService *service,
566                                           const QList<QByteArray> &messages)
567 {
568     QList<QByteArray> prefixedMessages;
569     foreach (const QByteArray &message, messages) {
570         QByteArray prefixed;
571         QDataStream out(&prefixed, QIODevice::WriteOnly);
572         out << service->name() << message;
573         prefixedMessages << prefixed;
574     }
575
576     QMetaObject::invokeMethod(this, "_q_sendMessages", Qt::QueuedConnection,
577                               Q_ARG(QList<QByteArray>, prefixedMessages));
578 }
579
580 bool QQmlDebugServer::waitForMessage(QQmlDebugService *service)
581 {
582     // to be executed in GUI thread
583     Q_ASSERT(QThread::currentThread() == QCoreApplication::instance()->thread());
584
585     Q_D(QQmlDebugServer);
586     QReadLocker(&d->pluginsLock);
587
588     if (!service
589             || !d->plugins.contains(service->name()))
590         return false;
591
592     d->messageArrivedMutex.lock();
593     d->waitingForMessageNames << service->name();
594     do {
595         d->messageArrivedCondition.wait(&d->messageArrivedMutex);
596     } while (d->waitingForMessageNames.contains(service->name()));
597     d->messageArrivedMutex.unlock();
598     return true;
599 }
600
601 QT_END_NAMESPACE
602
603 #include "moc_qqmldebugserver_p.cpp"