summaryrefslogtreecommitdiff
path: root/src/qtmetaobjectpublisher.cpp
diff options
context:
space:
mode:
authorMilian Wolff <milian.wolff@kdab.com>2013-11-21 13:11:03 +0100
committerMilian Wolff <milian.wolff@kdab.com>2013-12-11 13:08:40 +0100
commitacf7f0b1ae956f2fac7182c194e0441cd9c6f4d0 (patch)
tree21420faece9a6c1eca68727c916660c9a286c912 /src/qtmetaobjectpublisher.cpp
parentb6158dc3525c1c906d4040b2e88cd20feb21a2b2 (diff)
downloadqtwebchannel-acf7f0b1ae956f2fac7182c194e0441cd9c6f4d0.tar.gz
Port the MetaObjectPublisher to C++.
This will allow us to create a stand-alone WebChannel C++ library, without any QML dependencies, that can be used to publisher QObjects to remote clients running in any browser engine supporting WebSockets. The patch is large, as working with introspection through the QMetaObject from C++ is more complicated compared to QML. On the other hand, the move to C++ allows a much more performant implementation of quite some parts of the publisher. One thing is that signal and method invocations can be handled via numeric indices, where before we needed to transmit the string identifier of the signal or method. Eventually this can now also be applied to properties, further decreasing the size of messages between server and clients. Note that this patch contains quite some TODOs and rough edges, such as the invokable bench_* helper functions in the public API of the MetaObjectPublisher. These are to be seen as temporary, and will be cleaned up in followup commits later. This is done to prevent a further blow-up of this already big patch. Change-Id: I57e788d8a19edd410651611382d912f9ad6660c9 Reviewed-by: Simon Hausmann <simon.hausmann@digia.com>
Diffstat (limited to 'src/qtmetaobjectpublisher.cpp')
-rw-r--r--src/qtmetaobjectpublisher.cpp629
1 files changed, 574 insertions, 55 deletions
diff --git a/src/qtmetaobjectpublisher.cpp b/src/qtmetaobjectpublisher.cpp
index 511f514..9ca0d93 100644
--- a/src/qtmetaobjectpublisher.cpp
+++ b/src/qtmetaobjectpublisher.cpp
@@ -43,24 +43,433 @@
****************************************************************************/
#include "qtmetaobjectpublisher.h"
+#include "qwebchannel.h"
+#include "variantargument.h"
+#include "signalhandler.h"
#include <QStringList>
#include <QMetaObject>
-#include <QMetaProperty>
+#include <QJsonObject>
+#include <QJsonArray>
+#include <QBasicTimer>
#include <QDebug>
+#include <QPointer>
+#include <QEvent>
-static const QString KEY_SIGNALS = QStringLiteral("signals");
-static const QString KEY_METHODS = QStringLiteral("methods");
-static const QString KEY_PROPERTIES = QStringLiteral("properties");
-static const QString KEY_ENUMS = QStringLiteral("enums");
+namespace {
+const QString KEY_SIGNALS = QStringLiteral("signals");
+const QString KEY_METHODS = QStringLiteral("methods");
+const QString KEY_PROPERTIES = QStringLiteral("properties");
+const QString KEY_ENUMS = QStringLiteral("enums");
+const QString KEY_QOBJECT = QStringLiteral("__QObject*__");
+const QString KEY_ID = QStringLiteral("id");
+const QString KEY_DATA = QStringLiteral("data");
+const QString KEY_OBJECT = QStringLiteral("object");
+const QString KEY_DESTROYED = QStringLiteral("destroyed");
+const QString KEY_SIGNAL = QStringLiteral("signal");
+const QString KEY_TYPE = QStringLiteral("type");
+const QString KEY_MESSAGE = QStringLiteral("message");
+const QString KEY_METHOD = QStringLiteral("method");
+const QString KEY_ARGS = QStringLiteral("args");
+const QString KEY_PROPERTY = QStringLiteral("property");
+const QString KEY_VALUE = QStringLiteral("value");
-static const QString KEY_QOBJECT = QStringLiteral("__QObject*__");
-static const QString KEY_ID = QStringLiteral("id");
-static const QString KEY_DATA = QStringLiteral("data");
+const QString TYPE_SIGNAL = QStringLiteral("Qt.signal");
+const QString TYPE_PROPERTY_UPDATE = QStringLiteral("Qt.propertyUpdate");
+const QString TYPE_INIT = QStringLiteral("Qt.init");
+const QString TYPE_IDLE = QStringLiteral("Qt.idle");
+const QString TYPE_DEBUG = QStringLiteral("Qt.debug");
+const QString TYPE_INVOKE_METHOD = QStringLiteral("Qt.invokeMethod");
+const QString TYPE_CONNECT_TO_SIGNAL = QStringLiteral("Qt.connectToSignal");
+const QString TYPE_DISCONNECT_FROM_SIGNAL = QStringLiteral("Qt.disconnectFromSignal");
+const QString TYPE_SET_PROPERTY = QStringLiteral("Qt.setProperty");
-QtMetaObjectPublisher::QtMetaObjectPublisher(QQuickItem *parent)
- : QQuickItem(parent)
+QString objectId(const QObject *object)
{
+ return QString::number(quintptr(object), 16);
+}
+
+const int s_destroyedSignalIndex = QObject::staticMetaObject.indexOfMethod("destroyed(QObject*)");
+
+/// TODO: what is the proper value here?
+const int PROPERTY_UPDATE_INTERVAL = 50;
+}
+
+struct QtMetaObjectPublisherPrivate
+{
+ QtMetaObjectPublisherPrivate(QtMetaObjectPublisher *q)
+ : q(q)
+ , signalHandler(this)
+ , clientIsIdle(false)
+ , blockUpdates(false)
+ , pendingInit(false)
+ , propertyUpdatesInitialized(false)
+ {
+ }
+
+ /**
+ * Set the client to idle or busy, based on the value of @p isIdle.
+ *
+ * When the value changed, start/stop the property update timer accordingly.
+ */
+ void setClientIsIdle(bool isIdle);
+
+ /**
+ * Initialize clients by sending them the class information of the registered objects.
+ *
+ * Furthermore, if that was not done already, connect to their property notify signals.
+ */
+ void initializeClients();
+
+ /**
+ * Go through all properties of the given object and connect to their notify signal.
+ *
+ * When receiving a notify signal, it will store the information in pendingPropertyUpdates which
+ * gets send via a Qt.propertyUpdate message to the server when the grouping timer timeouts.
+ */
+ void initializePropertyUpdates(const QObject *const object, const QVariantMap &objectInfo);
+
+ /**
+ * Send the clients the new property values since the last time this function was invoked.
+ *
+ * This is a grouped batch of all properties for which their notify signal was emitted.
+ * The list of signals as well as the arguments they contained, are also transmitted to
+ * the remote clients.
+ *
+ * @sa timer, initializePropertyUpdates
+ */
+ void sendPendingPropertyUpdates();
+
+ /**
+ * Invoke the method of index @p methodIndex on @p object with the arguments @p args.
+ *
+ * The return value of the method invocation is then transmitted to the calling client
+ * via a webchannel response to the message identified by @p id.
+ */
+ bool invokeMethod(QObject *const object, const int methodIndex, const QJsonArray &args, const QJsonValue &id);
+
+ /**
+ * Callback of the signalHandler which forwards the signal invocation to the webchannel clients.
+ */
+ void signalEmitted(const QObject *object, const int signalIndex, const QVariantList &arguments);
+
+ /**
+ * Callback for registered or wrapped objects which erases all data related to @p object.
+ *
+ * @sa signalEmitted
+ */
+ void objectDestroyed(const QObject *object);
+
+ /**
+ * Given a QVariant containing a QObject*, wrap the object and register for property updates
+ * return the objects class information.
+ *
+ * All other input types are returned as-is.
+ *
+ * TODO: support wrapping of initially-registered objects
+ */
+ QVariant wrapResult(const QVariant &result);
+
+ /**
+ * Invoke delete later on @p object.
+ */
+ void deleteWrappedObject(QObject *object) const;
+
+ QtMetaObjectPublisher *q;
+ QPointer<QWebChannel> webChannel;
+ SignalHandler<QtMetaObjectPublisherPrivate> signalHandler;
+
+ // true when the client is idle, false otherwise
+ bool clientIsIdle;
+
+ // true when no property updates should be sent, false otherwise
+ bool blockUpdates;
+
+ // true when at least one client needs to be initialized,
+ // i.e. when a Qt.init came in which was not handled yet.
+ bool pendingInit;
+
+ // true when at least one client was initialized and thus
+ // the property updates have been initialized and the
+ // object info map set.
+ bool propertyUpdatesInitialized;
+
+ // Map of registered objects indexed by their id.
+ QHash<QString, QObject *> registeredObjects;
+
+ // Map the registered objects to their id.
+ QHash<const QObject *, QString> registeredObjectIds;
+
+ // Map of object names to maps of signal indices to a set of all their properties.
+ // The last value is a set as a signal can be the notify signal of multiple properties.
+ typedef QHash<int, QSet<QString> > SignalToPropertyNameMap;
+ QHash<const QObject *, SignalToPropertyNameMap> signalToPropertyMap;
+
+ // Objects that changed their properties and are waiting for idle client.
+ // map of object name to map of signal index to arguments
+ typedef QHash<int, QVariantList> SignalToArgumentsMap;
+ typedef QHash<const QObject *, SignalToArgumentsMap> PendingPropertyUpdates;
+ PendingPropertyUpdates pendingPropertyUpdates;
+
+ // Maps wrapped object to class info
+ QHash<const QObject *, QVariantMap> wrappedObjects;
+
+ // Aggregate property updates since we get multiple Qt.idle message when we have multiple
+ // clients. They all share the same QWebProcess though so we must take special care to
+ // prevent message flooding.
+ QBasicTimer timer;
+};
+
+void QtMetaObjectPublisherPrivate::setClientIsIdle(bool isIdle)
+{
+ if (clientIsIdle == isIdle) {
+ return;
+ }
+ clientIsIdle = isIdle;
+ if (!isIdle && timer.isActive()) {
+ timer.stop();
+ } else if (isIdle && !timer.isActive()) {
+ timer.start(PROPERTY_UPDATE_INTERVAL, q);
+ }
+}
+
+void QtMetaObjectPublisherPrivate::initializeClients()
+{
+ QJsonObject objectInfos;
+ {
+ const QHash<QString, QObject *>::const_iterator end = registeredObjects.constEnd();
+ for (QHash<QString, QObject *>::const_iterator it = registeredObjects.constBegin(); it != end; ++it) {
+ const QVariantMap &info = q->classInfoForObject(it.value());
+ if (!propertyUpdatesInitialized) {
+ initializePropertyUpdates(it.value(), info);
+ }
+ objectInfos[it.key()] = QJsonObject::fromVariantMap(info);
+ }
+ }
+ webChannel->sendMessage(TYPE_INIT, objectInfos);
+ propertyUpdatesInitialized = true;
+ pendingInit = false;
+}
+
+void QtMetaObjectPublisherPrivate::initializePropertyUpdates(const QObject *const object, const QVariantMap &objectInfo)
+{
+ foreach (const QVariant &propertyInfoVar, objectInfo[KEY_PROPERTIES].toList()) {
+ const QVariantList &propertyInfo = propertyInfoVar.toList();
+ if (propertyInfo.size() < 2) {
+ qWarning() << "Invalid property info encountered:" << propertyInfoVar;
+ continue;
+ }
+ const QString &propertyName = propertyInfo.at(0).toString();
+ const QVariantList &signalData = propertyInfo.at(1).toList();
+
+ if (signalData.isEmpty()) {
+ // Property without NOTIFY signal
+ continue;
+ }
+
+ const int signalIndex = signalData.at(1).toInt();
+
+ QSet<QString> &connectedProperties = signalToPropertyMap[object][signalIndex];
+
+ // Only connect for a property update once
+ if (connectedProperties.isEmpty()) {
+ signalHandler.connectTo(object, signalIndex);
+ }
+
+ connectedProperties.insert(propertyName);
+ }
+
+ // also always connect to destroyed signal
+ signalHandler.connectTo(object, s_destroyedSignalIndex);
+}
+
+void QtMetaObjectPublisherPrivate::sendPendingPropertyUpdates()
+{
+ if (blockUpdates || !clientIsIdle || pendingPropertyUpdates.isEmpty()) {
+ return;
+ }
+
+ QJsonArray data;
+
+ // convert pending property updates to JSON data
+ const PendingPropertyUpdates::const_iterator end = pendingPropertyUpdates.constEnd();
+ for (PendingPropertyUpdates::const_iterator it = pendingPropertyUpdates.constBegin(); it != end; ++it) {
+ const QObject *object = it.key();
+ const QMetaObject *const metaObject = object->metaObject();
+ const SignalToPropertyNameMap &objectsSignalToPropertyMap = signalToPropertyMap.value(object);
+ // maps property name to current property value
+ QJsonObject properties;
+ // maps signal index to list of arguments of the last emit
+ QJsonObject sigs;
+ const SignalToArgumentsMap::const_iterator sigEnd = it.value().constEnd();
+ for (SignalToArgumentsMap::const_iterator sigIt = it.value().constBegin(); sigIt != sigEnd; ++sigIt) {
+ // TODO: use property indices
+ foreach (const QString &propertyName, objectsSignalToPropertyMap.value(sigIt.key())) {
+ int propertyIndex = metaObject->indexOfProperty(qPrintable(propertyName));
+ if (propertyIndex == -1) {
+ qWarning("Unknown property %d encountered", propertyIndex);
+ continue;
+ }
+ const QMetaProperty &property = metaObject->property(propertyIndex);
+ properties[QString::fromLatin1(property.name())] = QJsonValue::fromVariant(property.read(object));
+ }
+ // TODO: can we get rid of the int <-> string conversions here?
+ sigs[QString::number(sigIt.key())] = QJsonArray::fromVariantList(sigIt.value());
+ }
+ QJsonObject obj;
+ obj[KEY_OBJECT] = registeredObjectIds.value(object);
+ obj[KEY_SIGNALS] = sigs;
+ obj[KEY_PROPERTIES] = properties;
+ data.push_back(obj);
+ }
+
+ pendingPropertyUpdates.clear();
+ webChannel->sendMessage(TYPE_PROPERTY_UPDATE, data);
+ setClientIsIdle(false);
+}
+
+bool QtMetaObjectPublisherPrivate::invokeMethod(QObject *const object, const int methodIndex,
+ const QJsonArray &args, const QJsonValue &id)
+{
+ const QMetaMethod &method = object->metaObject()->method(methodIndex);
+
+ if (method.name() == QByteArrayLiteral("deleteLater")) {
+ // invoke `deleteLater` on wrapped QObject indirectly
+ deleteWrappedObject(object);
+ return true;
+ } else if (!method.isValid()) {
+ qWarning() << "Cannot invoke unknown method of index" << methodIndex << "on object" << object << '.';
+ return false;
+ } else if (method.access() != QMetaMethod::Public) {
+ qWarning() << "Cannot invoke non-public method" << method.name() << "on object" << object << '.';
+ return false;
+ } else if (method.methodType() != QMetaMethod::Method && method.methodType() != QMetaMethod::Slot) {
+ qWarning() << "Cannot invoke non-public method" << method.name() << "on object" << object << '.';
+ return false;
+ } else if (args.size() > 10) {
+ qWarning() << "Cannot invoke method" << method.name() << "on object" << object << "with more than 10 arguments, as that is not supported by QMetaMethod::invoke.";
+ return false;
+ } else if (args.size() > method.parameterCount()) {
+ qWarning() << "Ignoring additional arguments while invoking method" << method.name() << "on object" << object << ':'
+ << args.size() << "arguments given, but method only takes" << method.parameterCount() << '.';
+ }
+
+ // construct converter objects of QVariant to QGenericArgument
+ VariantArgument arguments[10];
+ for (int i = 0; i < qMin(args.size(), method.parameterCount()); ++i) {
+ arguments[i].setValue(args.at(i).toVariant(), method.parameterType(i));
+ }
+
+ // construct QGenericReturnArgument
+ QVariant returnValue;
+ if (method.returnType() != qMetaTypeId<QVariant>() && method.returnType() != qMetaTypeId<void>()) {
+ // Only init variant with return type if its not a variant itself, which would
+ // lead to nested variants which is not what we want.
+ // Also, skip void-return types for obvious reasons (and to prevent a runtime warning inside Qt).
+ returnValue = QVariant(method.returnType(), 0);
+ }
+ QGenericReturnArgument returnArgument(method.typeName(), returnValue.data());
+
+ // now we can call the method
+ method.invoke(object, returnArgument,
+ arguments[0], arguments[1], arguments[2], arguments[3], arguments[4],
+ arguments[5], arguments[6], arguments[7], arguments[8], arguments[9]);
+
+ // and send the return value to the client
+ webChannel->respond(id, QJsonValue::fromVariant(wrapResult(returnValue)));
+
+ return true;
+}
+
+void QtMetaObjectPublisherPrivate::signalEmitted(const QObject *object, const int signalIndex, const QVariantList &arguments)
+{
+ if (!webChannel) {
+ return;
+ }
+ if (!signalToPropertyMap.value(object).contains(signalIndex)) {
+ QJsonObject data;
+ const QString &objectName = registeredObjectIds.value(object);
+ Q_ASSERT(!objectName.isEmpty());
+ data[KEY_OBJECT] = objectName;
+ data[KEY_SIGNAL] = signalIndex;
+ if (!arguments.isEmpty()) {
+ // TODO: wrap (new) objects on the fly
+ data[KEY_ARGS] = QJsonArray::fromVariantList(arguments);
+ }
+ webChannel->sendMessage(TYPE_SIGNAL, data);
+
+ if (signalIndex == s_destroyedSignalIndex) {
+ objectDestroyed(object);
+ }
+ } else {
+ pendingPropertyUpdates[object][signalIndex] = arguments;
+ if (clientIsIdle && !blockUpdates && !timer.isActive()) {
+ timer.start(PROPERTY_UPDATE_INTERVAL, q);
+ }
+ }
+}
+
+void QtMetaObjectPublisherPrivate::objectDestroyed(const QObject *object)
+{
+ const QString &id = registeredObjectIds.take(object);
+ Q_ASSERT(!id.isEmpty());
+ bool removed = registeredObjects.remove(id);
+ Q_ASSERT(removed);
+ Q_UNUSED(removed);
+
+ signalToPropertyMap.remove(object);
+ pendingPropertyUpdates.remove(object);
+ wrappedObjects.remove(object);
+}
+
+QVariant QtMetaObjectPublisherPrivate::wrapResult(const QVariant &result)
+{
+ if (QObject *object = result.value<QObject *>()) {
+ QVariantMap &objectInfo = wrappedObjects[object];
+ if (!objectInfo.isEmpty()) {
+ // already registered, use cached information
+ Q_ASSERT(registeredObjectIds.contains(object));
+ return objectInfo;
+ } // else the object is not yet wrapped, do it now
+
+ const QString &id = objectId(object);
+ Q_ASSERT(!registeredObjects.contains(id));
+ Q_ASSERT(!registeredObjectIds.contains(object));
+
+ objectInfo[KEY_QOBJECT] = true;
+ objectInfo[KEY_ID] = id;
+ objectInfo[KEY_DATA] = q->classInfoForObject(object);
+
+ registeredObjectIds[object] = id;
+ registeredObjects[id] = object;
+ wrappedObjects.insert(object, objectInfo);
+
+ initializePropertyUpdates(object, objectInfo);
+ return objectInfo;
+ }
+
+ // no need to wrap this
+ return result;
+}
+
+void QtMetaObjectPublisherPrivate::deleteWrappedObject(QObject *object) const
+{
+ if (!wrappedObjects.contains(object)) {
+ qWarning() << "Not deleting non-wrapped object" << object;
+ return;
+ }
+ object->deleteLater();
+}
+
+QtMetaObjectPublisher::QtMetaObjectPublisher(QObject *parent)
+ : QObject(parent)
+ , d(new QtMetaObjectPublisherPrivate(this))
+{
+}
+
+QtMetaObjectPublisher::~QtMetaObjectPublisher()
+{
+
}
QVariantMap QtMetaObjectPublisher::classInfoForObjects(const QVariantMap &objectMap) const
@@ -68,7 +477,7 @@ QVariantMap QtMetaObjectPublisher::classInfoForObjects(const QVariantMap &object
QVariantMap ret;
QMap<QString, QVariant>::const_iterator it = objectMap.constBegin();
while (it != objectMap.constEnd()) {
- QObject* object = it.value().value<QObject*>();
+ QObject *object = it.value().value<QObject *>();
if (object) {
const QVariantMap &info = classInfoForObject(object);
if (!info.isEmpty()) {
@@ -90,15 +499,15 @@ QVariantMap QtMetaObjectPublisher::classInfoForObject(QObject *object) const
QVariantList qtSignals, qtMethods;
QVariantList qtProperties;
QVariantMap qtEnums;
- const QMetaObject* metaObject = object->metaObject();
+ const QMetaObject *metaObject = object->metaObject();
QSet<int> notifySignals;
- QSet<QString> properties;
+ QSet<QString> identifiers;
for (int i = 0; i < metaObject->propertyCount(); ++i) {
const QMetaProperty &prop = metaObject->property(i);
QVariantList propertyInfo;
const QString &propertyName = QString::fromLatin1(prop.name());
propertyInfo.append(propertyName);
- properties << propertyName;
+ identifiers << propertyName;
if (prop.hasNotifySignal()) {
notifySignals << prop.notifySignalIndex();
const int numParams = prop.notifySignal().parameterCount();
@@ -112,9 +521,9 @@ QVariantMap QtMetaObjectPublisher::classInfoForObject(QObject *object) const
if (notifySignal.length() == changedSuffix.length() + propertyName.length() &&
notifySignal.endsWith(changedSuffix) && notifySignal.startsWith(prop.name()))
{
- propertyInfo.append(1);
+ propertyInfo.append(QVariant::fromValue(QVariantList() << 1 << prop.notifySignalIndex()));
} else {
- propertyInfo.append(QString::fromLatin1(notifySignal));
+ propertyInfo.append(QVariant::fromValue(QVariantList() << QString::fromLatin1(notifySignal) << prop.notifySignalIndex()));
}
} else {
if (!prop.isConstant()) {
@@ -122,7 +531,7 @@ QVariantMap QtMetaObjectPublisher::classInfoForObject(QObject *object) const
"value updates in HTML will be broken!",
prop.name(), object->metaObject()->className());
}
- propertyInfo.append(0);
+ propertyInfo.append(QVariant::fromValue(QVariantList()));
}
propertyInfo.append(prop.read(object));
qtProperties.append(QVariant::fromValue(propertyInfo));
@@ -132,18 +541,22 @@ QVariantMap QtMetaObjectPublisher::classInfoForObject(QObject *object) const
continue;
}
const QMetaMethod &method = metaObject->method(i);
- //NOTE: This will not work for overloaded methods/signals.
//NOTE: this must be a string, otherwise it will be converted to '{}' in QML
const QString &name = QString::fromLatin1(method.name());
- if (properties.contains(name)) {
- // optimize: Don't send the getter method, it gets overwritten by the
- // property on the client side anyways.
+ // optimize: skip overloaded methods/signals or property getters, on the JS side we can only
+ // call one of them anyways
+ // TODO: basic support for overloaded signals, methods
+ if (identifiers.contains(name)) {
continue;
}
- if (method.methodType() == QMetaMethod::Signal)
- qtSignals << name;
- else if (method.access() == QMetaMethod::Public)
- qtMethods << name;
+ identifiers << name;
+ // send data as array to client with format: [name, index]
+ const QVariant data = QVariant::fromValue(QVariantList() << name << i);
+ if (method.methodType() == QMetaMethod::Signal) {
+ qtSignals.append(data);
+ } else if (method.access() == QMetaMethod::Public) {
+ qtMethods.append(data);
+ }
}
for (int i = 0; i < metaObject->enumeratorCount(); ++i) {
QMetaEnum enumerator = metaObject->enumerator(i);
@@ -160,53 +573,159 @@ QVariantMap QtMetaObjectPublisher::classInfoForObject(QObject *object) const
return data;
}
-static QString objectId(QObject *object)
+void QtMetaObjectPublisher::registerObjects(const QVariantMap &objects)
{
- return QString::number(quintptr(object), 16);
+ if (d->propertyUpdatesInitialized) {
+ qWarning("Registered new object after initialization. This does not work!");
+ return;
+ }
+ const QMap<QString, QVariant>::const_iterator end = objects.end();
+ for (QMap<QString, QVariant>::const_iterator it = objects.begin(); it != end; ++it) {
+ QObject *object = it.value().value<QObject *>();
+ if (!object) {
+ qWarning("Invalid QObject given to register under name %s", qPrintable(it.key()));
+ continue;
+ }
+ d->registeredObjects[it.key()] = object;
+ d->registeredObjectIds[object] = it.key();
+ }
}
-QVariant QtMetaObjectPublisher::wrapObject(QObject *object)
+bool QtMetaObjectPublisher::handleRequest(const QJsonObject &message)
{
- if (!object)
- return QVariant();
-
- const QString& id = objectId(object);
+ if (!message.contains(KEY_DATA)) {
+ return false;
+ }
- const WrapMapCIt& p = m_wrappedObjects.constFind(id);
- if (p != m_wrappedObjects.constEnd())
- return p.value().second;
+ const QJsonObject &payload = message.value(KEY_DATA).toObject();
+ if (!payload.contains(KEY_TYPE)) {
+ return false;
+ }
- QVariantMap objectInfo;
- objectInfo[KEY_QOBJECT] = true;
- objectInfo[KEY_ID] = id;
- objectInfo[KEY_DATA] = classInfoForObject(object);
+ const QString &type = payload.value(KEY_TYPE).toString();
+ if (type == TYPE_IDLE) {
+ d->setClientIsIdle(true);
+ return true;
+ } else if (type == TYPE_INIT) {
+ if (!d->blockUpdates) {
+ d->initializeClients();
+ } else {
+ d->pendingInit = true;
+ }
+ return true;
+ } else if (type == TYPE_DEBUG) {
+ static QTextStream out(stdout);
+ out << "DEBUG: " << payload.value(KEY_MESSAGE).toString() << endl;
+ return true;
+ } else if (payload.contains(KEY_OBJECT)) {
+ const QString &objectName = payload.value(KEY_OBJECT).toString();
+ QObject *object = d->registeredObjects.value(objectName);
+ if (!object) {
+ qWarning() << "Unknown object encountered" << objectName;
+ return false;
+ }
- m_wrappedObjects.insert(id, WrapInfo(object, objectInfo));
- connect(object, SIGNAL(destroyed(QObject*)), SLOT(wrappedObjectDestroyed(QObject*)));
+ if (type == TYPE_INVOKE_METHOD) {
+ return d->invokeMethod(object, payload.value(KEY_METHOD).toInt(-1), payload.value(KEY_ARGS).toArray(), message.value(KEY_ID));
+ } else if (type == TYPE_CONNECT_TO_SIGNAL) {
+ d->signalHandler.connectTo(object, payload.value(KEY_SIGNAL).toInt(-1));
+ return true;
+ } else if (type == TYPE_DISCONNECT_FROM_SIGNAL) {
+ d->signalHandler.disconnectFrom(object, payload.value(KEY_SIGNAL).toInt(-1));
+ return true;
+ } else if (type == TYPE_SET_PROPERTY) {
+ // TODO: use property indices
+ const QString &propertyName = payload.value(KEY_PROPERTY).toString();
+ const int propertyIdx = object->metaObject()->indexOfProperty(qPrintable(propertyName));
+ if (propertyIdx == -1) {
+ qWarning() << "Cannot set unknown property" << propertyName << "of object" << objectName;
+ return false;
+ }
+ object->metaObject()->property(propertyIdx).write(object, payload.value(KEY_VALUE).toVariant());
+ return true;
+ }
+ }
+ return false;
+}
- return objectInfo;
+QWebChannel *QtMetaObjectPublisher::webChannel() const
+{
+ return d->webChannel;
}
-QObject *QtMetaObjectPublisher::unwrapObject(const QString& id) const
+void QtMetaObjectPublisher::setWebChannel(QWebChannel *webChannel)
{
- const WrapMapCIt& p = m_wrappedObjects.constFind(id);
- if (p != m_wrappedObjects.constEnd())
- return p.value().first;
- return 0;
+ if (d->webChannel == webChannel) {
+ return;
+ }
+
+ d->webChannel = webChannel;
+
+ emit webChannelChanged(webChannel);
}
-void QtMetaObjectPublisher::wrappedObjectDestroyed(QObject* object)
+bool QtMetaObjectPublisher::blockUpdates() const
{
- const QString& id = objectId(object);
- m_wrappedObjects.remove(id);
- emit wrappedObjectDestroyed(id);
+ return d->blockUpdates;
}
-void QtMetaObjectPublisher::deleteWrappedObject(QObject* object) const
+void QtMetaObjectPublisher::setBlockUpdates(bool block)
{
- if (!m_wrappedObjects.contains(objectId(object))) {
- qWarning() << "Not deleting non-wrapped object" << object;
+ if (d->blockUpdates == block) {
return;
}
- object->deleteLater();
+ d->blockUpdates = block;
+
+ if (!d->blockUpdates) {
+ if (d->pendingInit) {
+ d->initializeClients();
+ } else {
+ d->sendPendingPropertyUpdates();
+ }
+ } else if (d->timer.isActive()) {
+ d->timer.stop();
+ }
+
+ emit blockUpdatesChanged(block);
+}
+
+void QtMetaObjectPublisher::bench_ensureUpdatesInitialized()
+{
+ if (!d->propertyUpdatesInitialized) {
+ d->initializeClients();
+ }
+}
+
+void QtMetaObjectPublisher::bench_sendPendingPropertyUpdates()
+{
+ d->clientIsIdle = true;
+ d->sendPendingPropertyUpdates();
+}
+
+void QtMetaObjectPublisher::bench_initializeClients()
+{
+ d->propertyUpdatesInitialized = false;
+ d->signalToPropertyMap.clear();
+ d->signalHandler.clear();
+ d->initializeClients();
+}
+
+void QtMetaObjectPublisher::bench_registerObjects(const QVariantMap &objects)
+{
+ d->propertyUpdatesInitialized = false;
+ registerObjects(objects);
+}
+
+bool QtMetaObjectPublisher::test_clientIsIdle() const
+{
+ return d->clientIsIdle;
+}
+
+void QtMetaObjectPublisher::timerEvent(QTimerEvent *event)
+{
+ if (event->timerId() == d->timer.timerId()) {
+ d->sendPendingPropertyUpdates();
+ } else {
+ QObject::timerEvent(event);
+ }
}