d49d04f595799c2aa34b44a435dbdacb37bfa53d
[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
307                 QQmlDebugServerPrivate *d = qQmlDebugServer->d_func();
308                 QMutexLocker locker(&d->messageArrivedMutex);
309                 thread->start();
310
311                 if (block)
312                     d->messageArrivedCondition.wait(&d->messageArrivedMutex);
313
314             } else {
315                 qWarning() << QString(QLatin1String(
316                                   "QML Debugger: Ignoring \"-qmljsdebugger=%1\". "
317                                   "Format is -qmljsdebugger=port:<port>[,block]")).arg(
318                                   appD->qmljsDebugArgumentsString());
319             }
320         }
321 #else
322         if (!appD->qmljsDebugArgumentsString().isEmpty()) {
323             qWarning() << QString(QLatin1String(
324                          "QML Debugger: Ignoring \"-qmljsdebugger=%1\". "
325                          "QtQml is not configured for debugging.")).arg(
326                          appD->qmljsDebugArgumentsString());
327         }
328 #endif
329     }
330
331     return qQmlDebugServer;
332 }
333
334 QQmlDebugServer::QQmlDebugServer()
335     : QObject(*(new QQmlDebugServerPrivate))
336 {
337     qAddPostRoutine(cleanup);
338 }
339
340 // called from GUI thread!
341 QQmlDebugServer::~QQmlDebugServer()
342 {
343     Q_D(QQmlDebugServer);
344
345     {
346         QReadLocker lock(&d->pluginsLock);
347         foreach (QQmlDebugService *service, d->plugins.values()) {
348             d->changeServiceStateCalls.ref();
349             QMetaObject::invokeMethod(this, "_q_changeServiceState", Qt::QueuedConnection,
350                                       Q_ARG(QString, service->name()),
351                                       Q_ARG(QQmlDebugService::State, QQmlDebugService::NotConnected));
352         }
353     }
354
355     // Wait for changeServiceState calls to finish
356     // (while running an event loop because some services
357     // might again use slots to execute stuff in the GUI thread)
358     QEventLoop loop;
359     while (!d->changeServiceStateCalls.testAndSetOrdered(0, 0))
360         loop.processEvents();
361
362     if (d->thread) {
363         d->thread->exit();
364         d->thread->wait();
365         delete d->thread;
366     }
367     delete d->connection;
368 }
369
370 void QQmlDebugServer::receiveMessage(const QByteArray &message)
371 {
372     // to be executed in debugger thread
373     Q_ASSERT(QThread::currentThread() == thread());
374
375     Q_D(QQmlDebugServer);
376
377     QDataStream in(message);
378
379     QString name;
380
381     in >> name;
382     if (name == QLatin1String("QDeclarativeDebugServer")) {
383         int op = -1;
384         in >> op;
385         if (op == 0) {
386             int version;
387             in >> version >> d->clientPlugins;
388
389             // Send the hello answer immediately, since it needs to arrive before
390             // the plugins below start sending messages.
391             QByteArray helloAnswer;
392             {
393                 QDataStream out(&helloAnswer, QIODevice::WriteOnly);
394                 QStringList pluginNames;
395                 QList<float> pluginVersions;
396                 foreach (QQmlDebugService *service, d->plugins.values()) {
397                     pluginNames << service->name();
398                     pluginVersions << service->version();
399                 }
400
401                 out << QString(QStringLiteral("QDeclarativeDebugClient")) << 0 << protocolVersion << pluginNames << pluginVersions;
402             }
403             d->connection->send(QList<QByteArray>() << helloAnswer);
404
405             d->gotHello = true;
406
407             QReadLocker lock(&d->pluginsLock);
408             QHash<QString, QQmlDebugService*>::ConstIterator iter = d->plugins.constBegin();
409             for (; iter != d->plugins.constEnd(); ++iter) {
410                 QQmlDebugService::State newState = QQmlDebugService::Unavailable;
411                 if (d->clientPlugins.contains(iter.key()))
412                     newState = QQmlDebugService::Enabled;
413                 d->changeServiceStateCalls.ref();
414                 d->_q_changeServiceState(iter.value()->name(), newState);
415             }
416
417             d->messageArrivedCondition.wakeAll();
418
419         } else if (op == 1) {
420
421             // Service Discovery
422             QStringList oldClientPlugins = d->clientPlugins;
423             in >> d->clientPlugins;
424
425             QReadLocker lock(&d->pluginsLock);
426             QHash<QString, QQmlDebugService*>::ConstIterator iter = d->plugins.constBegin();
427             for (; iter != d->plugins.constEnd(); ++iter) {
428                 const QString pluginName = iter.key();
429                 QQmlDebugService::State newState = QQmlDebugService::Unavailable;
430                 if (d->clientPlugins.contains(pluginName))
431                     newState = QQmlDebugService::Enabled;
432
433                 if (oldClientPlugins.contains(pluginName)
434                         != d->clientPlugins.contains(pluginName)) {
435                     d->changeServiceStateCalls.ref();
436                     d->_q_changeServiceState(iter.value()->name(), newState);
437                 }
438             }
439
440         } else {
441             qWarning("QML Debugger: Invalid control message %d.", op);
442             d->connection->disconnect();
443             return;
444         }
445
446     } else {
447         if (d->gotHello) {
448             QByteArray message;
449             in >> message;
450
451             QReadLocker lock(&d->pluginsLock);
452             QHash<QString, QQmlDebugService *>::Iterator iter = d->plugins.find(name);
453             if (iter == d->plugins.end()) {
454                 qWarning() << "QML Debugger: Message received for missing plugin" << name << ".";
455             } else {
456                 (*iter)->messageReceived(message);
457
458                 if (d->waitingForMessageNames.removeOne(name))
459                     d->messageArrivedCondition.wakeAll();
460             }
461         } else {
462             qWarning("QML Debugger: Invalid hello message.");
463         }
464
465     }
466 }
467
468 void QQmlDebugServerPrivate::_q_changeServiceState(const QString &serviceName,
469                                                    QQmlDebugService::State newState)
470 {
471     // to be executed in debugger thread
472     Q_ASSERT(QThread::currentThread() == q_func()->thread());
473
474     QQmlDebugService *service = 0;
475     {
476         QReadLocker lock(&pluginsLock);
477         service = plugins.value(serviceName);
478     }
479
480     if (service && (service->d_func()->state != newState)) {
481         service->stateAboutToBeChanged(newState);
482         service->d_func()->state = newState;
483         if (newState == QQmlDebugService::NotConnected)
484             service->d_func()->server = 0;
485         service->stateChanged(newState);
486     }
487
488     changeServiceStateCalls.deref();
489 }
490
491 void QQmlDebugServerPrivate::_q_sendMessages(const QList<QByteArray> &messages)
492 {
493     // to be executed in debugger thread
494     Q_ASSERT(QThread::currentThread() == q_func()->thread());
495
496     if (connection)
497         connection->send(messages);
498 }
499
500 QList<QQmlDebugService*> QQmlDebugServer::services() const
501 {
502     Q_D(const QQmlDebugServer);
503     QReadLocker lock(&d->pluginsLock);
504     return d->plugins.values();
505 }
506
507 QStringList QQmlDebugServer::serviceNames() const
508 {
509     Q_D(const QQmlDebugServer);
510     QReadLocker lock(&d->pluginsLock);
511     return d->plugins.keys();
512 }
513
514 bool QQmlDebugServer::addService(QQmlDebugService *service)
515 {
516     Q_D(QQmlDebugServer);
517
518     // to be executed in GUI thread
519     Q_ASSERT(QThread::currentThread() == QCoreApplication::instance()->thread());
520
521     {
522         QWriteLocker lock(&d->pluginsLock);
523         if (!service || d->plugins.contains(service->name()))
524             return false;
525         d->plugins.insert(service->name(), service);
526     }
527     {
528         QReadLocker lock(&d->pluginsLock);
529         d->advertisePlugins();
530         QQmlDebugService::State newState = QQmlDebugService::Unavailable;
531         if (d->clientPlugins.contains(service->name()))
532             newState = QQmlDebugService::Enabled;
533         service->d_func()->state = newState;
534     }
535     return true;
536 }
537
538 bool QQmlDebugServer::removeService(QQmlDebugService *service)
539 {
540     Q_D(QQmlDebugServer);
541
542     // to be executed in GUI thread
543     Q_ASSERT(QThread::currentThread() == QCoreApplication::instance()->thread());
544
545     {
546         QWriteLocker lock(&d->pluginsLock);
547         QQmlDebugService::State newState = QQmlDebugService::NotConnected;
548
549         d->changeServiceStateCalls.ref();
550         QMetaObject::invokeMethod(this, "_q_changeServiceState", Qt::QueuedConnection,
551                                   Q_ARG(QString, service->name()),
552                                   Q_ARG(QQmlDebugService::State, newState));
553
554         if (!service || !d->plugins.contains(service->name()))
555             return false;
556         d->plugins.remove(service->name());
557
558         d->advertisePlugins();
559     }
560
561     return true;
562 }
563
564 void QQmlDebugServer::sendMessages(QQmlDebugService *service,
565                                           const QList<QByteArray> &messages)
566 {
567     QList<QByteArray> prefixedMessages;
568     foreach (const QByteArray &message, messages) {
569         QByteArray prefixed;
570         QDataStream out(&prefixed, QIODevice::WriteOnly);
571         out << service->name() << message;
572         prefixedMessages << prefixed;
573     }
574
575     QMetaObject::invokeMethod(this, "_q_sendMessages", Qt::QueuedConnection,
576                               Q_ARG(QList<QByteArray>, prefixedMessages));
577 }
578
579 bool QQmlDebugServer::waitForMessage(QQmlDebugService *service)
580 {
581     // to be executed in GUI thread
582     Q_ASSERT(QThread::currentThread() == QCoreApplication::instance()->thread());
583
584     Q_D(QQmlDebugServer);
585     QReadLocker lock(&d->pluginsLock);
586
587     if (!service
588             || !d->plugins.contains(service->name()))
589         return false;
590
591     d->messageArrivedMutex.lock();
592     d->waitingForMessageNames << service->name();
593     do {
594         d->messageArrivedCondition.wait(&d->messageArrivedMutex);
595     } while (d->waitingForMessageNames.contains(service->name()));
596     d->messageArrivedMutex.unlock();
597     return true;
598 }
599
600 QT_END_NAMESPACE
601
602 #include "moc_qqmldebugserver_p.cpp"