1/****************************************************************************
2**
3** Copyright (C) 2017 Ford Motor Company
4** Contact: https://www.qt.io/licensing/
5**
6** This file is part of the QtRemoteObjects module of the Qt Toolkit.
7**
8** $QT_BEGIN_LICENSE:LGPL$
9** Commercial License Usage
10** Licensees holding valid commercial Qt licenses may use this file in
11** accordance with the commercial license agreement provided with the
12** Software or, alternatively, in accordance with the terms contained in
13** a written agreement between you and The Qt Company. For licensing terms
14** and conditions see https://www.qt.io/terms-conditions. For further
15** information use the contact form at https://www.qt.io/contact-us.
16**
17** GNU Lesser General Public License Usage
18** Alternatively, this file may be used under the terms of the GNU Lesser
19** General Public License version 3 as published by the Free Software
20** Foundation and appearing in the file LICENSE.LGPL3 included in the
21** packaging of this file. Please review the following information to
22** ensure the GNU Lesser General Public License version 3 requirements
23** will be met: https://www.gnu.org/licenses/lgpl-3.0.html.
24**
25** GNU General Public License Usage
26** Alternatively, this file may be used under the terms of the GNU
27** General Public License version 2.0 or (at your option) the GNU General
28** Public license version 3 or any later version approved by the KDE Free
29** Qt Foundation. The licenses are as published by the Free Software
30** Foundation and appearing in the file LICENSE.GPL2 and LICENSE.GPL3
31** included in the packaging of this file. Please review the following
32** information to ensure the GNU General Public License requirements will
33** be met: https://www.gnu.org/licenses/gpl-2.0.html and
34** https://www.gnu.org/licenses/gpl-3.0.html.
35**
36** $QT_END_LICENSE$
37**
38****************************************************************************/
39
40#include "qremoteobjectreplica.h"
41#include "qremoteobjectreplica_p.h"
42
43#include "qremoteobjectnode.h"
44#include "qremoteobjectdynamicreplica.h"
45#include "qremoteobjectpacket_p.h"
46#include "qremoteobjectpendingcall_p.h"
47#include "qconnectionfactories_p.h"
48#include "qremoteobjectsource_p.h"
49
50#include <QtCore/qcoreapplication.h>
51#include <QtCore/qdatastream.h>
52#include <QtCore/qelapsedtimer.h>
53#include <QtCore/qvariant.h>
54#include <QtCore/qthread.h>
55
56#include <limits>
57
58QT_BEGIN_NAMESPACE
59
60using namespace QRemoteObjectPackets;
61
62QT_WARNING_PUSH
63QT_WARNING_DISABLE_GCC("-Wtautological-compare")
64
65#if !defined(Q_OS_WIN) && !defined(Q_OS_INTEGRITY)
66Q_STATIC_ASSERT_X(&QRemoteObjectReplica::staticMetaObject == &QRemoteObjectDynamicReplica::staticMetaObject,
67 "m_signalOffset initializer expects QRemoteObjectDynamicReplica to not have a unique staticMetaObject");
68#endif
69
70QT_WARNING_POP
71
72// If QRemoteObjectDynamicReplica ever gets its own staticMetaObject, some commented out code will need to be
73// used. It was changed to avoid a Coverity complaint. We use the above static assert to detect if this changes
74// in the future. See FIX #1, #2, #3 in this file.
75
76QRemoteObjectReplicaImplementation::QRemoteObjectReplicaImplementation(const QString &name, const QMetaObject *meta, QRemoteObjectNode *_node)
77 : QObject(nullptr), m_objectName(name), m_metaObject(meta), m_numSignals(0), m_methodOffset(0)
78 // Uncomment the following two lines if QRemoteObjectDynamicReplica gets a unique staticMetaObject (FIX #1, #2)
79 //, m_signalOffset(meta ? QRemoteObjectReplica::staticMetaObject.methodCount() : QRemoteObjectDynamicReplica::staticMetaObject.methodCount())
80 //, m_propertyOffset(meta ? QRemoteObjectReplica::staticMetaObject.propertyCount() : QRemoteObjectDynamicReplica::staticMetaObject.propertyCount())
81 , m_signalOffset(QRemoteObjectReplica::staticMetaObject.methodCount())
82 , m_propertyOffset(QRemoteObjectReplica::staticMetaObject.propertyCount())
83 , m_node(_node)
84 , m_objectSignature(QtPrivate::qtro_classinfo_signature(metaObject: m_metaObject))
85 , m_state(meta ? QRemoteObjectReplica::Default : QRemoteObjectReplica::Uninitialized)
86{
87}
88
89QRemoteObjectReplicaImplementation::~QRemoteObjectReplicaImplementation()
90{
91}
92
93QConnectedReplicaImplementation::QConnectedReplicaImplementation(const QString &name, const QMetaObject *meta, QRemoteObjectNode *node)
94 : QRemoteObjectReplicaImplementation(name, meta, node), connectionToSource(nullptr)
95{
96 m_heartbeatTimer.setTimerType(Qt::CoarseTimer);
97 m_heartbeatTimer.setSingleShot(true);
98 m_heartbeatTimer.setInterval(node->heartbeatInterval());
99
100 connect(sender: node, signal: &QRemoteObjectNode::heartbeatIntervalChanged, context: this, slot: [this](int interval) {
101 m_heartbeatTimer.stop();
102 m_heartbeatTimer.setInterval(interval);
103 if (interval)
104 m_heartbeatTimer.start();
105 });
106 connect(sender: &m_heartbeatTimer, signal: &QTimer::timeout, context: this, slot: [this] {
107 // TODO: Revisit if a baseclass method can be used to avoid specialized cast
108 // conditional logic.
109 auto clientIo = qobject_cast<ClientIoDevice *>(object: connectionToSource);
110 if (m_pendingCalls.contains(akey: 0)) {
111 m_pendingCalls.take(akey: 0);
112 // The source didn't respond in time, disconnect the connection
113 if (clientIo)
114 clientIo->disconnectFromServer();
115 else if (connectionToSource)
116 connectionToSource->close();
117 } else {
118 serializePingPacket(ds&: m_packet, name: m_objectName);
119 if (sendCommandWithReply(serialId: 0).d->serialId == -1) {
120 m_heartbeatTimer.stop();
121 if (clientIo)
122 clientIo->disconnectFromServer();
123 else if (connectionToSource)
124 connectionToSource->close();
125 }
126 }
127 });
128
129 if (!meta)
130 return;
131
132 auto offsetMeta = m_metaObject;
133 QtRemoteObjects::getTypeNameAndMetaobjectFromClassInfo(meta&: offsetMeta);
134 for (int index = offsetMeta->propertyOffset(); index < offsetMeta->propertyCount(); ++index) {
135 const QMetaProperty property = offsetMeta->property(index);
136 if (QMetaType::typeFlags(type: property.userType()).testFlag(flag: QMetaType::PointerToQObject))
137 m_childIndices << index - offsetMeta->propertyOffset();
138 }
139}
140
141QConnectedReplicaImplementation::~QConnectedReplicaImplementation()
142{
143 if (!connectionToSource.isNull()) {
144 qCDebug(QT_REMOTEOBJECT) << "Replica deleted: sending RemoveObject to RemoteObjectSource" << m_objectName;
145 serializeRemoveObjectPacket(m_packet, name: m_objectName);
146 sendCommand();
147 }
148 for (auto prop : m_propertyStorage) {
149 if (prop.canConvert<QObject*>())
150 prop.value<QObject *>()->deleteLater();
151 }
152}
153
154bool QRemoteObjectReplicaImplementation::needsDynamicInitialization() const
155{
156 return m_metaObject == nullptr;
157}
158
159void QRemoteObjectReplicaImplementation::setState(QRemoteObjectReplica::State state)
160{
161 if (m_state.loadAcquire() != QRemoteObjectReplica::Suspect && m_state.loadAcquire() >= state)
162 return;
163
164 int oldState = m_state.loadAcquire();
165 m_state.storeRelease(newValue: state);
166
167 // We should emit initialized before emitting any changed signals in case connections are made in a
168 // Slot responding to initialized/validChanged.
169 if (m_state.loadAcquire() == QRemoteObjectReplica::Valid) {
170 // we're initialized now, emit signal
171 emitInitialized();
172 }
173
174 const static int stateChangedIndex = QRemoteObjectReplica::staticMetaObject.indexOfMethod(method: "stateChanged(State,State)");
175 Q_ASSERT(stateChangedIndex != -1);
176 void *args[] = {nullptr, &state, &oldState};
177 QMetaObject::activate(sender: this, metaObject(), local_signal_index: stateChangedIndex, argv: args);
178}
179
180void QRemoteObjectReplicaImplementation::emitNotified()
181{
182 const static int notifiedIndex = QRemoteObjectReplica::staticMetaObject.indexOfMethod(method: "notified()");
183 Q_ASSERT(notifiedIndex != -1);
184 void *args[] = {nullptr};
185 QMetaObject::activate(sender: this, metaObject(), local_signal_index: notifiedIndex, argv: args);
186}
187
188bool QConnectedReplicaImplementation::sendCommand()
189{
190 if (connectionToSource.isNull() || !connectionToSource->isOpen()) {
191 if (connectionToSource.isNull())
192 qCWarning(QT_REMOTEOBJECT) << "connectionToSource is null";
193 return false;
194 }
195
196 connectionToSource->write(data: m_packet.array, m_packet.size);
197 if (m_heartbeatTimer.interval())
198 m_heartbeatTimer.start();
199 return true;
200}
201
202QVector<int> QConnectedReplicaImplementation::childIndices() const
203{
204 return m_childIndices;
205}
206
207void QConnectedReplicaImplementation::initialize(QVariantList &values)
208{
209 qCDebug(QT_REMOTEOBJECT) << "initialize()" << m_propertyStorage.size();
210 const int nParam = values.size();
211 QVarLengthArray<int> changedProperties(nParam);
212 const int offset = m_propertyOffset;
213 for (int i = 0; i < nParam; ++i) {
214 qCDebug(QT_REMOTEOBJECT) << " in loop" << i << m_propertyStorage.size();
215 changedProperties[i] = -1;
216 if (m_propertyStorage[i] != values.at(i)) {
217 const QMetaProperty property = m_metaObject->property(index: i+offset);
218 m_propertyStorage[i] = QRemoteObjectPackets::decodeVariant(value&: values[i], type: property.userType());
219 changedProperties[i] = i;
220 }
221 qCDebug(QT_REMOTEOBJECT) << "SETPROPERTY" << i << m_metaObject->property(index: i+offset).name() << values.at(i).typeName() << values.at(i).toString();
222 }
223
224 Q_ASSERT(m_state.loadAcquire() < QRemoteObjectReplica::Valid || m_state.loadAcquire() == QRemoteObjectReplica::Suspect);
225 setState(QRemoteObjectReplica::Valid);
226
227 void *args[] = {nullptr, nullptr};
228 for (int i = 0; i < nParam; ++i) {
229 if (changedProperties[i] < 0)
230 continue;
231 const int notifyIndex = m_metaObject->property(index: changedProperties[i]+offset).notifySignalIndex();
232 if (notifyIndex < 0)
233 continue;
234 qCDebug(QT_REMOTEOBJECT) << " Before activate" << notifyIndex << m_metaObject->property(index: notifyIndex).name();
235 args[1] = m_propertyStorage[i].data();
236 QMetaObject::activate(sender: this, metaObject(), local_signal_index: notifyIndex, argv: args);
237 }
238 emitNotified();
239
240 qCDebug(QT_REMOTEOBJECT) << "isSet = true for" << m_objectName;
241 if (node()->heartbeatInterval())
242 m_heartbeatTimer.start();
243}
244
245void QRemoteObjectReplicaImplementation::emitInitialized()
246{
247 const static int initializedIndex = QRemoteObjectReplica::staticMetaObject.indexOfMethod(method: "initialized()");
248 Q_ASSERT(initializedIndex != -1);
249 void *noArgs[] = {nullptr};
250 QMetaObject::activate(sender: this, metaObject(), local_signal_index: initializedIndex, argv: noArgs);
251}
252
253/*!
254 \internal
255*/
256void QRemoteObjectReplica::persistProperties(const QString &repName, const QByteArray &repSig, const QVariantList &props) const
257{
258 if (!node()) {
259 qWarning(msg: "Tried calling persistProperties on a replica (%s) that hasn't been initialized with a node", qPrintable(repName));
260 return;
261 }
262 node()->persistProperties(repName, repSig, props);
263}
264
265/*!
266 \internal
267*/
268QVariantList QRemoteObjectReplica::retrieveProperties(const QString &repName, const QByteArray &repSig) const
269{
270 if (!node()) {
271 qWarning(msg: "Tried calling retrieveProperties on a replica (%s) that hasn't been initialized with a node", qPrintable(repName));
272 return QVariantList();
273 }
274 return node()->retrieveProperties(repName, repSig);
275}
276
277void QRemoteObjectReplicaImplementation::setDynamicMetaObject(const QMetaObject *meta)
278{
279 Q_ASSERT(!m_metaObject);
280
281 m_metaObject = meta;
282}
283
284void QConnectedReplicaImplementation::setDynamicMetaObject(const QMetaObject *meta)
285{
286 QRemoteObjectReplicaImplementation::setDynamicMetaObject(meta);
287
288 for (int index = m_metaObject->propertyOffset(); index < m_metaObject->propertyCount(); ++index) {
289 const QMetaProperty property = m_metaObject->property(index);
290 if (QMetaType::typeFlags(type: property.userType()).testFlag(flag: QMetaType::PointerToQObject))
291 m_childIndices << index - m_metaObject->propertyOffset();
292 }
293}
294
295void QRemoteObjectReplicaImplementation::setDynamicProperties(const QVariantList &values)
296{
297 //rely on order of properties;
298 setProperties(values);
299}
300
301void QConnectedReplicaImplementation::setDynamicProperties(const QVariantList &values)
302{
303 QRemoteObjectReplicaImplementation::setDynamicProperties(values);
304 for (QRemoteObjectReplica *obj : qExchange(t&: m_parentsNeedingConnect, newValue: {}))
305 configurePrivate(obj);
306
307 Q_ASSERT(m_state.loadAcquire() < QRemoteObjectReplica::Valid);
308 setState(QRemoteObjectReplica::Valid);
309
310 void *args[] = {nullptr, nullptr};
311 for (int index = m_metaObject->propertyOffset(); index < m_metaObject->propertyCount(); ++index) {
312 const QMetaProperty mp = m_metaObject->property(index);
313 if (mp.hasNotifySignal()) {
314 qCDebug(QT_REMOTEOBJECT) << " Before activate" << index << m_metaObject->property(index).name();
315 args[1] = this->m_propertyStorage[index-m_propertyOffset].data();
316 QMetaObject::activate(sender: this, metaObject(), local_signal_index: mp.notifySignalIndex(), argv: args);
317 }
318 }
319 emitNotified();
320
321 qCDebug(QT_REMOTEOBJECT) << "isSet = true for" << m_objectName;
322}
323
324bool QConnectedReplicaImplementation::isInitialized() const
325{
326 return m_state.loadAcquire() > QRemoteObjectReplica::Default && m_state.loadAcquire() != QRemoteObjectReplica::SignatureMismatch;
327}
328
329bool QConnectedReplicaImplementation::waitForSource(int timeout)
330{
331 switch (state()) {
332 case QRemoteObjectReplica::State::Valid:
333 return true;
334 case QRemoteObjectReplica::State::SignatureMismatch:
335 return false;
336 default:
337 break;
338 }
339
340 const static int stateChangedIndex = QRemoteObjectReplica::staticMetaObject.indexOfMethod(method: "stateChanged(State,State)");
341 Q_ASSERT(stateChangedIndex != -1);
342
343 QEventLoop loop;
344 QMetaObject::connect(sender: this, signal_index: stateChangedIndex,
345 receiver: &loop, method_index: QEventLoop::staticMetaObject.indexOfMethod(method: "quit()"),
346 type: Qt::DirectConnection, types: nullptr);
347
348 QTimer t; // NB: Related to QTBUG-94570 - don't use QTimer::singleShot here.
349 if (timeout >= 0) {
350 t.setSingleShot(true);
351 connect(sender: &t, signal: &QTimer::timeout, receiver: &loop, slot: &QEventLoop::quit);
352 t.start(msec: timeout);
353 }
354
355 // enter the event loop and wait for a reply
356 loop.exec(flags: QEventLoop::ExcludeUserInputEvents | QEventLoop::WaitForMoreEvents);
357
358 return state() == QRemoteObjectReplica::State::Valid;
359}
360
361void QConnectedReplicaImplementation::_q_send(QMetaObject::Call call, int index, const QVariantList &args)
362{
363 static const bool debugArgs = qEnvironmentVariableIsSet(varName: "QT_REMOTEOBJECT_DEBUG_ARGUMENTS");
364
365 Q_ASSERT(call == QMetaObject::InvokeMetaMethod || call == QMetaObject::WriteProperty);
366
367 if (call == QMetaObject::InvokeMetaMethod) {
368 if (debugArgs) {
369 qCDebug(QT_REMOTEOBJECT) << "Send" << call << this->m_metaObject->method(index).name() << index << args << connectionToSource;
370 } else {
371 qCDebug(QT_REMOTEOBJECT) << "Send" << call << this->m_metaObject->method(index).name() << index << connectionToSource;
372 }
373 if (index < m_methodOffset) //index - m_methodOffset < 0 is invalid, and can't be resolved on the Source side
374 qCWarning(QT_REMOTEOBJECT) << "Skipping invalid method invocation. Index not found:" << index << "( offset =" << m_methodOffset << ") object:" << m_objectName << this->m_metaObject->method(index).name();
375 else {
376 serializeInvokePacket(m_packet, name: m_objectName, call, index: index - m_methodOffset, args);
377 sendCommand();
378 }
379 } else {
380 qCDebug(QT_REMOTEOBJECT) << "Send" << call << this->m_metaObject->property(index).name() << index << args << connectionToSource;
381 if (index < m_propertyOffset) //index - m_propertyOffset < 0 is invalid, and can't be resolved on the Source side
382 qCWarning(QT_REMOTEOBJECT) << "Skipping invalid property invocation. Index not found:" << index << "( offset =" << m_propertyOffset << ") object:" << m_objectName << this->m_metaObject->property(index).name();
383 else {
384 serializeInvokePacket(m_packet, name: m_objectName, call, index: index - m_propertyOffset, args);
385 sendCommand();
386 }
387 }
388}
389
390QRemoteObjectPendingCall QConnectedReplicaImplementation::_q_sendWithReply(QMetaObject::Call call, int index, const QVariantList &args)
391{
392 Q_ASSERT(call == QMetaObject::InvokeMetaMethod);
393
394 qCDebug(QT_REMOTEOBJECT) << "Send" << call << this->m_metaObject->method(index).name() << index << args << connectionToSource;
395 int serialId = (m_curSerialId == std::numeric_limits<int>::max() ? 1 : m_curSerialId++);
396 serializeInvokePacket(m_packet, name: m_objectName, call, index: index - m_methodOffset, args, serialId);
397 return sendCommandWithReply(serialId);
398}
399
400QRemoteObjectPendingCall QConnectedReplicaImplementation::sendCommandWithReply(int serialId)
401{
402 bool success = sendCommand();
403 if (!success) {
404 return QRemoteObjectPendingCall(); // invalid
405 }
406
407 qCDebug(QT_REMOTEOBJECT) << "Sent InvokePacket with serial id:" << serialId;
408 QRemoteObjectPendingCall pendingCall(new QRemoteObjectPendingCallData(serialId, this));
409 Q_ASSERT(!m_pendingCalls.contains(serialId));
410 m_pendingCalls[serialId] = pendingCall;
411 return pendingCall;
412}
413
414void QConnectedReplicaImplementation::notifyAboutReply(int ackedSerialId, const QVariant &value)
415{
416 QRemoteObjectPendingCall call = m_pendingCalls.take(akey: ackedSerialId);
417 if (ackedSerialId == 0) {
418 m_heartbeatTimer.stop();
419 if (m_heartbeatTimer.interval())
420 m_heartbeatTimer.start();
421 return;
422 }
423
424 QMutexLocker mutex(&call.d->mutex);
425
426 // clear error flag
427 call.d->error = QRemoteObjectPendingCall::NoError;
428 call.d->returnValue = value;
429
430 // notify watchers if needed
431 if (call.d->watcherHelper)
432 call.d->watcherHelper->emitSignals();
433}
434
435bool QConnectedReplicaImplementation::waitForFinished(const QRemoteObjectPendingCall& call, int timeout)
436{
437 if (!call.d->watcherHelper)
438 call.d->watcherHelper.reset(other: new QRemoteObjectPendingCallWatcherHelper);
439
440 call.d->mutex.unlock();
441
442 QEventLoop loop;
443 loop.connect(sender: call.d->watcherHelper.data(), signal: &QRemoteObjectPendingCallWatcherHelper::finished,
444 receiver: &loop, slot: &QEventLoop::quit);
445
446 QTimer t; // NB: Related to QTBUG-94570 - don't use QTimer::singleShot here.
447 if (timeout >= 0) {
448 t.setSingleShot(true);
449 connect(sender: &t, signal: &QTimer::timeout, receiver: &loop, slot: &QEventLoop::quit);
450 t.start(msec: timeout);
451 }
452
453 // enter the event loop and wait for a reply
454 loop.exec(flags: QEventLoop::ExcludeUserInputEvents | QEventLoop::WaitForMoreEvents);
455
456 call.d->mutex.lock();
457
458 return call.d->error != QRemoteObjectPendingCall::InvalidMessage;
459}
460
461const QVariant QConnectedReplicaImplementation::getProperty(int i) const
462{
463 Q_ASSERT_X(i >= 0 && i < m_propertyStorage.size(), __FUNCTION__, qPrintable(QString(QLatin1String("0 <= %1 < %2")).arg(i).arg(m_propertyStorage.size())));
464 return m_propertyStorage[i];
465}
466
467void QConnectedReplicaImplementation::setProperties(const QVariantList &properties)
468{
469 Q_ASSERT(m_propertyStorage.isEmpty());
470 m_propertyStorage.reserve(alloc: properties.length());
471 m_propertyStorage = properties;
472}
473
474void QConnectedReplicaImplementation::setProperty(int i, const QVariant &prop)
475{
476 m_propertyStorage[i] = prop;
477}
478
479void QConnectedReplicaImplementation::setConnection(IoDeviceBase *conn)
480{
481 if (connectionToSource.isNull()) {
482 connectionToSource = conn;
483 qCDebug(QT_REMOTEOBJECT) << "setConnection started" << conn << m_objectName;
484 }
485 requestRemoteObjectSource();
486}
487
488void QConnectedReplicaImplementation::setDisconnected()
489{
490 connectionToSource.clear();
491 setState(QRemoteObjectReplica::State::Suspect);
492 for (const int index : childIndices()) {
493 auto pointerToQObject = qvariant_cast<QObject *>(v: getProperty(i: index));
494 auto child = qobject_cast<QRemoteObjectReplica *>(object: pointerToQObject);
495 if (child)
496 static_cast<QConnectedReplicaImplementation *>(child->d_impl.data())->setDisconnected();
497 }
498}
499
500void QConnectedReplicaImplementation::requestRemoteObjectSource()
501{
502 serializeAddObjectPacket(m_packet, name: m_objectName, isDynamic: needsDynamicInitialization());
503 sendCommand();
504}
505
506void QRemoteObjectReplicaImplementation::configurePrivate(QRemoteObjectReplica *rep)
507{
508 qCDebug(QT_REMOTEOBJECT) << "configurePrivate starting for" << this->m_objectName;
509 //We need to connect the Replicant only signals too
510 // Uncomment the following two lines if QRemoteObjectDynamicReplica gets a unique staticMetaObject (FIX #3)
511 //const QMetaObject *m = rep->inherits("QRemoteObjectDynamicReplica") ?
512 // &QRemoteObjectDynamicReplica::staticMetaObject : &QRemoteObjectReplica::staticMetaObject;
513 const QMetaObject *m = &QRemoteObjectReplica::staticMetaObject;
514 for (int i = m->methodOffset(); i < m->methodCount(); ++i)
515 {
516 const QMetaMethod mm = m->method(index: i);
517 if (mm.methodType() == QMetaMethod::Signal) {
518 const bool res = QMetaObject::connect(sender: this, signal_index: i, receiver: rep, method_index: i, type: Qt::DirectConnection, types: nullptr);
519 qCDebug(QT_REMOTEOBJECT) << " Rep connect"<<i<<res<<mm.name();
520 Q_UNUSED(res);
521 }
522 }
523 if (m_methodOffset == 0) //We haven't initialized the offsets yet
524 {
525 const int index = m_metaObject->indexOfClassInfo(QCLASSINFO_REMOTEOBJECT_TYPE);
526 const QMetaObject *metaObject = m_metaObject;
527 if (index != -1) { //We have an object created from repc or at least with QCLASSINFO defined
528 while (true) {
529 Q_ASSERT(metaObject->superClass()); //This recurses to QObject, which doesn't have QCLASSINFO_REMOTEOBJECT_TYPE
530 if (index != metaObject->superClass()->indexOfClassInfo(QCLASSINFO_REMOTEOBJECT_TYPE)) //At the point we don't find the same QCLASSINFO_REMOTEOBJECT_TYPE,
531 //we have the metaobject we should work from
532 break;
533 metaObject = metaObject->superClass();
534 }
535 }
536
537 for (int i = m_signalOffset; i < metaObject->methodCount(); ++i) {
538 const QMetaMethod mm = metaObject->method(index: i);
539 if (mm.methodType() == QMetaMethod::Signal) {
540 ++m_numSignals;
541 const bool res = QMetaObject::connect(sender: this, signal_index: i, receiver: rep, method_index: i, type: Qt::DirectConnection, types: nullptr);
542 qCDebug(QT_REMOTEOBJECT) << " Connect"<<i<<res<<mm.name();
543 Q_UNUSED(res);
544 }
545 }
546 m_methodOffset = m_signalOffset + m_numSignals;
547 qCDebug(QT_REMOTEOBJECT) << QStringLiteral("configurePrivate finished, signalOffset = %1, methodOffset = %2, #Signals = %3").arg(a: m_signalOffset).arg(a: m_methodOffset).arg(a: m_numSignals);
548 } else { //We have initialized offsets, this is an additional Replica attaching
549 for (int i = m_signalOffset; i < m_methodOffset; ++i) {
550 const bool res = QMetaObject::connect(sender: this, signal_index: i, receiver: rep, method_index: i, type: Qt::DirectConnection, types: nullptr);
551 qCDebug(QT_REMOTEOBJECT) << " Connect"<<i<<res<<m_metaObject->method(index: i).name();
552 Q_UNUSED(res);
553 }
554 if (isInitialized()) {
555 qCDebug(QT_REMOTEOBJECT) << QStringLiteral("ReplicaImplementation initialized, emitting signal on replica");
556 emit rep->initialized(); //Emit from new replica only
557 }
558 if (state() != QRemoteObjectReplica::Valid) {
559 qCDebug(QT_REMOTEOBJECT) << QStringLiteral("ReplicaImplementation not currently valid, emitting signal on replica");
560 emit rep->stateChanged(state: state(), oldState: m_metaObject ? QRemoteObjectReplica::Default : QRemoteObjectReplica::Uninitialized);
561 }
562
563 qCDebug(QT_REMOTEOBJECT) << QStringLiteral("configurePrivate finished, added replica to existing ReplicaImplementation");
564 }
565}
566
567void QConnectedReplicaImplementation::configurePrivate(QRemoteObjectReplica *rep)
568{
569 if (m_metaObject) {
570 // see QRemoteObjectReplicaImplementation::configurePrivate
571 const bool firstReplicaInstance = (m_methodOffset == 0);
572
573 QRemoteObjectReplicaImplementation::configurePrivate(rep);
574
575 // ensure that notify signals are emitted for the new replica, when
576 // we are initializing an nth replica of the same type
577 if (!firstReplicaInstance) {
578 const int offset = m_propertyOffset;
579 const int nParam = m_propertyStorage.count();
580 void *args[] = {nullptr, nullptr};
581 for (int i = 0; i < nParam; ++i) {
582 const int notifyIndex = m_metaObject->property(index: i+offset).notifySignalIndex();
583 if (notifyIndex < 0)
584 continue;
585 qCDebug(QT_REMOTEOBJECT) << " Before activate" << notifyIndex << m_metaObject->property(index: i+offset).name();
586 args[1] = m_propertyStorage[i].data();
587 // NOTE: this over-emits (assumes all values have changed)
588 QMetaObject::activate(sender: rep, rep->metaObject(), local_signal_index: notifyIndex - m_signalOffset, argv: args);
589 }
590 }
591 } else
592 m_parentsNeedingConnect.append(t: rep);
593}
594
595/*!
596 \class QRemoteObjectReplica
597 \inmodule QtRemoteObjects
598 \brief A class interacting with (but not implementing) a Qt API on the Remote Object network.
599
600 A Remote Object Replica is a QObject proxy for another QObject (called the
601 \l {Source} object). Once initialized, a replica can be considered a
602 "latent copy" of the \l {Source} object. That is, every change to a
603 Q_PROPERTY on the \l {Source}, or signal emitted by the \l {Source} will be
604 updated/emitted by all \l {Replica} objects. Latency
605 is introduced by process scheduling by any OSes involved and network
606 communication latency. As long as the replica has been initialized and the
607 communication is not disrupted, receipt and order of changes is guaranteed.
608
609 The \l {isInitialized} and \l {state} properties (and corresponding \l {initialized()}/\l {stateChanged()} signals) allow the state of a \l {Replica} to be determined.
610
611 While Qt Remote Objects (QtRO) handles the initialization and synchronization of \l {Replica} objects, there are numerous steps happening behind the scenes which can fail and that aren't encountered in single process Qt applications. See \l {Troubleshooting} for advice on how to handle such issues when using a remote objects network.
612*/
613
614/*!
615 \enum QRemoteObjectReplica::State
616
617 This enum type specifies the various state codes associated with QRemoteObjectReplica states:
618
619 \value Uninitialized initial value of DynamicReplica, where nothing is known about the replica before connection to source.
620 \value Default initial value of static replica, where any defaults set in the .rep file are available so it can be used if necessary.
621 \value Valid indicates the replica is connected, has good property values and can be interacted with.
622 \value Suspect error state that occurs if the connection to the source is lost after it is initialized.
623 \value SignatureMismatch error state that occurs if a connection to the source is made, but the source and replica are not derived from the same .rep (only possible for static Replicas).
624*/
625
626/*!
627 \fn void QRemoteObjectReplica::stateChanged(State state, State oldState)
628
629 This signal is emitted whenever a replica's state toggles between \l QRemoteObjectReplica::State.
630
631 The change in state is represented with \a state and \a oldState.
632
633 \sa state(), initialized()
634*/
635
636/*!
637 \fn void QRemoteObjectReplica::initialized()
638
639 This signal is emitted once the replica is initialized. An intialized replica
640 has all property values set, but has not yet emitted any property change
641 notifications.
642
643 \sa isInitialized(), stateChanged()
644*/
645
646/*!
647 \fn void QRemoteObjectReplica::notified()
648
649 This signal is emitted once the replica is initialized and all property change
650 notifications have been emitted.
651
652 It is sometimes useful to respond to property changes as events.
653 For example, you might want to display a user notification when a certain
654 property change occurs. However, this user notification would then also be
655 triggered when a replica first became \c QRemoteObjectReplica::Valid, as
656 all property change signals are emitted at that time. This isn't always desirable,
657 and \c notified allows the developer to distinguish between these two cases.
658*/
659
660/*!
661 \internal
662 \enum QRemoteObjectReplica::ConstructorType
663*/
664
665/*!
666 \property QRemoteObjectReplica::state
667 \brief Returns the replica state.
668
669 This property holds the replica \l QRemoteObjectReplica::State.
670*/
671
672/*!
673 \property QRemoteObjectReplica::node
674 \brief A pointer to the node this object was acquired from.
675*/
676
677/*!
678 \internal This (protected) constructor for QRemoteObjectReplica can be used to create
679 replica objects from QML.
680*/
681QRemoteObjectReplica::QRemoteObjectReplica(ConstructorType t)
682 : QObject(nullptr)
683 , d_impl(t == DefaultConstructor ? new QStubReplicaImplementation : nullptr)
684{
685 qRegisterMetaType<State>(typeName: "State");
686}
687
688QRemoteObjectReplica::QRemoteObjectReplica(QObjectPrivate &dptr, QObject *parent)
689 : QObject(dptr, parent)
690 , d_impl(new QStubReplicaImplementation)
691{
692}
693
694/*!
695 \internal
696*/
697QRemoteObjectReplica::~QRemoteObjectReplica()
698{
699}
700
701/*!
702 \internal
703*/
704void QRemoteObjectReplica::send(QMetaObject::Call call, int index, const QVariantList &args)
705{
706 Q_ASSERT(index != -1);
707
708 d_impl->_q_send(call, index, args);
709}
710
711/*!
712 \internal
713*/
714QRemoteObjectPendingCall QRemoteObjectReplica::sendWithReply(QMetaObject::Call call, int index, const QVariantList &args)
715{
716 return d_impl->_q_sendWithReply(call, index, args);
717}
718
719/*!
720 \internal
721*/
722const QVariant QRemoteObjectReplica::propAsVariant(int i) const
723{
724 return d_impl->getProperty(i);
725}
726
727/*!
728 \internal
729*/
730void QRemoteObjectReplica::initializeNode(QRemoteObjectNode *node, const QString &name)
731{
732 node->initializeReplica(instance: this, name);
733}
734
735/*!
736 \internal
737*/
738void QRemoteObjectReplica::setProperties(const QVariantList &properties)
739{
740 d_impl->setProperties(properties);
741}
742
743/*!
744 \internal
745*/
746void QRemoteObjectReplica::setChild(int i, const QVariant &value)
747{
748 d_impl->setProperty(i, value);
749}
750
751/*!
752 Returns \c true if this replica has been initialized with data from the \l {Source} object. Returns \c false otherwise.
753
754 \sa state()
755*/
756bool QRemoteObjectReplica::isInitialized() const
757{
758 return d_impl->isInitialized();
759}
760
761/*!
762 Returns \c true if this replica has been initialized with data from the \l {Source} object. Returns \c false otherwise.
763
764 \sa isInitialized()
765*/
766QRemoteObjectReplica::State QRemoteObjectReplica::state() const
767{
768 return d_impl->state();
769}
770
771QRemoteObjectNode *QRemoteObjectReplica::node() const
772{
773 return d_impl->node();
774}
775
776void QRemoteObjectReplica::setNode(QRemoteObjectNode *_node)
777{
778 const QRemoteObjectNode *curNode = node();
779 if (curNode) {
780 qCWarning(QT_REMOTEOBJECT) << "Ignoring call to setNode as the node has already been set";
781 return;
782 }
783 d_impl.clear();
784 _node->initializeReplica(instance: this);
785}
786
787/*!
788 \internal
789*/
790void QRemoteObjectReplica::initialize()
791{
792}
793
794/*!
795 Returns \c true if this replica has been initialized and has a valid connection with the \l {QRemoteObjectNode} {node} hosting the \l {Source}. Returns \c false otherwise.
796
797 \sa isInitialized()
798*/
799bool QRemoteObjectReplica::isReplicaValid() const
800{
801 return state() == Valid;
802}
803
804/*!
805 Blocking call that waits for the replica to become initialized or until the \a timeout (in ms) expires. Returns \c true if the replica is initialized when the call completes, \c false otherwise.
806
807 If \a timeout is -1, this function will not time out.
808
809 \sa isInitialized(), initialized()
810*/
811bool QRemoteObjectReplica::waitForSource(int timeout)
812{
813 return d_impl->waitForSource(timeout);
814}
815
816QInProcessReplicaImplementation::QInProcessReplicaImplementation(const QString &name, const QMetaObject *meta, QRemoteObjectNode * node)
817 : QRemoteObjectReplicaImplementation(name, meta, node)
818{
819}
820
821QInProcessReplicaImplementation::~QInProcessReplicaImplementation()
822{
823}
824
825const QVariant QInProcessReplicaImplementation::getProperty(int i) const
826{
827 Q_ASSERT(connectionToSource);
828 Q_ASSERT(connectionToSource->m_object);
829 const int index = i + QRemoteObjectSource::qobjectPropertyOffset;
830 Q_ASSERT(index >= 0 && index < connectionToSource->m_object->metaObject()->propertyCount());
831 return connectionToSource->m_object->metaObject()->property(index).read(obj: connectionToSource->m_object);
832}
833
834void QInProcessReplicaImplementation::setProperties(const QVariantList &)
835{
836 //TODO some verification here maybe?
837}
838
839void QInProcessReplicaImplementation::setProperty(int i, const QVariant &property)
840{
841 Q_ASSERT(connectionToSource);
842 Q_ASSERT(connectionToSource->m_object);
843 const int index = i + QRemoteObjectSource::qobjectPropertyOffset;
844 Q_ASSERT(index >= 0 && index < connectionToSource->m_object->metaObject()->propertyCount());
845 connectionToSource->m_object->metaObject()->property(index).write(obj: connectionToSource->m_object, value: property);
846}
847
848void QInProcessReplicaImplementation::_q_send(QMetaObject::Call call, int index, const QVariantList &args)
849{
850 Q_ASSERT(call == QMetaObject::InvokeMetaMethod || call == QMetaObject::WriteProperty);
851
852 const SourceApiMap *api = connectionToSource->m_api;
853 if (call == QMetaObject::InvokeMetaMethod) {
854 const int resolvedIndex = api->sourceMethodIndex(index: index - m_methodOffset);
855 if (resolvedIndex < 0)
856 qCWarning(QT_REMOTEOBJECT) << "Skipping invalid invocation. Index not found:" << index - m_methodOffset;
857 else
858 connectionToSource->invoke(c: call, index: index - m_methodOffset, args);
859 } else {
860 const int resolvedIndex = connectionToSource->m_api->sourcePropertyIndex(index: index - m_propertyOffset);
861 if (resolvedIndex < 0)
862 qCWarning(QT_REMOTEOBJECT) << "Skipping invalid property setter. Index not found:" << index - m_propertyOffset;
863 else
864 connectionToSource->invoke(c: call, index: index - m_propertyOffset, args);
865 }
866}
867
868QRemoteObjectPendingCall QInProcessReplicaImplementation::_q_sendWithReply(QMetaObject::Call call, int index, const QVariantList &args)
869{
870 Q_ASSERT(call == QMetaObject::InvokeMetaMethod);
871
872 const int ReplicaIndex = index - m_methodOffset;
873 int typeId = QMetaType::type(typeName: connectionToSource->m_api->typeName(index: ReplicaIndex).constData());
874 if (!QMetaType(typeId).sizeOf())
875 typeId = QVariant::Invalid;
876 QVariant returnValue(typeId, nullptr);
877
878 const int resolvedIndex = connectionToSource->m_api->sourceMethodIndex(index: ReplicaIndex);
879 if (resolvedIndex < 0) {
880 qCWarning(QT_REMOTEOBJECT) << "Skipping invalid invocation. Index not found:" << ReplicaIndex;
881 return QRemoteObjectPendingCall();
882 }
883
884 connectionToSource->invoke(c: call, index: ReplicaIndex, args, returnValue: &returnValue);
885 return QRemoteObjectPendingCall::fromCompletedCall(returnValue);
886}
887
888QStubReplicaImplementation::QStubReplicaImplementation() {}
889
890QStubReplicaImplementation::~QStubReplicaImplementation() {}
891
892const QVariant QStubReplicaImplementation::getProperty(int i) const
893{
894 Q_ASSERT_X(i >= 0 && i < m_propertyStorage.size(), __FUNCTION__, qPrintable(QString(QLatin1String("0 <= %1 < %2")).arg(i).arg(m_propertyStorage.size())));
895 return m_propertyStorage[i];
896}
897
898void QStubReplicaImplementation::setProperties(const QVariantList &properties)
899{
900 Q_ASSERT(m_propertyStorage.isEmpty());
901 m_propertyStorage.reserve(alloc: properties.length());
902 m_propertyStorage = properties;
903}
904
905void QStubReplicaImplementation::setProperty(int i, const QVariant &prop)
906{
907 m_propertyStorage[i] = prop;
908}
909
910void QStubReplicaImplementation::_q_send(QMetaObject::Call call, int index, const QVariantList &args)
911{
912 Q_UNUSED(call);
913 Q_UNUSED(index);
914 Q_UNUSED(args);
915 qWarning(msg: "Tried calling a slot or setting a property on a replica that hasn't been initialized with a node");
916}
917
918QRemoteObjectPendingCall QStubReplicaImplementation::_q_sendWithReply(QMetaObject::Call call, int index, const QVariantList &args)
919{
920 Q_UNUSED(call);
921 Q_UNUSED(index);
922 Q_UNUSED(args);
923 qWarning(msg: "Tried calling a slot or setting a property on a replica that hasn't been initialized with a node");
924 return QRemoteObjectPendingCall(); //Invalid
925}
926
927QT_END_NAMESPACE
928

source code of qtremoteobjects/src/remoteobjects/qremoteobjectreplica.cpp