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 "private/qmetaobjectbuilder_p.h"
41
42#include "qremoteobjectnode.h"
43#include "qremoteobjectnode_p.h"
44
45#include "qremoteobjectregistry.h"
46#include "qremoteobjectdynamicreplica.h"
47#include "qremoteobjectpacket_p.h"
48#include "qremoteobjectregistrysource_p.h"
49#include "qremoteobjectreplica_p.h"
50#include "qremoteobjectsource_p.h"
51#include "qremoteobjectabstractitemmodelreplica_p.h"
52#include "qremoteobjectabstractitemmodeladapter_p.h"
53#include <QtCore/qabstractitemmodel.h>
54#include <memory>
55
56QT_BEGIN_NAMESPACE
57
58using namespace QtRemoteObjects;
59using namespace QRemoteObjectStringLiterals;
60
61using GadgetType = QVector<QVariant>;
62using RegisteredType = QPair<GadgetType, std::shared_ptr<QMetaObject>>;
63static QMutex s_managedTypesMutex;
64static QHash<int, RegisteredType> s_managedTypes;
65static QHash<int, QSet<IoDeviceBase*>> s_trackedConnections;
66
67static void GadgetsStaticMetacallFunction(QObject *_o, QMetaObject::Call _c, int _id, void **_a)
68{
69 if (_c == QMetaObject::ReadProperty) {
70 GadgetType *_t = reinterpret_cast<GadgetType *>(_o);
71 if (_id < _t->size()) {
72 const auto &prop = _t->at(i: _id);
73 QMetaType::destruct(type: int(prop.userType()), where: _a[0]);
74 QMetaType::construct(type: int(prop.userType()), where: _a[0], copy: prop.constData());
75 }
76 } else if (_c == QMetaObject::WriteProperty) {
77 GadgetType *_t = reinterpret_cast<GadgetType *>(_o);
78 if (_id < _t->size()) {
79 auto & prop = (*_t)[_id];
80 prop = QVariant(prop.userType(), _a[0]);
81 }
82 }
83}
84
85static void GadgetTypedDestructor(int, void *ptr)
86{
87 reinterpret_cast<GadgetType*>(ptr)->~GadgetType();
88}
89
90static void *GadgetTypedConstructor(int type, void *where, const void *copy)
91{
92 GadgetType *ret = where ? new(where) GadgetType : new GadgetType;
93 if (copy) {
94 *ret = *reinterpret_cast<const GadgetType*>(copy);
95 } else {
96 QMutexLocker lock(&s_managedTypesMutex);
97 auto it = s_managedTypes.find(akey: type);
98 if (it == s_managedTypes.end()) {
99 delete ret;
100 return nullptr;
101 }
102 *ret = it->first;
103 }
104 return ret;
105}
106
107static void GadgetSaveOperator(QDataStream & out, const void *data)
108{
109 const GadgetType *gadgetProperties = reinterpret_cast<const GadgetType *>(data);
110 for (const auto &prop : *gadgetProperties)
111 out << prop;
112}
113
114static void GadgetLoadOperator(QDataStream &in, void *data)
115{
116 GadgetType *gadgetProperties = reinterpret_cast<GadgetType *>(data);
117 for (auto &prop : *gadgetProperties)
118 in >> prop;
119}
120
121// Like the Q_GADGET static methods above, we need constructor/destructor methods
122// in order to use dynamically defined enums with QVariant or as signal/slot
123// parameters (i.e., the queued connection mechanism, which QtRO leverages).
124//
125// We will need the enum methods to support different sizes when typed scope enum
126// support is added, so might as well use that now.
127template<typename T>
128static void EnumDestructor(void *ptr)
129{
130 static_cast<T*>(ptr)->~T();
131}
132
133template<typename T>
134static void *EnumConstructor(void *where, const void *copy)
135{
136 T *ret = where ? new(where) T : new T;
137 if (copy)
138 *ret = *static_cast<const T*>(copy);
139 return ret;
140}
141
142// Not used, but keeping these in case we end up with a need for save/load.
143template<typename T>
144static void EnumSaveOperator(QDataStream & out, const void *data)
145{
146 const T value = *static_cast<const T *>(data);
147 out << value;
148}
149
150template<typename T>
151static void EnumLoadOperator(QDataStream &in, void *data)
152{
153 T value = *static_cast<T *>(data);
154 in >> value;
155}
156
157static QString name(const QMetaObject * const mobj)
158{
159 const int ind = mobj->indexOfClassInfo(QCLASSINFO_REMOTEOBJECT_TYPE);
160 return ind >= 0 ? QString::fromLatin1(str: mobj->classInfo(index: ind).value()) : QString();
161}
162
163QString QtRemoteObjects::getTypeNameAndMetaobjectFromClassInfo(const QMetaObject *& meta) {
164 QString typeName;
165 const int ind = meta->indexOfClassInfo(QCLASSINFO_REMOTEOBJECT_TYPE);
166 if (ind != -1) { //We have an object created from repc or at least with QCLASSINFO defined
167 typeName = QString::fromLatin1(str: meta->classInfo(index: ind).value());
168 while (true) {
169 Q_ASSERT(meta->superClass());//This recurses to QObject, which doesn't have QCLASSINFO_REMOTEOBJECT_TYPE
170 //At the point superclass doesn't have the same QCLASSINFO_REMOTEOBJECT_TYPE,
171 //we have the metaobject we should work from
172 if (ind != meta->superClass()->indexOfClassInfo(QCLASSINFO_REMOTEOBJECT_TYPE))
173 break;
174 meta = meta->superClass();
175 }
176 }
177 return typeName;
178}
179
180template <typename K, typename V, typename Query>
181bool map_contains(const QMap<K,V> &map, const Query &key, typename QMap<K,V>::const_iterator &result)
182{
183 const typename QMap<K,V>::const_iterator it = map.find(key);
184 if (it == map.end())
185 return false;
186 result = it;
187 return true;
188}
189
190/*!
191 \qmltype Node
192 \instantiates QRemoteObjectNode
193 \inqmlmodule QtRemoteObjects
194 \brief A node on a Qt Remote Objects network.
195
196 The Node type provides an entry point to a Qt Remote Objects network. A network
197 can be as simple as two nodes, or an arbitrarily complex set of processes and devices.
198
199 A Node does not have a url that other nodes can connect to, and thus is able to acquire
200 replicas only. It is not able to share source objects.
201
202*/
203
204QRemoteObjectNodePrivate::QRemoteObjectNodePrivate()
205 : QObjectPrivate()
206 , registry(nullptr)
207 , retryInterval(250)
208 , lastError(QRemoteObjectNode::NoError)
209 , persistedStore(nullptr)
210{ }
211
212QRemoteObjectNodePrivate::~QRemoteObjectNodePrivate()
213{
214}
215
216QRemoteObjectSourceLocations QRemoteObjectNodePrivate::remoteObjectAddresses() const
217{
218 if (registry)
219 return registry->sourceLocations();
220 return QRemoteObjectSourceLocations();
221}
222
223QRemoteObjectSourceLocations QRemoteObjectRegistryHostPrivate::remoteObjectAddresses() const
224{
225 if (registrySource)
226 return registrySource->sourceLocations();
227 return QRemoteObjectSourceLocations();
228}
229
230/*!
231 \reimp
232*/
233void QRemoteObjectNode::timerEvent(QTimerEvent*)
234{
235 Q_D(QRemoteObjectNode);
236
237 for (auto it = d->pendingReconnect.begin(), end = d->pendingReconnect.end(); it != end; /*erasing*/) {
238 const auto &conn = *it;
239 if (conn->isOpen()) {
240 it = d->pendingReconnect.erase(i: it);
241 } else {
242 conn->connectToServer();
243 ++it;
244 }
245 }
246
247 if (d->pendingReconnect.isEmpty())
248 d->reconnectTimer.stop();
249
250 qRODebug(this) << "timerEvent" << d->pendingReconnect.size();
251}
252
253/*!
254 \qmlproperty int Node::heartbeatInterval
255
256 Heartbeat interval in ms.
257
258 The heartbeat (only helpful for socket connections) will periodically send a
259 message to connected nodes to detect whether the connection was disrupted.
260 Qt Remote Objects will try to reconnect automatically if it detects a dropped
261 connection. This function can help with that detection since the client will
262 only detect that the server is unavailable when it tries to send data.
263
264 A value of \c 0 (the default) will disable the heartbeat.
265*/
266
267
268/*!
269 \property QRemoteObjectNode::heartbeatInterval
270 \brief Heartbeat interval in ms.
271
272 The heartbeat (only helpful for socket connections) will periodically send a
273 message to connected nodes to detect whether the connection was disrupted.
274 Qt Remote Objects will try to reconnect automatically if it detects a dropped
275 connection. This function can help with that detection since the client will
276 only detect that the server is unavailable when it tries to send data.
277
278 A value of \c 0 (the default) will disable the heartbeat.
279*/
280int QRemoteObjectNode::heartbeatInterval() const
281{
282 Q_D(const QRemoteObjectNode);
283 return d->m_heartbeatInterval;
284}
285
286void QRemoteObjectNode::setHeartbeatInterval(int interval)
287{
288 Q_D(QRemoteObjectNode);
289 if (d->m_heartbeatInterval == interval)
290 return;
291 d->m_heartbeatInterval = interval;
292 emit heartbeatIntervalChanged(heartbeatInterval: interval);
293}
294
295/*!
296 \since 5.12
297 \typedef QRemoteObjectNode::RemoteObjectSchemaHandler
298
299 Typedef for a std::function method that can take a QUrl input and is
300 responsible for creating the communications channel between this node and
301 the node hosting the desired \l Source. As some types of QIODevices (e.g.,
302 QSslSocket) require additional steps before the device is ready for use,
303 the method is responsible for calling \l addClientSideConnection once the
304 connection is fully established.
305*/
306
307/*!
308 \since 5.12
309 \brief Provide a custom method to handle externally provided schemas
310
311 This method is tied to the \l Registry and \l {External Schemas}. By
312 registering a std::function handler for an external schema, the registered
313 method will be called when the registry is notified of a \l Source you've
314 acquired being available. Without this registration, QtRO would only be
315 able to handle the "built-in" schemas.
316
317 The provided method, \a handler, will be called when the registry sees a \l
318 Source object on a new (not yet connected) Node with a {QUrl::schema()} of
319 \a schema. The \a handler, of type \l
320 QRemoteObjectNode::RemoteObjectSchemaHandler will get the \l QUrl of the
321 Node providing the \l Source as an input parameter, and is responsible for
322 establishing the communications channel (a \l QIODevice of some sort) and
323 calling \l addClientSideConnection with it.
324
325 \sa RemoteObjectSchemaHandler
326*/
327void QRemoteObjectNode::registerExternalSchema(const QString &schema, QRemoteObjectNode::RemoteObjectSchemaHandler handler)
328{
329 Q_D(QRemoteObjectNode);
330 d->schemaHandlers.insert(akey: schema, avalue: handler);
331}
332
333/*!
334 \since 5.11
335 \brief Forward Remote Objects from another network
336
337 The proxy functionality is useful when you want to share \l Source objects
338 over multiple networks. For instance, if you have an embedded target using
339 target-only connections (like local) and you want to make some of those
340 same objects available externally.
341
342 As a concrete example, say you have a set of processes talking to each
343 other on your target hardware using a registry, with the \l Registry at
344 "local:registry" and separate processes using a node at "local:MyHost" that
345 holds \l Source objects. If you wanted to access these objects, but over
346 tcp, you could create a new proxyNode like so:
347
348\code
349 // myInternalHost is a node only visible on the device...
350 QRemoteObjectHost myInternalHost("local:MyHost");
351 myInternalHost.enableRemoting<SomeObject>(&someObject);
352
353 // Regular host node, listening on port 12123, so visible to other
354 // devices
355 QRemoteObjectHost proxyNode("tcp://localhost:12123");
356
357 // Enable proxying objects from nodes on the local machine's internal
358 // QtRO bus
359 proxyNode.proxy("local:registry");
360\endcode
361
362 And from another device you create another node:
363
364\code
365 // NB: localhost resolves to a different ip address than proxyNode
366 QRemoteObjectHost nodeOnRemoteDevice("tcp://localhost:23234");
367
368 // Connect to the target's proxyNode directly, or use a tcp registry...
369 nodeOnRemoteDevice.connectToNode("tcp://<target device>:12123");
370
371 // Because of the proxy, we can get the object over tcp/ip port 12123,
372 // even though we can't connect directly to "local:MyHost"
373 SomeObject *so = nodeOnRemoteDevice.acquire<SomeObject>();
374\endcode
375
376 This would (internally) create a node in proxyNode, which (again
377 internally/automatically) connects to the provided registry (given by the
378 \a registryUrl parameter, "local:registry" in this example). Whenever
379 local:registry emits the \l remoteObjectAdded signal, the
380 \c QRemoteObjectSourceLocation is passed to the \a filter given to the proxy
381 call. If this method returns true (the default filter simply returns true
382 without any filtering), the object is acquired() from the internal node and
383 enableRemoting() (once the replica is initialized) is called on proxyNode.
384
385 If a \a hostUrl is provided (which is required to enable reverseProxy, but
386 not needed otherwise), the internal node will be a \l QRemoteObjectHost node
387 configured with the provided address. If no \a hostUrl is provided, the
388 internal node will be a QRemoteObjectNode (not HostNode).
389
390 Returns \c true if the object is acquired from the internal node.
391
392 \sa reverseProxy()
393*/
394bool QRemoteObjectHostBase::proxy(const QUrl &registryUrl, const QUrl &hostUrl, RemoteObjectNameFilter filter)
395{
396 Q_D(QRemoteObjectHostBase);
397 if (!registryUrl.isValid() || !QtROClientFactory::instance()->isValid(url: registryUrl)) {
398 qROWarning(this) << "Can't proxy to registryUrl (invalid url or schema)" << registryUrl;
399 return false;
400 }
401
402 if (!hostUrl.isEmpty() && !QtROClientFactory::instance()->isValid(url: hostUrl)) {
403 qROWarning(this) << "Can't proxy using hostUrl (invalid schema)" << hostUrl;
404 return false;
405 }
406
407 if (d->proxyInfo) {
408 qROWarning(this) << "Proxying from multiple objects is currently not supported.";
409 return false;
410 }
411
412 QRemoteObjectNode *node;
413 if (hostUrl.isEmpty()) {
414 node = new QRemoteObjectNode(registryUrl);
415 } else {
416 node = new QRemoteObjectHost(hostUrl, registryUrl);
417 }
418 d->proxyInfo = new ProxyInfo(node, this, filter);
419 return true;
420}
421
422/*!
423 \since 5.11
424 \brief Forwards remote objects to another network.
425
426 The reverseProxy() function allows the \l proxy() functionality to be
427 extended, in effect mirroring the proxy functionality in the "reverse"
428 direction. These are distinct, because node communication is not symmetric,
429 one side calls enableRemoting() with a \l Source object, the other side
430 calls acquire() to get a \l Replica. Using \l proxy() allows you to
431 "observe" objects on a target device remotely via acquire, but it does not
432 allow off-target \l Source objects to be acquired from the device's local:*
433 network. That is where \l reverseProxy() comes in. If a proxyNode is
434 created like so:
435
436\code
437 // myInternalHost is a node only visible on the device...
438 QRemoteObjectHost myInternalHost("local:MyHost");
439
440 // RegistryHost node, listening on port 12123, so visible to other
441 // devices. The node must be a RegistryHost, so the Sources on
442 // the "outside" network can be forwarded to the inner network.
443 QRemoteObjectRegistryHost proxyNode("tcp://localhost:12123");
444
445 // Enable proxying objects from nodes on the local machine's internal
446 // QtRO bus. Note the hostUrl parameter is now needed.
447 proxyNode.proxy("local:registry", "local:fromProxy");
448 proxyNode.reverseProxy();
449\endcode
450
451 And from another device you create another node:
452
453\code
454 // NB: localhost resolves to a different ip address than proxyNode
455 QRemoteObjectHost nodeOnRemoteDevice("tcp://localhost:23234");
456
457 // Connect to the target's proxyNode directly, or use a tcp registry...
458 nodeOnRemoteDevice.connectToNode("tcp://<target device>:12123");
459
460 // Because of the reverseProxy, we can expose objects on this device
461 // and they will make their way to proxyNode...
462 nodeOnRemoteDevice.enableRemoting<OtherObject>(&otherObject);
463\endcode
464
465\code
466 // Acquire() can now see the objects on other devices through proxyNode,
467 // due to the reverseProxy call.
468 OtherObject *oo = myInternalHost.acquire<OtherObject>();
469\endcode
470
471 While the \l proxy() functionality allows \l Source objects on another
472 network to be acquired(), reverseProxy() allows \l Source objects to be
473 "pushed" to an otherwise inaccessible network.
474
475 \note proxy() needs to be called before \l reverseProxy(), and a
476 hostUrl needs to be provided to \l proxy for \l reverseProxy() to work. The
477 \l reverseProxy() method allows a separate \a filter to be applied. This
478 reverseProxy specific filter will receive notifications of new \l Source
479 objects on proxyNode and acquire them on the internal node if they pass the
480 reverseFilter.
481
482 Returns \c true on success, \c false otherwise.
483
484 \sa proxy()
485*/
486bool QRemoteObjectHostBase::reverseProxy(QRemoteObjectHostBase::RemoteObjectNameFilter filter)
487{
488 Q_D(QRemoteObjectHostBase);
489
490 if (!d->proxyInfo) {
491 qROWarning(this) << "proxy() needs to be called before setting up reverse proxy.";
492 return false;
493 }
494
495 QRemoteObjectHost *host = qobject_cast<QRemoteObjectHost *>(object: d->proxyInfo->proxyNode);
496 if (!host) {
497 qROWarning(this) << "proxy() needs called with host-url to enable reverse proxy.";
498 return false;
499 }
500
501 return d->proxyInfo->setReverseProxy(filter);
502}
503
504/*!
505 \internal The replica needs to have a default constructor to be able
506 to create a replica from QML. In order for it to be properly
507 constructed, there needs to be a way to associate the replica with a
508 node and start the replica initialization. Thus we need a public
509 method on node to facilitate that. That's initializeReplica.
510*/
511void QRemoteObjectNode::initializeReplica(QRemoteObjectReplica *instance, const QString &name)
512{
513 Q_D(QRemoteObjectNode);
514 if (instance->inherits(classname: "QRemoteObjectDynamicReplica")) {
515 d->setReplicaImplementation(nullptr, instance, name);
516 } else {
517 const QMetaObject *meta = instance->metaObject();
518 // This is a templated acquire, so we tell the Source we don't need
519 // them to send the class definition. Thus we need to store the
520 // metaObject for this class - if this is a nested class, the QObject
521 // could be a nullptr or updated from the source,
522 d->dynamicTypeManager.addFromMetaObject(meta);
523 d->setReplicaImplementation(meta, instance, name.isEmpty() ? ::name(mobj: meta) : name);
524 }
525}
526
527void QRemoteObjectNodePrivate::setLastError(QRemoteObjectNode::ErrorCode errorCode)
528{
529 Q_Q(QRemoteObjectNode);
530 lastError = errorCode;
531 emit q->error(errorCode: lastError);
532}
533
534void QRemoteObjectNodePrivate::setReplicaImplementation(const QMetaObject *meta, QRemoteObjectReplica *instance, const QString &name)
535{
536 qROPrivDebug() << "Starting setReplicaImplementation for" << name;
537 openConnectionIfNeeded(name);
538 QMutexLocker locker(&mutex);
539 if (hasInstance(name)) {
540 qCDebug(QT_REMOTEOBJECT)<<"setReplicaImplementation - using existing instance";
541 QSharedPointer<QRemoteObjectReplicaImplementation> rep = qSharedPointerCast<QRemoteObjectReplicaImplementation>(src: replicas.value(akey: name).toStrongRef());
542 Q_ASSERT(rep);
543 instance->d_impl = rep;
544 rep->configurePrivate(instance);
545 } else {
546 instance->d_impl.reset(t: handleNewAcquire(meta, instance, name));
547 instance->initialize();
548 replicas.insert(akey: name, avalue: instance->d_impl.toWeakRef());
549 qROPrivDebug() << "setReplicaImplementation - Created new instance" << name<<remoteObjectAddresses();
550 }
551}
552
553/*!
554 Returns a pointer to the Node's \l {QRemoteObjectRegistry}, if the Node
555 is using the Registry feature; otherwise it returns 0.
556*/
557const QRemoteObjectRegistry *QRemoteObjectNode::registry() const
558{
559 Q_D(const QRemoteObjectNode);
560 return d->registry;
561}
562
563/*!
564 \class QRemoteObjectAbstractPersistedStore
565 \inmodule QtRemoteObjects
566 \brief A class which provides the methods for setting PROP values of a
567 replica to value they had the last time the replica was used.
568
569 This can be used to provide a "reasonable" value to be displayed until the
570 connection to the source is established and current values are available.
571
572 This class must be overridden to provide an implementation for saving (\l
573 QRemoteObjectAbstractPersistedStore::saveProperties) and restoring (\l
574 QRemoteObjectAbstractPersistedStore::restoreProperties) PROP values. The derived
575 type can then be set for a node, and any replica acquired from that node
576 will then automatically store PERSISTED properties when the replica
577 destructor is called, and retrieve the values when the replica is
578 instantiated.
579*/
580
581/*!
582 Constructs a QRemoteObjectAbstractPersistedStore with the given \a parent.
583 The default value of \a parent is \c nullptr.
584*/
585QRemoteObjectAbstractPersistedStore::QRemoteObjectAbstractPersistedStore(QObject *parent)
586 : QObject(parent)
587{
588}
589
590QRemoteObjectAbstractPersistedStore::~QRemoteObjectAbstractPersistedStore()
591{
592}
593
594/*!
595 \fn virtual void QRemoteObjectAbstractPersistedStore::saveProperties(const QString &repName, const QByteArray &repSig, const QVariantList &values)
596
597 This method will be provided the replica class's \a repName, \a repSig and the list of
598 \a values that PERSISTED properties have when the replica destructor was
599 called. It is the responsibility of the inheriting class to store the
600 information in a manner consistent for \l
601 QRemoteObjectAbstractPersistedStore::restoreProperties to retrieve.
602
603 \sa QRemoteObjectAbstractPersistedStore::restoreProperties
604*/
605
606/*!
607 \fn virtual QVariantList QRemoteObjectAbstractPersistedStore::restoreProperties(const QString &repName, const QByteArray &repSig)
608
609 This method will be provided the replica class's \a repName and \a repSig when the
610 replica is being initialized. It is the responsibility of the inheriting
611 class to get the last values persisted by \l
612 QRemoteObjectAbstractPersistedStore::saveProperties and return them. An empty
613 QVariantList should be returned if no values are available.
614
615 \sa QRemoteObjectAbstractPersistedStore::saveProperties
616*/
617
618
619QRemoteObjectAbstractPersistedStore *QRemoteObjectNode::persistedStore() const
620{
621 Q_D(const QRemoteObjectNode);
622 return d->persistedStore;
623}
624
625/*!
626 \qmlproperty QRemoteObjectAbstractPersistedStore Node::persistedStore
627
628 Allows setting a \l QRemoteObjectAbstractPersistedStore instance for the node.
629
630 Allows replica \l PROP members with the PERSISTED trait to save their current value when the
631 replica is deleted and restore a stored value the next time the replica is started.
632
633 Requires a \l QRemoteObjectAbstractPersistedStore class implementation to control where and how
634 persistence is handled. A default QSettings-based implementation is provided by SettingsStore.
635*/
636
637/*!
638 \since 5.11
639 \property QRemoteObjectNode::persistedStore
640 \brief Allows setting a \l QRemoteObjectAbstractPersistedStore instance for the node.
641
642 Allows replica \l PROP members with the PERSISTED trait to save their current value when the
643 replica is deleted and restore a stored value the next time the replica is started.
644
645 Requires a \l QRemoteObjectAbstractPersistedStore class implementation to control where and how
646 persistence is handled.
647*/
648void QRemoteObjectNode::setPersistedStore(QRemoteObjectAbstractPersistedStore *persistedStore)
649{
650 Q_D(QRemoteObjectNode);
651 d->persistedStore = persistedStore;
652}
653
654QRemoteObjectAbstractPersistedStore::QRemoteObjectAbstractPersistedStore(QRemoteObjectAbstractPersistedStorePrivate &dptr, QObject *parent)
655 : QObject(dptr, parent)
656{
657}
658
659QRemoteObjectAbstractPersistedStorePrivate::QRemoteObjectAbstractPersistedStorePrivate()
660{
661}
662
663QRemoteObjectAbstractPersistedStorePrivate::~QRemoteObjectAbstractPersistedStorePrivate()
664{
665}
666
667QRemoteObjectMetaObjectManager::~QRemoteObjectMetaObjectManager()
668{
669 for (QMetaObject *mo : dynamicTypes)
670 free(ptr: mo); //QMetaObjectBuilder uses malloc, not new
671}
672
673const QMetaObject *QRemoteObjectMetaObjectManager::metaObjectForType(const QString &type)
674{
675 qCDebug(QT_REMOTEOBJECT) << "metaObjectForType: looking for" << type << "static keys:" << staticTypes.keys() << "dynamic keys:" << dynamicTypes.keys();
676 Q_ASSERT(staticTypes.contains(type) || dynamicTypes.contains(type));
677 auto it = staticTypes.constFind(akey: type);
678 if (it != staticTypes.constEnd())
679 return it.value();
680 return dynamicTypes.value(akey: type);
681}
682
683static void trackConnection(int typeId, IoDeviceBase *connection)
684{
685 QMutexLocker lock(&s_managedTypesMutex);
686 if (s_trackedConnections[typeId].contains(value: connection))
687 return;
688 s_trackedConnections[typeId].insert(value: connection);
689 auto unregisterIfNotUsed = [typeId, connection]{
690 QMutexLocker lock(&s_managedTypesMutex);
691 Q_ASSERT(s_trackedConnections.contains(typeId));
692 Q_ASSERT(s_trackedConnections[typeId].contains(connection));
693 s_trackedConnections[typeId].remove(value: connection);
694 if (s_trackedConnections[typeId].isEmpty()) {
695 s_trackedConnections.remove(akey: typeId);
696 s_managedTypes.remove(akey: typeId);
697 QMetaType::unregisterType(type: typeId);
698 }
699 };
700
701 // Unregister the type only when the connection is destroyed
702 // Do not unregister types when the connections is discconected, because
703 // if it gets reconnected it will not register the types again
704 QObject::connect(sender: connection, signal: &IoDeviceBase::destroyed, slot: unregisterIfNotUsed);
705}
706
707struct EnumPair {
708 QByteArray name;
709 int value;
710};
711
712struct EnumData {
713 QByteArray name;
714 bool isFlag, isScoped;
715 quint32 keyCount, size;
716 QVector<EnumPair> values;
717};
718
719struct GadgetProperty {
720 QByteArray name;
721 QByteArray type;
722};
723
724struct GadgetData {
725 QVector<GadgetProperty> properties;
726 QVector<EnumData> enums;
727};
728
729using Gadgets = QHash<QByteArray, GadgetData>;
730
731static void registerEnum(const QByteArray &name, const QMetaObject *meta, int size=4)
732{
733 // When we add support for enum classes, we will need to set this to something like
734 // QByteArray(enumClass).append("::").append(enumMeta.name()) when enumMeta.isScoped() is true.
735 // That is a new feature, though.
736 if (QMetaType::isRegistered(type: QMetaType::type(typeName: name)))
737 return;
738 static const auto flags = QMetaType::IsEnumeration | QMetaType::NeedsConstruction | QMetaType::NeedsDestruction;
739 int id;
740 switch (size) {
741 case 1: id = QMetaType::registerType(typeName: name.constData(), deleter: nullptr, creator: nullptr, destructor: &EnumDestructor<qint8>,
742 constructor: &EnumConstructor<qint8>, size, flags, metaObject: meta);
743 break;
744 case 2: id = QMetaType::registerType(typeName: name.constData(), deleter: nullptr, creator: nullptr, destructor: &EnumDestructor<qint16>,
745 constructor: &EnumConstructor<qint16>, size, flags, metaObject: meta);
746 break;
747 case 4: id = QMetaType::registerType(typeName: name.constData(), deleter: nullptr, creator: nullptr, destructor: &EnumDestructor<qint32>,
748 constructor: &EnumConstructor<qint32>, size, flags, metaObject: meta);
749 break;
750 // Qt currently only supports enum values of 4 or less bytes (QMetaEnum value(index) returns int)
751// case 8: id = QMetaType::registerType(name.constData(), nullptr, nullptr, &EnumDestructor<qint64>,
752// &EnumConstructor<qint64>, size, flags, meta);
753// break;
754 default:
755 qWarning() << "Invalid enum detected" << name << "with size" << size << ". Defaulting to register as int.";
756 size = 4;
757 id = QMetaType::registerType(typeName: name.constData(), deleter: nullptr, creator: nullptr, destructor: &EnumDestructor<qint32>,
758 constructor: &EnumConstructor<qint32>, size, flags, metaObject: meta);
759 }
760#ifdef QTRO_VERBOSE_PROTOCOL
761 qDebug() << "Registering new enum with id" << id << name << "size:" << size;
762#endif
763 qCDebug(QT_REMOTEOBJECT) << "Registering new enum with id" << id << name << "size:" << size;
764}
765
766static int registerGadgets(IoDeviceBase *connection, Gadgets &gadgets, QByteArray typeName)
767{
768 const auto &gadget = gadgets.take(akey: typeName);
769 int typeId = QMetaType::type(typeName);
770 if (typeId != QMetaType::UnknownType) {
771 trackConnection(typeId, connection);
772 return typeId;
773 }
774
775 QMetaObjectBuilder gadgetBuilder;
776 gadgetBuilder.setClassName(typeName);
777 gadgetBuilder.setFlags(QMetaObjectBuilder::DynamicMetaObject | QMetaObjectBuilder::PropertyAccessInStaticMetaCall);
778 GadgetType gadgetType;
779 for (const auto &prop : gadget.properties) {
780 int propertyType = QMetaType::type(typeName: prop.type);
781 if (!propertyType && gadgets.contains(akey: prop.type))
782 propertyType = registerGadgets(connection, gadgets, typeName: prop.type);
783 gadgetType.push_back(t: QVariant(QVariant::Type(propertyType)));
784 auto dynamicProperty = gadgetBuilder.addProperty(name: prop.name, type: prop.type);
785 dynamicProperty.setWritable(true);
786 dynamicProperty.setReadable(true);
787 }
788 for (const auto &enumData: gadget.enums) {
789 auto enumBuilder = gadgetBuilder.addEnumerator(name: enumData.name);
790 enumBuilder.setIsFlag(enumData.isFlag);
791 enumBuilder.setIsScoped(enumData.isScoped);
792
793 for (quint32 k = 0; k < enumData.keyCount; ++k) {
794 const auto pair = enumData.values.at(i: k);
795 enumBuilder.addKey(name: pair.name, value: pair.value);
796 }
797 }
798 auto meta = gadgetBuilder.toMetaObject();
799 const auto enumCount = meta->enumeratorCount();
800 for (int i = 0; i < enumCount; i++) {
801 const QByteArray registeredName = QByteArray(typeName).append(s: "::").append(s: meta->enumerator(index: i).name());
802 registerEnum(name: registeredName, meta, size: gadget.enums.at(i).size);
803 }
804 QMetaType::TypeFlags flags = QMetaType::IsGadget;
805 int gadgetTypeId;
806 if (meta->propertyCount()) {
807 meta->d.static_metacall = &GadgetsStaticMetacallFunction;
808 meta->d.superdata = nullptr;
809 flags |= QMetaType::NeedsConstruction | QMetaType::NeedsDestruction;
810 gadgetTypeId = QMetaType::registerType(typeName: typeName.constData(),
811 destructor: &GadgetTypedDestructor,
812 constructor: &GadgetTypedConstructor,
813 size: sizeof(GadgetType),
814 flags, metaObject: meta);
815 QMetaType::registerStreamOperators(type: gadgetTypeId, saveOp: &GadgetSaveOperator, loadOp: &GadgetLoadOperator);
816 } else {
817 gadgetTypeId = QMetaType::registerType(typeName: typeName.constData(),
818 destructor: nullptr,
819 constructor: nullptr,
820 size: sizeof(GadgetType),
821 flags, metaObject: meta);
822 }
823 trackConnection(typeId: gadgetTypeId, connection);
824 QMutexLocker lock(&s_managedTypesMutex);
825 s_managedTypes[gadgetTypeId] = qMakePair(x: gadgetType, y: std::shared_ptr<QMetaObject>{meta, [](QMetaObject *ptr){ ::free(ptr: ptr); }});
826 return gadgetTypeId;
827}
828
829static void registerAllGadgets(IoDeviceBase *connection, Gadgets &gadgets)
830{
831 while (!gadgets.isEmpty())
832 registerGadgets(connection, gadgets, typeName: gadgets.constBegin().key());
833}
834
835static void deserializeEnum(QDataStream &ds, EnumData &enumData)
836{
837 ds >> enumData.name;
838 ds >> enumData.isFlag;
839 ds >> enumData.isScoped;
840 ds >> enumData.size;
841 ds >> enumData.keyCount;
842 for (quint32 i = 0; i < enumData.keyCount; i++) {
843 EnumPair pair;
844 ds >> pair.name;
845 ds >> pair.value;
846 enumData.values.push_back(t: pair);
847 }
848}
849
850static void parseGadgets(IoDeviceBase *connection, QDataStream &in)
851{
852 quint32 qtEnums, numGadgets;
853 in >> qtEnums; // Qt enums - just need registration
854 for (quint32 i = 0; i < qtEnums; ++i) {
855 QByteArray enumName;
856 in >> enumName;
857 QMetaType t(QMetaType::type(typeName: enumName.constData()));
858 registerEnum(name: enumName, meta: t.metaObject()); // All Qt enums have default type int
859 }
860 in >> numGadgets;
861 if (numGadgets == 0)
862 return;
863 Gadgets gadgets;
864 for (quint32 i = 0; i < numGadgets; ++i) {
865 QByteArray type;
866 in >> type;
867 quint32 numProperties, numEnums;
868 in >> numProperties;
869 auto &properties = gadgets[type].properties;
870 for (quint32 p = 0; p < numProperties; ++p) {
871 GadgetProperty prop;
872 in >> prop.name;
873 in >> prop.type;
874 properties.push_back(t: prop);
875 }
876 in >> numEnums;
877 auto &enums = gadgets[type].enums;
878 for (quint32 e = 0; e < numEnums; ++e) {
879 EnumData enumData;
880 deserializeEnum(ds&: in, enumData);
881 enums.push_back(t: enumData);
882 }
883 }
884 registerAllGadgets(connection, gadgets);
885}
886
887QMetaObject *QRemoteObjectMetaObjectManager::addDynamicType(IoDeviceBase *connection, QDataStream &in)
888{
889 QMetaObjectBuilder builder;
890 builder.setSuperClass(&QRemoteObjectReplica::staticMetaObject);
891 builder.setFlags(QMetaObjectBuilder::DynamicMetaObject);
892
893 QString typeString;
894 QByteArray type;
895 quint32 numEnums = 0;
896 quint32 numSignals = 0;
897 quint32 numMethods = 0;
898 quint32 numProperties = 0;
899
900 in >> typeString;
901 type = typeString.toLatin1();
902 builder.addClassInfo(QCLASSINFO_REMOTEOBJECT_TYPE, value: type);
903 builder.setClassName(type);
904
905 in >> numEnums;
906 QVector<quint32> enumSizes(numEnums);
907 for (quint32 i = 0; i < numEnums; ++i) {
908 EnumData enumData;
909 deserializeEnum(ds&: in, enumData);
910 auto enumBuilder = builder.addEnumerator(name: enumData.name);
911 enumBuilder.setIsFlag(enumData.isFlag);
912 enumBuilder.setIsScoped(enumData.isScoped);
913 enumSizes[i] = enumData.size;
914
915 for (quint32 k = 0; k < enumData.keyCount; ++k) {
916 const auto pair = enumData.values.at(i: k);
917 enumBuilder.addKey(name: pair.name, value: pair.value);
918 }
919 }
920 parseGadgets(connection, in);
921
922 int curIndex = 0;
923
924 in >> numSignals;
925 for (quint32 i = 0; i < numSignals; ++i) {
926 QByteArray signature;
927 QList<QByteArray> paramNames;
928 in >> signature;
929 in >> paramNames;
930 ++curIndex;
931 auto mmb = builder.addSignal(signature);
932 mmb.setParameterNames(paramNames);
933 }
934
935 in >> numMethods;
936 for (quint32 i = 0; i < numMethods; ++i) {
937 QByteArray signature, returnType;
938 QList<QByteArray> paramNames;
939 in >> signature;
940 in >> returnType;
941 in >> paramNames;
942 ++curIndex;
943 const bool isVoid = returnType.isEmpty() || returnType == QByteArrayLiteral("void");
944 QMetaMethodBuilder mmb;
945 if (isVoid)
946 mmb = builder.addMethod(signature);
947 else
948 mmb = builder.addMethod(signature, QByteArrayLiteral("QRemoteObjectPendingCall"));
949 mmb.setParameterNames(paramNames);
950 }
951
952 in >> numProperties;
953
954 for (quint32 i = 0; i < numProperties; ++i) {
955 QByteArray name;
956 QByteArray typeName;
957 QByteArray signalName;
958 in >> name;
959 in >> typeName;
960 in >> signalName;
961 if (signalName.isEmpty())
962 builder.addProperty(name, type: typeName);
963 else
964 builder.addProperty(name, type: typeName, notifierId: builder.indexOfSignal(signature: signalName));
965 }
966
967 auto meta = builder.toMetaObject();
968 // Our type likely has enumerations from the inherited base classes, such as the Replica State
969 // We only want to register the new enumerations, and since we just added them, we know they
970 // are the last indices. Thus a backwards count seems most efficient.
971 const int totalEnumCount = meta->enumeratorCount();
972 int incrementingIndex = 0;
973 for (int i = numEnums; i > 0; i--) {
974 auto const enumMeta = meta->enumerator(index: totalEnumCount - i);
975 const QByteArray registeredName = QByteArray(type).append(s: "::").append(s: enumMeta.name());
976 registerEnum(name: registeredName, meta, size: enumSizes.at(i: incrementingIndex++));
977 }
978 dynamicTypes.insert(akey: typeString, avalue: meta);
979 return meta;
980}
981
982void QRemoteObjectMetaObjectManager::addFromMetaObject(const QMetaObject *metaObject)
983{
984 QString className = QLatin1String(metaObject->className());
985 if (!className.endsWith(s: QLatin1String("Replica")))
986 return;
987 if (className == QLatin1String("QRemoteObjectDynamicReplica") || staticTypes.contains(akey: className))
988 return;
989 className.chop(n: 7); //Remove 'Replica' from name
990 staticTypes.insert(akey: className, avalue: metaObject);
991}
992
993void QRemoteObjectNodePrivate::connectReplica(QObject *object, QRemoteObjectReplica *instance)
994{
995 int nConnections = 0;
996 const QMetaObject *us = instance->metaObject();
997 const QMetaObject *them = object->metaObject();
998
999 static const int memberOffset = QRemoteObjectReplica::staticMetaObject.methodCount();
1000 for (int idx = memberOffset; idx < us->methodCount(); ++idx) {
1001 const QMetaMethod mm = us->method(index: idx);
1002
1003 qROPrivDebug() << idx << mm.name();
1004 if (mm.methodType() != QMetaMethod::Signal)
1005 continue;
1006
1007 // try to connect to a signal on the parent that has the same method signature
1008 QByteArray sig = QMetaObject::normalizedSignature(method: mm.methodSignature().constData());
1009 qROPrivDebug() << sig;
1010 if (them->indexOfSignal(signal: sig.constData()) == -1)
1011 continue;
1012
1013 sig.prepend(QSIGNAL_CODE + '0');
1014 const char * const csig = sig.constData();
1015 const bool res = QObject::connect(sender: object, signal: csig, receiver: instance, member: csig);
1016 Q_UNUSED(res);
1017 ++nConnections;
1018
1019 qROPrivDebug() << sig << res;
1020 }
1021
1022 qROPrivDebug() << "# connections =" << nConnections;
1023}
1024
1025void QRemoteObjectNodePrivate::openConnectionIfNeeded(const QString &name)
1026{
1027 qROPrivDebug() << Q_FUNC_INFO << name << this;
1028 if (!remoteObjectAddresses().contains(akey: name)) {
1029 qROPrivDebug() << name << "not available - available addresses:" << remoteObjectAddresses();
1030 return;
1031 }
1032
1033 if (!initConnection(address: remoteObjectAddresses().value(akey: name).hostUrl))
1034 qROPrivWarning() << "failed to open connection to" << name;
1035}
1036
1037bool QRemoteObjectNodePrivate::initConnection(const QUrl &address)
1038{
1039 Q_Q(QRemoteObjectNode);
1040 if (requestedUrls.contains(value: address)) {
1041 qROPrivDebug() << "Connection already requested for " << address.toString();
1042 return true;
1043 }
1044
1045 requestedUrls.insert(value: address);
1046
1047 if (schemaHandlers.contains(akey: address.scheme())) {
1048 schemaHandlers[address.scheme()](address);
1049 return true;
1050 }
1051
1052 ClientIoDevice *connection = QtROClientFactory::instance()->create(url: address, parent: q);
1053 if (!connection) {
1054 qROPrivWarning() << "Could not create ClientIoDevice for client. Invalid url/scheme provided?" << address;
1055 return false;
1056 }
1057 qROPrivDebug() << "Opening connection to" << address.toString();
1058 qROPrivDebug() << "Replica Connection isValid" << connection->isOpen();
1059 QObject::connect(sender: connection, signal: &ClientIoDevice::shouldReconnect, context: q, slot: [this, connection]() {
1060 onShouldReconnect(ioDevice: connection);
1061 });
1062 QObject::connect(sender: connection, signal: &IoDeviceBase::readyRead, context: q, slot: [this, connection]() {
1063 onClientRead(obj: connection);
1064 });
1065 connection->connectToServer();
1066
1067 return true;
1068}
1069
1070bool QRemoteObjectNodePrivate::hasInstance(const QString &name)
1071{
1072 if (!replicas.contains(akey: name))
1073 return false;
1074
1075 QSharedPointer<QReplicaImplementationInterface> rep = replicas.value(akey: name).toStrongRef();
1076 if (!rep) { //already deleted
1077 replicas.remove(akey: name);
1078 return false;
1079 }
1080
1081 return true;
1082}
1083
1084void QRemoteObjectNodePrivate::onRemoteObjectSourceAdded(const QRemoteObjectSourceLocation &entry)
1085{
1086 qROPrivDebug() << "onRemoteObjectSourceAdded" << entry << replicas << replicas.contains(akey: entry.first);
1087 if (!entry.first.isEmpty()) {
1088 QRemoteObjectSourceLocations locs = registry->sourceLocations();
1089 locs[entry.first] = entry.second;
1090 //TODO Is there a way to extend QRemoteObjectSourceLocations in place?
1091 registry->d_impl->setProperty(i: 0, QVariant::fromValue(value: locs));
1092 qROPrivDebug() << "onRemoteObjectSourceAdded, now locations =" << locs;
1093 }
1094 if (replicas.contains(akey: entry.first)) //We have a replica waiting on this remoteObject
1095 {
1096 QSharedPointer<QReplicaImplementationInterface> rep = replicas.value(akey: entry.first).toStrongRef();
1097 if (!rep) { //replica has been deleted, remove from list
1098 replicas.remove(akey: entry.first);
1099 return;
1100 }
1101
1102 initConnection(address: entry.second.hostUrl);
1103
1104 qROPrivDebug() << "Called initConnection due to new RemoteObjectSource added via registry" << entry.first;
1105 }
1106}
1107
1108void QRemoteObjectNodePrivate::onRemoteObjectSourceRemoved(const QRemoteObjectSourceLocation &entry)
1109{
1110 if (!entry.first.isEmpty()) {
1111 QRemoteObjectSourceLocations locs = registry->sourceLocations();
1112 locs.remove(akey: entry.first);
1113 registry->d_impl->setProperty(i: 0, QVariant::fromValue(value: locs));
1114 }
1115}
1116
1117void QRemoteObjectNodePrivate::onRegistryInitialized()
1118{
1119 qROPrivDebug() << "Registry Initialized" << remoteObjectAddresses();
1120
1121 const auto remotes = remoteObjectAddresses();
1122 for (auto i = remotes.cbegin(), end = remotes.cend(); i != end; ++i) {
1123 if (replicas.contains(akey: i.key())) //We have a replica waiting on this remoteObject
1124 {
1125 QSharedPointer<QReplicaImplementationInterface> rep = replicas.value(akey: i.key()).toStrongRef();
1126 if (rep && !requestedUrls.contains(value: i.value().hostUrl))
1127 initConnection(address: i.value().hostUrl);
1128 else if (!rep) //replica has been deleted, remove from list
1129 replicas.remove(akey: i.key());
1130
1131 continue;
1132 }
1133 }
1134}
1135
1136void QRemoteObjectNodePrivate::onShouldReconnect(ClientIoDevice *ioDevice)
1137{
1138 Q_Q(QRemoteObjectNode);
1139
1140 const auto remoteObjects = ioDevice->remoteObjects();
1141 for (const QString &remoteObject : remoteObjects) {
1142 connectedSources.remove(akey: remoteObject);
1143 ioDevice->removeSource(remoteObject);
1144 if (replicas.contains(akey: remoteObject)) { //We have a replica waiting on this remoteObject
1145 QSharedPointer<QConnectedReplicaImplementation> rep = qSharedPointerCast<QConnectedReplicaImplementation>(src: replicas.value(akey: remoteObject).toStrongRef());
1146 if (rep && !rep->connectionToSource.isNull()) {
1147 rep->setDisconnected();
1148 } else if (!rep) {
1149 replicas.remove(akey: remoteObject);
1150 }
1151 }
1152 }
1153 if (requestedUrls.contains(value: ioDevice->url())) {
1154 // Only try to reconnect to URLs requested via connectToNode
1155 // If we connected via registry, wait for the registry to see the node/source again
1156 pendingReconnect.insert(value: ioDevice);
1157 if (!reconnectTimer.isActive()) {
1158 reconnectTimer.start(msec: retryInterval, obj: q);
1159 qROPrivDebug() << "Starting reconnect timer";
1160 }
1161 } else {
1162 qROPrivDebug() << "Url" << ioDevice->url().toDisplayString().toLatin1()
1163 << "lost. We will reconnect Replicas if they reappear on the Registry.";
1164 }
1165}
1166
1167//This version of handleNewAcquire creates a QConnectedReplica. If this is a
1168//host node, the QRemoteObjectHostBasePrivate overload is called instead.
1169QReplicaImplementationInterface *QRemoteObjectNodePrivate::handleNewAcquire(const QMetaObject *meta, QRemoteObjectReplica *instance, const QString &name)
1170{
1171 Q_Q(QRemoteObjectNode);
1172 QConnectedReplicaImplementation *rp = new QConnectedReplicaImplementation(name, meta, q);
1173 rp->configurePrivate(instance);
1174 if (connectedSources.contains(akey: name)) { //Either we have a peer connections, or existing connection via registry
1175 handleReplicaConnection(sourceSignature: connectedSources[name].objectSignature, rep: rp, connection: connectedSources[name].device);
1176 } else {
1177 //No existing connection, but we know we can connect via registry
1178 const auto &sourceLocations = remoteObjectAddresses();
1179 const auto it = sourceLocations.constFind(akey: name);
1180 // This will try the connection, and if successful, the remoteObjects will be sent
1181 // The link to the replica will be handled then
1182 if (it != sourceLocations.constEnd())
1183 initConnection(address: it.value().hostUrl);
1184 }
1185 return rp;
1186}
1187
1188void QRemoteObjectNodePrivate::handleReplicaConnection(const QString &name)
1189{
1190 QSharedPointer<QRemoteObjectReplicaImplementation> rep = qSharedPointerCast<QRemoteObjectReplicaImplementation>(src: replicas.value(akey: name).toStrongRef());
1191 if (!rep) { //replica has been deleted, remove from list
1192 replicas.remove(akey: name);
1193 return;
1194 }
1195
1196 if (rep->isShortCircuit())
1197 return;
1198
1199 QConnectedReplicaImplementation *connectedRep = static_cast<QConnectedReplicaImplementation *>(rep.data());
1200 if (connectedRep->connectionToSource.isNull()) {
1201 const auto sourceInfo = connectedSources.value(akey: name);
1202 handleReplicaConnection(sourceSignature: sourceInfo.objectSignature, rep: connectedRep, connection: sourceInfo.device);
1203 }
1204}
1205
1206void QRemoteObjectNodePrivate::handleReplicaConnection(const QByteArray &sourceSignature, QConnectedReplicaImplementation *rep, IoDeviceBase *connection)
1207{
1208 if (!checkSignatures(a: rep->m_objectSignature, b: sourceSignature)) {
1209 qROPrivWarning() << "Signature mismatch for" << rep->m_metaObject->className() << (rep->m_objectName.isEmpty() ? QLatin1String("(unnamed)") : rep->m_objectName);
1210 rep->setState(QRemoteObjectReplica::SignatureMismatch);
1211 return;
1212 }
1213 rep->setConnection(connection);
1214}
1215
1216//Host Nodes can use the more efficient InProcess Replica if we (this Node) hold the Source for the
1217//requested Replica. If not, fall back to the Connected Replica case.
1218QReplicaImplementationInterface *QRemoteObjectHostBasePrivate::handleNewAcquire(const QMetaObject *meta, QRemoteObjectReplica *instance, const QString &name)
1219{
1220 QMap<QString, QRemoteObjectSourceBase*>::const_iterator mapIt;
1221 if (remoteObjectIo && map_contains(map: remoteObjectIo->m_sourceObjects, key: name, result&: mapIt)) {
1222 Q_Q(QRemoteObjectHostBase);
1223 QInProcessReplicaImplementation *rp = new QInProcessReplicaImplementation(name, meta, q);
1224 rp->configurePrivate(instance);
1225 connectReplica(object: mapIt.value()->m_object, instance);
1226 rp->connectionToSource = mapIt.value();
1227 return rp;
1228 }
1229 return QRemoteObjectNodePrivate::handleNewAcquire(meta, instance, name);
1230}
1231
1232void QRemoteObjectNodePrivate::onClientRead(QObject *obj)
1233{
1234 using namespace QRemoteObjectPackets;
1235 IoDeviceBase *connection = qobject_cast<IoDeviceBase*>(object: obj);
1236 QRemoteObjectPacketTypeEnum packetType;
1237 Q_ASSERT(connection);
1238
1239 do {
1240 if (!connection->read(packetType, rxName))
1241 return;
1242
1243 if (packetType != Handshake && !m_handshakeReceived) {
1244 qROPrivWarning() << "Expected Handshake, got " << packetType;
1245 setLastError(QRemoteObjectNode::ProtocolMismatch);
1246 connection->close();
1247 break;
1248 }
1249
1250 switch (packetType) {
1251 case Pong:
1252 {
1253 QSharedPointer<QRemoteObjectReplicaImplementation> rep = qSharedPointerCast<QRemoteObjectReplicaImplementation>(src: replicas.value(akey: rxName).toStrongRef());
1254 if (rep)
1255 rep->notifyAboutReply(0, {});
1256 else //replica has been deleted, remove from list
1257 replicas.remove(akey: rxName);
1258 break;
1259 }
1260 case Handshake:
1261 if (rxName != QtRemoteObjects::protocolVersion) {
1262 qWarning() << "*** Protocol Mismatch, closing connection ***. Got" << rxName << "expected" << QtRemoteObjects::protocolVersion;
1263 setLastError(QRemoteObjectNode::ProtocolMismatch);
1264 connection->close();
1265 } else {
1266 m_handshakeReceived = true;
1267 }
1268 break;
1269 case ObjectList:
1270 {
1271 deserializeObjectListPacket(connection->stream(), rxObjects);
1272 qROPrivDebug() << "newObjects:" << rxObjects;
1273 // We need to make sure all of the source objects are in connectedSources before we add connections,
1274 // otherwise nested QObjects could fail (we want to acquire children before parents, and the object
1275 // list is unordered)
1276 for (const auto &remoteObject : qAsConst(t&: rxObjects)) {
1277 qROPrivDebug() << " connectedSources.contains(" << remoteObject << ")" << connectedSources.contains(akey: remoteObject.name) << replicas.contains(akey: remoteObject.name);
1278 if (!connectedSources.contains(akey: remoteObject.name)) {
1279 connectedSources[remoteObject.name] = SourceInfo{.device: connection, .typeName: remoteObject.typeName, .objectSignature: remoteObject.signature};
1280 connection->addSource(remoteObject.name);
1281 // Make sure we handle Registry first if it is available
1282 if (remoteObject.name == QLatin1String("Registry") && replicas.contains(akey: remoteObject.name))
1283 handleReplicaConnection(name: remoteObject.name);
1284 }
1285 }
1286 for (const auto &remoteObject : qAsConst(t&: rxObjects)) {
1287 if (replicas.contains(akey: remoteObject.name)) //We have a replica waiting on this remoteObject
1288 handleReplicaConnection(name: remoteObject.name);
1289 }
1290 break;
1291 }
1292 case InitPacket:
1293 {
1294 qROPrivDebug() << "InitPacket-->" << rxName << this;
1295 QSharedPointer<QConnectedReplicaImplementation> rep = qSharedPointerCast<QConnectedReplicaImplementation>(src: replicas.value(akey: rxName).toStrongRef());
1296 //Use m_rxArgs (a QVariantList to hold the properties QVariantList)
1297 deserializeInitPacket(connection->stream(), rxArgs);
1298 if (rep)
1299 {
1300 handlePointerToQObjectProperties(rep: rep.data(), properties&: rxArgs);
1301 rep->initialize(values&: rxArgs);
1302 } else { //replica has been deleted, remove from list
1303 replicas.remove(akey: rxName);
1304 }
1305 break;
1306 }
1307 case InitDynamicPacket:
1308 {
1309 qROPrivDebug() << "InitDynamicPacket-->" << rxName << this;
1310 const QMetaObject *meta = dynamicTypeManager.addDynamicType(connection, in&: connection->stream());
1311 deserializeInitPacket(connection->stream(), rxArgs);
1312 QSharedPointer<QConnectedReplicaImplementation> rep = qSharedPointerCast<QConnectedReplicaImplementation>(src: replicas.value(akey: rxName).toStrongRef());
1313 if (rep)
1314 {
1315 rep->setDynamicMetaObject(meta);
1316 handlePointerToQObjectProperties(rep: rep.data(), properties&: rxArgs);
1317 rep->setDynamicProperties(rxArgs);
1318 } else { //replica has been deleted, remove from list
1319 replicas.remove(akey: rxName);
1320 }
1321 break;
1322 }
1323 case RemoveObject:
1324 {
1325 qROPrivDebug() << "RemoveObject-->" << rxName << this;
1326 connectedSources.remove(akey: rxName);
1327 connection->removeSource(rxName);
1328 if (replicas.contains(akey: rxName)) { //We have a replica using the removed source
1329 QSharedPointer<QConnectedReplicaImplementation> rep = qSharedPointerCast<QConnectedReplicaImplementation>(src: replicas.value(akey: rxName).toStrongRef());
1330 if (rep && !rep->connectionToSource.isNull()) {
1331 rep->connectionToSource.clear();
1332 rep->setState(QRemoteObjectReplica::Suspect);
1333 } else if (!rep) {
1334 replicas.remove(akey: rxName);
1335 }
1336 }
1337 break;
1338 }
1339 case PropertyChangePacket:
1340 {
1341 int propertyIndex;
1342 deserializePropertyChangePacket(in&: connection->stream(), index&: propertyIndex, value&: rxValue);
1343 QSharedPointer<QRemoteObjectReplicaImplementation> rep = qSharedPointerCast<QRemoteObjectReplicaImplementation>(src: replicas.value(akey: rxName).toStrongRef());
1344 if (rep) {
1345 QConnectedReplicaImplementation *connectedRep = nullptr;
1346 if (!rep->isShortCircuit()) {
1347 connectedRep = static_cast<QConnectedReplicaImplementation *>(rep.data());
1348 if (!connectedRep->childIndices().contains(t: propertyIndex))
1349 connectedRep = nullptr; //connectedRep will be a valid pointer only if propertyIndex is a child index
1350 }
1351 if (connectedRep)
1352 rep->setProperty(i: propertyIndex, handlePointerToQObjectProperty(rep: connectedRep, index: propertyIndex, property: rxValue));
1353 else {
1354 const QMetaProperty property = rep->m_metaObject->property(index: propertyIndex + rep->m_metaObject->propertyOffset());
1355 if (property.userType() == QMetaType::QVariant && rxValue.canConvert<QRO_>()) {
1356 // This is a type that requires registration
1357 QRO_ typeInfo = rxValue.value<QRO_>();
1358 QDataStream in(typeInfo.classDefinition);
1359 parseGadgets(connection, in);
1360 QDataStream ds(typeInfo.parameters);
1361 ds >> rxValue;
1362 }
1363 rep->setProperty(i: propertyIndex, decodeVariant(value&: rxValue, type: property.userType()));
1364 }
1365 } else { //replica has been deleted, remove from list
1366 replicas.remove(akey: rxName);
1367 }
1368 break;
1369 }
1370 case InvokePacket:
1371 {
1372 int call, index, serialId, propertyIndex;
1373 deserializeInvokePacket(in&: connection->stream(), call, index, args&: rxArgs, serialId, propertyIndex);
1374 QSharedPointer<QRemoteObjectReplicaImplementation> rep = qSharedPointerCast<QRemoteObjectReplicaImplementation>(src: replicas.value(akey: rxName).toStrongRef());
1375 if (rep) {
1376 static QVariant null(QMetaType::QObjectStar, (void*)0);
1377 QVariant paramValue;
1378 // Qt usually supports 9 arguments, so ten should be usually safe
1379 QVarLengthArray<void*, 10> param(rxArgs.size() + 1);
1380 param[0] = null.data(); //Never a return value
1381 if (rxArgs.size()) {
1382 auto signal = rep->m_metaObject->method(index: index+rep->m_signalOffset);
1383 for (int i = 0; i < rxArgs.size(); i++) {
1384 if (signal.parameterType(index: i) == QMetaType::QVariant)
1385 param[i + 1] = const_cast<void*>(reinterpret_cast<const void*>(&rxArgs.at(i)));
1386 else {
1387 decodeVariant(value&: rxArgs[i], type: signal.parameterType(index: i));
1388 param[i + 1] = const_cast<void *>(rxArgs.at(i).data());
1389 }
1390 }
1391 } else if (propertyIndex != -1) {
1392 param.resize(asize: 2);
1393 paramValue = rep->getProperty(i: propertyIndex);
1394 param[1] = paramValue.data();
1395 }
1396 qROPrivDebug() << "Replica Invoke-->" << rxName << rep->m_metaObject->method(index: index+rep->m_signalOffset).name() << index << rep->m_signalOffset;
1397 // We activate on rep->metaobject() so the private metacall is used, not m_metaobject (which
1398 // is the class thie replica looks like)
1399 QMetaObject::activate(sender: rep.data(), rep->metaObject(), local_signal_index: index+rep->m_signalOffset, argv: param.data());
1400 } else { //replica has been deleted, remove from list
1401 replicas.remove(akey: rxName);
1402 }
1403 break;
1404 }
1405 case InvokeReplyPacket:
1406 {
1407 int ackedSerialId;
1408 deserializeInvokeReplyPacket(in&: connection->stream(), ackedSerialId, value&: rxValue);
1409 QSharedPointer<QRemoteObjectReplicaImplementation> rep = qSharedPointerCast<QRemoteObjectReplicaImplementation>(src: replicas.value(akey: rxName).toStrongRef());
1410 if (rep) {
1411 qROPrivDebug() << "Received InvokeReplyPacket ack'ing serial id:" << ackedSerialId;
1412 rep->notifyAboutReply(ackedSerialId, rxValue);
1413 } else { //replica has been deleted, remove from list
1414 replicas.remove(akey: rxName);
1415 }
1416 break;
1417 }
1418 case AddObject:
1419 case Invalid:
1420 case Ping:
1421 qROPrivWarning() << "Unexpected packet received";
1422 }
1423 } while (connection->bytesAvailable()); // have bytes left over, so do another iteration
1424}
1425
1426/*!
1427 \class QRemoteObjectNode
1428 \inmodule QtRemoteObjects
1429 \brief A node on a Qt Remote Objects network.
1430
1431 The QRemoteObjectNode class provides an entry point to a QtRemoteObjects
1432 network. A network can be as simple as two nodes, or an arbitrarily complex
1433 set of processes and devices.
1434
1435 A QRemoteObjectNode does not have a url that other nodes can connect to,
1436 and thus is able to acquire replicas only. It is not able to share source
1437 objects (only QRemoteObjectHost and QRemoteObjectRegistryHost Nodes can
1438 share).
1439
1440 Nodes may connect to each other directly using \l connectToNode, or
1441 they can use the QRemoteObjectRegistry to simplify connections.
1442
1443 The QRemoteObjectRegistry is a special replica available to every node that
1444 connects to the Registry Url. It knows how to connect to every
1445 QRemoteObjectSource object on the network.
1446
1447 \sa QRemoteObjectHost, QRemoteObjectRegistryHost
1448*/
1449
1450/*!
1451 \class QRemoteObjectHostBase
1452 \inmodule QtRemoteObjects
1453 \brief The QRemoteObjectHostBase class provides base functionality common to \l
1454 {QRemoteObjectHost} {Host} and \l {QRemoteObjectRegistryHost} {RegistryHost} classes.
1455
1456 QRemoteObjectHostBase is a base class that cannot be instantiated directly.
1457 It provides the enableRemoting and disableRemoting functionality shared by
1458 all host nodes (\l {QRemoteObjectHost} {Host} and \l
1459 {QRemoteObjectRegistryHost} {RegistryHost}) as well as the logic required
1460 to expose \l {Source} objects on the Remote Objects network.
1461*/
1462
1463/*!
1464 \class QRemoteObjectHost
1465 \inmodule QtRemoteObjects
1466 \brief A (Host) Node on a Qt Remote Objects network.
1467
1468 The QRemoteObjectHost class provides an entry point to a QtRemoteObjects
1469 network. A network can be as simple as two nodes, or an arbitrarily complex
1470 set of processes and devices.
1471
1472 QRemoteObjectHosts have the same capabilities as QRemoteObjectNodes, but
1473 they can also be connected to and can share source objects on the network.
1474
1475 Nodes may connect to each other directly using \l connectToNode, or they
1476 can use the QRemoteObjectRegistry to simplify connections.
1477
1478 The QRemoteObjectRegistry is a special replica available to every node that
1479 connects to the uegistry Url. It knows how to connect to every
1480 QRemoteObjectSource object on the network.
1481
1482 \sa QRemoteObjectNode, QRemoteObjectRegistryHost
1483*/
1484
1485/*!
1486 \class QRemoteObjectRegistryHost
1487 \inmodule QtRemoteObjects
1488 \brief A (Host/Registry) node on a Qt Remote Objects network.
1489
1490 The QRemoteObjectRegistryHost class provides an entry point to a QtRemoteObjects
1491 network. A network can be as simple as two Nodes, or an arbitrarily complex
1492 set of processes and devices.
1493
1494 A QRemoteObjectRegistryHost has the same capability that a
1495 QRemoteObjectHost has (which includes everything a QRemoteObjectNode
1496 supports), and in addition is the owner of the Registry. Any
1497 QRemoteObjectHost node that connects to this Node will have all of their
1498 Source objects made available by the Registry.
1499
1500 Nodes only support connection to one \l registry, calling \l
1501 QRemoteObjectNode::setRegistryUrl when a Registry is already set is
1502 considered an error. For something like a secure and insecure network
1503 (where different Registries would be applicable), the recommendation is to
1504 create separate Nodes to connect to each, in effect creating two
1505 independent Qt Remote Objects networks.
1506
1507 Nodes may connect to each other directly using \l connectToNode, or they
1508 can use the QRemoteObjectRegistry to simplify connections.
1509
1510 The QRemoteObjectRegistry is a special Replica available to every Node that
1511 connects to the Registry Url. It knows how to connect to every
1512 QRemoteObjectSource object on the network.
1513
1514 \sa QRemoteObjectNode, QRemoteObjectHost
1515*/
1516
1517/*!
1518 \enum QRemoteObjectNode::ErrorCode
1519
1520 This enum type specifies the various error codes associated with QRemoteObjectNode errors:
1521
1522 \value NoError No error.
1523 \value RegistryNotAcquired The registry could not be acquired.
1524 \value RegistryAlreadyHosted The registry is already defined and hosting Sources.
1525 \value NodeIsNoServer The given QRemoteObjectNode is not a host node.
1526 \value ServerAlreadyCreated The host node has already been initialized.
1527 \value UnintendedRegistryHosting An attempt was made to create a host QRemoteObjectNode and connect to itself as the registry.
1528 \value OperationNotValidOnClientNode The attempted operation is not valid on a client QRemoteObjectNode.
1529 \value SourceNotRegistered The given QRemoteObjectSource is not registered on this node.
1530 \value MissingObjectName The given QObject does not have objectName() set.
1531 \value HostUrlInvalid The given url has an invalid or unrecognized scheme.
1532 \value ProtocolMismatch The client and the server have different protocol versions.
1533 \value ListenFailed Can't listen on the specified host port.
1534*/
1535
1536/*!
1537 \enum QRemoteObjectHostBase::AllowedSchemas
1538
1539 This enum is used to specify whether a Node will accept a url with an
1540 unrecognized schema for the hostUrl. By default only urls with known
1541 schemas are accepted, but using \c AllowExternalRegistration will enable
1542 the \l Registry to pass your external (to QtRO) url to client Nodes.
1543
1544 \value BuiltInSchemasOnly Only allow the hostUrl to be set to a QtRO
1545 supported schema. This is the default value, and causes a Node error to be
1546 set if an unrecognized schema is provided.
1547 \value AllowExternalRegistration The provided schema is registered as an
1548 \l {External Schemas}{External Schema}
1549
1550 \sa QRemoteObjectHost
1551*/
1552
1553/*!
1554 \fn ObjectType *QRemoteObjectNode::acquire(const QString &name)
1555
1556 Returns a pointer to a Replica of type ObjectType (which is a template
1557 parameter and must inherit from \l QRemoteObjectReplica). That is, the
1558 template parameter must be a \l {repc} generated type. The \a name
1559 parameter can be used to specify the \a name given to the object
1560 during the QRemoteObjectHost::enableRemoting() call.
1561*/
1562
1563void QRemoteObjectNodePrivate::initialize()
1564{
1565 qRegisterMetaType<QRemoteObjectNode *>();
1566 qRegisterMetaType<QRemoteObjectNode::ErrorCode>();
1567 qRegisterMetaType<QAbstractSocket::SocketError>(); //For queued qnx error()
1568 qRegisterMetaTypeStreamOperators<QVector<int> >();
1569 qRegisterMetaTypeStreamOperators<QRemoteObjectPackets::QRO_>();
1570 // To support dynamic MODELs, we need to make sure the types are registered
1571 QAbstractItemModelSourceAdapter::registerTypes();
1572}
1573
1574bool QRemoteObjectNodePrivate::checkSignatures(const QByteArray &a, const QByteArray &b)
1575{
1576 // if any of a or b is empty it means it's a dynamic ojects or an item model
1577 if (a.isEmpty() || b.isEmpty())
1578 return true;
1579 return a == b;
1580}
1581
1582
1583void QRemoteObjectNode::persistProperties(const QString &repName, const QByteArray &repSig, const QVariantList &props)
1584{
1585 Q_D(QRemoteObjectNode);
1586 if (d->persistedStore) {
1587 d->persistedStore->saveProperties(repName, repSig, values: props);
1588 } else {
1589 qCWarning(QT_REMOTEOBJECT) << qPrintable(objectName()) << "Unable to store persisted properties for" << repName;
1590 qCWarning(QT_REMOTEOBJECT) << " No persisted store set.";
1591 }
1592}
1593
1594QVariantList QRemoteObjectNode::retrieveProperties(const QString &repName, const QByteArray &repSig)
1595{
1596 Q_D(QRemoteObjectNode);
1597 if (d->persistedStore) {
1598 return d->persistedStore->restoreProperties(repName, repSig);
1599 }
1600 qCWarning(QT_REMOTEOBJECT) << qPrintable(objectName()) << "Unable to retrieve persisted properties for" << repName;
1601 qCWarning(QT_REMOTEOBJECT) << " No persisted store set.";
1602 return QVariantList();
1603}
1604
1605/*!
1606 Default constructor for QRemoteObjectNode with the given \a parent. A Node
1607 constructed in this manner can not be connected to, and thus can not expose
1608 Source objects on the network. It also will not include a \l
1609 QRemoteObjectRegistry, unless set manually using setRegistryUrl.
1610
1611 \sa connectToNode, setRegistryUrl
1612*/
1613QRemoteObjectNode::QRemoteObjectNode(QObject *parent)
1614 : QObject(*new QRemoteObjectNodePrivate, parent)
1615{
1616 Q_D(QRemoteObjectNode);
1617 d->initialize();
1618}
1619
1620/*!
1621 QRemoteObjectNode connected to a {QRemoteObjectRegistry} {Registry}. A Node
1622 constructed in this manner can not be connected to, and thus can not expose
1623 Source objects on the network. Finding and connecting to other (Host) Nodes
1624 is handled by the QRemoteObjectRegistry specified by \a registryAddress.
1625
1626 \sa connectToNode, setRegistryUrl, QRemoteObjectHost, QRemoteObjectRegistryHost
1627*/
1628QRemoteObjectNode::QRemoteObjectNode(const QUrl &registryAddress, QObject *parent)
1629 : QObject(*new QRemoteObjectNodePrivate, parent)
1630{
1631 Q_D(QRemoteObjectNode);
1632 d->initialize();
1633 setRegistryUrl(registryAddress);
1634}
1635
1636QRemoteObjectNode::QRemoteObjectNode(QRemoteObjectNodePrivate &dptr, QObject *parent)
1637 : QObject(dptr, parent)
1638{
1639 Q_D(QRemoteObjectNode);
1640 d->initialize();
1641}
1642
1643/*!
1644 \qmltype Host
1645 \instantiates QRemoteObjectHost
1646 \inqmlmodule QtRemoteObjects
1647 \brief A host node on a Qt Remote Objects network.
1648
1649 The Host type provides an entry point to a Qt Remote Objects network. A network
1650 can be as simple as two nodes, or an arbitrarily complex set of processes and devices.
1651
1652 Hosts have the same capabilities as Nodes, but they can also be connected to and can
1653 share source objects on the network.
1654*/
1655
1656/*!
1657 \internal This is a base class for both QRemoteObjectHost and
1658 QRemoteObjectRegistryHost to provide the shared features/functions for
1659 sharing \l Source objects.
1660*/
1661QRemoteObjectHostBase::QRemoteObjectHostBase(QRemoteObjectHostBasePrivate &d, QObject *parent)
1662 : QRemoteObjectNode(d, parent)
1663{ }
1664
1665/*!
1666 Constructs a new QRemoteObjectHost Node (i.e., a Node that supports
1667 exposing \l Source objects on the QtRO network) with the given \a parent.
1668 This constructor is meant specific to support QML in the future as it will
1669 not be available to connect to until \l {QRemoteObjectHost::}{setHostUrl} is called.
1670
1671 \sa setHostUrl(), setRegistryUrl()
1672*/
1673QRemoteObjectHost::QRemoteObjectHost(QObject *parent)
1674 : QRemoteObjectHostBase(*new QRemoteObjectHostPrivate, parent)
1675{ }
1676
1677/*!
1678 Constructs a new QRemoteObjectHost Node (i.e., a Node that supports
1679 exposing \l Source objects on the QtRO network) with address \a address. If
1680 set, \a registryAddress will be used to connect to the \l
1681 QRemoteObjectRegistry at the provided address. The \a allowedSchemas
1682 parameter is only needed (and should be set to \l
1683 {QRemoteObjectHostBase::AllowExternalRegistration}
1684 {AllowExternalRegistration}) if the schema of the url should be used as an
1685 \l {External Schemas} {External Schema} by the registry.
1686
1687 \sa setHostUrl(), setRegistryUrl()
1688*/
1689QRemoteObjectHost::QRemoteObjectHost(const QUrl &address, const QUrl &registryAddress,
1690 AllowedSchemas allowedSchemas, QObject *parent)
1691 : QRemoteObjectHostBase(*new QRemoteObjectHostPrivate, parent)
1692{
1693 if (!address.isEmpty()) {
1694 if (!setHostUrl(hostAddress: address, allowedSchemas))
1695 return;
1696 }
1697
1698 if (!registryAddress.isEmpty())
1699 setRegistryUrl(registryAddress);
1700}
1701
1702/*!
1703 Constructs a new QRemoteObjectHost Node (i.e., a Node that supports
1704 exposing \l Source objects on the QtRO network) with a url of \a
1705 address and the given \a parent. This overload is provided as a convenience for specifying a
1706 QObject parent without providing a registry address.
1707
1708 \sa setHostUrl(), setRegistryUrl()
1709*/
1710QRemoteObjectHost::QRemoteObjectHost(const QUrl &address, QObject *parent)
1711 : QRemoteObjectHostBase(*new QRemoteObjectHostPrivate, parent)
1712{
1713 if (!address.isEmpty())
1714 setHostUrl(hostAddress: address);
1715}
1716
1717/*!
1718 \internal QRemoteObjectHost::QRemoteObjectHost
1719*/
1720QRemoteObjectHost::QRemoteObjectHost(QRemoteObjectHostPrivate &d, QObject *parent)
1721 : QRemoteObjectHostBase(d, parent)
1722{ }
1723
1724QRemoteObjectHost::~QRemoteObjectHost() {}
1725
1726/*!
1727 Constructs a new QRemoteObjectRegistryHost Node with the given \a parent. RegistryHost Nodes have
1728 the same functionality as \l QRemoteObjectHost Nodes, except rather than
1729 being able to connect to a \l QRemoteObjectRegistry, the provided Host QUrl
1730 (\a registryAddress) becomes the address of the registry for other Nodes to
1731 connect to.
1732*/
1733QRemoteObjectRegistryHost::QRemoteObjectRegistryHost(const QUrl &registryAddress, QObject *parent)
1734 : QRemoteObjectHostBase(*new QRemoteObjectRegistryHostPrivate, parent)
1735{
1736 if (registryAddress.isEmpty())
1737 return;
1738
1739 setRegistryUrl(registryAddress);
1740}
1741
1742/*!
1743 \internal
1744*/
1745QRemoteObjectRegistryHost::QRemoteObjectRegistryHost(QRemoteObjectRegistryHostPrivate &d, QObject *parent)
1746 : QRemoteObjectHostBase(d, parent)
1747{ }
1748
1749QRemoteObjectRegistryHost::~QRemoteObjectRegistryHost() {}
1750
1751QRemoteObjectNode::~QRemoteObjectNode()
1752{ }
1753
1754QRemoteObjectHostBase::~QRemoteObjectHostBase()
1755{ }
1756
1757/*!
1758 Sets \a name as the internal name for this Node. This
1759 is then output as part of the logging (if enabled).
1760 This is primarily useful if you merge log data from multiple nodes.
1761*/
1762void QRemoteObjectNode::setName(const QString &name)
1763{
1764 setObjectName(name);
1765}
1766
1767/*!
1768 Similar to QObject::setObjectName() (which this method calls), but this
1769 version also applies the \a name to internal classes as well, which are
1770 used in some of the debugging output.
1771*/
1772void QRemoteObjectHostBase::setName(const QString &name)
1773{
1774 Q_D(QRemoteObjectHostBase);
1775 setObjectName(name);
1776 if (d->remoteObjectIo)
1777 d->remoteObjectIo->setObjectName(name);
1778}
1779
1780/*!
1781 \internal The HostBase version of this method is protected so the method
1782 isn't exposed on RegistryHost nodes.
1783*/
1784QUrl QRemoteObjectHostBase::hostUrl() const
1785{
1786 Q_D(const QRemoteObjectHostBase);
1787 if (d->remoteObjectIo)
1788 return d->remoteObjectIo->serverAddress();
1789 return QUrl();
1790}
1791
1792/*!
1793 \internal The HostBase version of this method is protected so the method
1794 isn't exposed on RegistryHost nodes.
1795*/
1796bool QRemoteObjectHostBase::setHostUrl(const QUrl &hostAddress, AllowedSchemas allowedSchemas)
1797{
1798 Q_D(QRemoteObjectHostBase);
1799 if (d->remoteObjectIo) {
1800 d->setLastError(ServerAlreadyCreated);
1801 return false;
1802 }
1803
1804 if (allowedSchemas == AllowedSchemas::BuiltInSchemasOnly && !QtROServerFactory::instance()->isValid(url: hostAddress)) {
1805 d->setLastError(HostUrlInvalid);
1806 return false;
1807 }
1808
1809 if (allowedSchemas == AllowedSchemas::AllowExternalRegistration && QtROServerFactory::instance()->isValid(url: hostAddress)) {
1810 qWarning() << qPrintable(objectName()) << "Overriding a valid QtRO url (" << hostAddress << ") with AllowExternalRegistration is not allowed.";
1811 d->setLastError(HostUrlInvalid);
1812 return false;
1813 }
1814 d->remoteObjectIo = new QRemoteObjectSourceIo(hostAddress, this);
1815
1816 if (allowedSchemas == AllowedSchemas::BuiltInSchemasOnly && !d->remoteObjectIo->startListening()) {
1817 d->setLastError(ListenFailed);
1818 delete d->remoteObjectIo;
1819 d->remoteObjectIo = nullptr;
1820 return false;
1821 }
1822
1823 //If we've given a name to the node, set it on the sourceIo as well
1824 if (!objectName().isEmpty())
1825 d->remoteObjectIo->setObjectName(objectName());
1826 //Since we don't know whether setHostUrl or setRegistryUrl/setRegistryHost will be called first,
1827 //break it into two pieces. setHostUrl connects the RemoteObjectSourceIo->[add/remove]RemoteObjectSource to QRemoteObjectReplicaNode->[add/remove]RemoteObjectSource
1828 //setRegistry* calls appropriately connect RemoteObjecSourcetIo->[add/remove]RemoteObjectSource to the registry when it is created
1829 QObject::connect(sender: d->remoteObjectIo, signal: &QRemoteObjectSourceIo::remoteObjectAdded, receiver: this, slot: &QRemoteObjectHostBase::remoteObjectAdded);
1830 QObject::connect(sender: d->remoteObjectIo, signal: &QRemoteObjectSourceIo::remoteObjectRemoved, receiver: this, slot: &QRemoteObjectHostBase::remoteObjectRemoved);
1831
1832 return true;
1833}
1834
1835/*!
1836 \qmlproperty url Host::hostUrl
1837
1838 The host address for the node.
1839
1840 This is the address where source objects remoted by this node will reside.
1841*/
1842
1843/*!
1844 \property QRemoteObjectHost::hostUrl
1845 \brief The host address for the node.
1846
1847 This is the address where source objects remoted by this node will reside.
1848*/
1849
1850/*!
1851 Returns the host address for the QRemoteObjectNode as a QUrl. If the Node
1852 is not a Host node, returns an empty QUrl.
1853
1854 \sa setHostUrl()
1855*/
1856QUrl QRemoteObjectHost::hostUrl() const
1857{
1858 return QRemoteObjectHostBase::hostUrl();
1859}
1860
1861/*!
1862 Sets the \a hostAddress for a host QRemoteObjectNode.
1863
1864 Returns \c true if the Host address is set, otherwise \c false.
1865
1866 The \a allowedSchemas parameter is only needed (and should be set to \l
1867 {QRemoteObjectHostBase::AllowExternalRegistration}
1868 {AllowExternalRegistration}) if the schema of the url should be used as an
1869 \l {External Schemas} {External Schema} by the registry.
1870*/
1871bool QRemoteObjectHost::setHostUrl(const QUrl &hostAddress, AllowedSchemas allowedSchemas)
1872{
1873 bool success = QRemoteObjectHostBase::setHostUrl(hostAddress, allowedSchemas);
1874 if (success)
1875 emit hostUrlChanged();
1876 return success;
1877}
1878
1879/*!
1880 This method can be used to set the address of this Node to \a registryUrl
1881 (used for other Nodes to connect to this one), if the QUrl isn't set in the
1882 constructor. Since this Node becomes the Registry, calling this setter
1883 method causes this Node to use the url as the host address. All other
1884 Node's use the \l {QRemoteObjectNode::setRegistryUrl} method initiate a
1885 connection to the Registry.
1886
1887 Returns \c true if the registry address is set, otherwise \c false.
1888
1889 \sa QRemoteObjectRegistryHost(), QRemoteObjectNode::setRegistryUrl
1890*/
1891bool QRemoteObjectRegistryHost::setRegistryUrl(const QUrl &registryUrl)
1892{
1893 Q_D(QRemoteObjectRegistryHost);
1894 if (setHostUrl(hostAddress: registryUrl)) {
1895 if (!d->remoteObjectIo) {
1896 d->setLastError(ServerAlreadyCreated);
1897 return false;
1898 } else if (d->registry) {
1899 d->setLastError(RegistryAlreadyHosted);
1900 return false;
1901 }
1902
1903 QRegistrySource *remoteObject = new QRegistrySource(this);
1904 enableRemoting(object: remoteObject);
1905 d->registryAddress = d->remoteObjectIo->serverAddress();
1906 d->registrySource = remoteObject;
1907 //Connect RemoteObjectSourceIo->remoteObject[Added/Removde] to the registry Slot
1908 QObject::connect(sender: this, signal: &QRemoteObjectRegistryHost::remoteObjectAdded, receiver: d->registrySource, slot: &QRegistrySource::addSource);
1909 QObject::connect(sender: this, signal: &QRemoteObjectRegistryHost::remoteObjectRemoved, receiver: d->registrySource, slot: &QRegistrySource::removeSource);
1910 QObject::connect(sender: d->remoteObjectIo, signal: &QRemoteObjectSourceIo::serverRemoved,receiver: d->registrySource, slot: &QRegistrySource::removeServer);
1911 //onAdd/Remove update the known remoteObjects list in the RegistrySource, so no need to connect to the RegistrySource remoteObjectAdded/Removed signals
1912 d->setRegistry(acquire<QRemoteObjectRegistry>());
1913 return true;
1914 }
1915 return false;
1916}
1917
1918/*!
1919 Returns the last error set.
1920*/
1921QRemoteObjectNode::ErrorCode QRemoteObjectNode::lastError() const
1922{
1923 Q_D(const QRemoteObjectNode);
1924 return d->lastError;
1925}
1926
1927/*!
1928 \qmlproperty url Node::registryUrl
1929
1930 The address of the \l {QRemoteObjectRegistry} {Registry} used by this node.
1931
1932 This is an empty QUrl if there is no registry in use.
1933*/
1934
1935/*!
1936 \property QRemoteObjectNode::registryUrl
1937 \brief The address of the \l {QRemoteObjectRegistry} {Registry} used by this node.
1938
1939 This is an empty QUrl if there is no registry in use.
1940*/
1941QUrl QRemoteObjectNode::registryUrl() const
1942{
1943 Q_D(const QRemoteObjectNode);
1944 return d->registryAddress;
1945}
1946
1947bool QRemoteObjectNode::setRegistryUrl(const QUrl &registryAddress)
1948{
1949 Q_D(QRemoteObjectNode);
1950 if (d->registry) {
1951 d->setLastError(RegistryAlreadyHosted);
1952 return false;
1953 }
1954
1955 d->registryAddress = registryAddress;
1956 d->setRegistry(acquire<QRemoteObjectRegistry>());
1957 //Connect remoteObject[Added/Removed] to the registry Slot
1958 QObject::connect(sender: this, signal: &QRemoteObjectNode::remoteObjectAdded, receiver: d->registry, slot: &QRemoteObjectRegistry::addSource);
1959 QObject::connect(sender: this, signal: &QRemoteObjectNode::remoteObjectRemoved, receiver: d->registry, slot: &QRemoteObjectRegistry::removeSource);
1960 connectToNode(address: registryAddress);
1961 return true;
1962}
1963
1964void QRemoteObjectNodePrivate::setRegistry(QRemoteObjectRegistry *reg)
1965{
1966 Q_Q(QRemoteObjectNode);
1967 registry = reg;
1968 reg->setParent(q);
1969 //Make sure when we get the registry initialized, we update our replicas
1970 QObject::connect(sender: reg, signal: &QRemoteObjectRegistry::initialized, context: q, slot: [this]() {
1971 onRegistryInitialized();
1972 });
1973 //Make sure we handle new RemoteObjectSources on Registry...
1974 QObject::connect(sender: reg, signal: &QRemoteObjectRegistry::remoteObjectAdded,
1975 context: q, slot: [this](const QRemoteObjectSourceLocation &location) {
1976 onRemoteObjectSourceAdded(entry: location);
1977 });
1978 QObject::connect(sender: reg, signal: &QRemoteObjectRegistry::remoteObjectRemoved,
1979 context: q, slot: [this](const QRemoteObjectSourceLocation &location) {
1980 onRemoteObjectSourceRemoved(entry: location);
1981 });
1982}
1983
1984QVariant QRemoteObjectNodePrivate::handlePointerToQObjectProperty(QConnectedReplicaImplementation *rep, int index, const QVariant &property)
1985{
1986 Q_Q(QRemoteObjectNode);
1987 using namespace QRemoteObjectPackets;
1988
1989 QVariant retval;
1990
1991 Q_ASSERT(property.canConvert<QRO_>());
1992 QRO_ childInfo = property.value<QRO_>();
1993 qROPrivDebug() << "QRO_:" << childInfo.name << replicas.contains(akey: childInfo.name) << replicas.keys();
1994 if (childInfo.isNull) {
1995 // Either the source has changed the pointer and we need to update it, or the source pointer is a nullptr
1996 if (replicas.contains(akey: childInfo.name))
1997 replicas.remove(akey: childInfo.name);
1998 if (childInfo.type == ObjectType::CLASS)
1999 retval = QVariant::fromValue<QRemoteObjectDynamicReplica*>(value: nullptr);
2000 else
2001 retval = QVariant::fromValue<QAbstractItemModelReplica*>(value: nullptr);
2002 return retval;
2003 }
2004
2005 const bool newReplica = !replicas.contains(akey: childInfo.name) || rep->isInitialized();
2006 if (newReplica) {
2007 if (rep->isInitialized()) {
2008 auto childRep = qSharedPointerCast<QConnectedReplicaImplementation>(src: replicas.take(akey: childInfo.name));
2009 if (childRep && !childRep->isShortCircuit()) {
2010 qCDebug(QT_REMOTEOBJECT) << "Checking if dynamic type should be added to dynamicTypeManager (type =" << childRep->m_metaObject->className() << ")";
2011 dynamicTypeManager.addFromMetaObject(metaObject: childRep->m_metaObject);
2012 }
2013 }
2014 if (childInfo.type == ObjectType::CLASS)
2015 retval = QVariant::fromValue(value: q->acquireDynamic(name: childInfo.name));
2016 else
2017 retval = QVariant::fromValue(value: q->acquireModel(name: childInfo.name));
2018 } else //We are receiving the initial data for the QObject
2019 retval = rep->getProperty(i: index); //Use existing value so changed signal isn't emitted
2020
2021 QSharedPointer<QConnectedReplicaImplementation> childRep = qSharedPointerCast<QConnectedReplicaImplementation>(src: replicas.value(akey: childInfo.name).toStrongRef());
2022 if (childRep->connectionToSource.isNull())
2023 childRep->connectionToSource = rep->connectionToSource;
2024 QVariantList parameters;
2025 QDataStream ds(childInfo.parameters);
2026 if (childRep->needsDynamicInitialization()) {
2027 if (childInfo.classDefinition.isEmpty()) {
2028 auto typeName = childInfo.typeName;
2029 if (typeName == QLatin1String("QObject")) {
2030 // The sender would have included the class name if needed
2031 // So the acquire must have been templated, and we have the typeName
2032 typeName = QString::fromLatin1(str: rep->getProperty(i: index).typeName());
2033 if (typeName.endsWith(s: QLatin1String("Replica*")))
2034 typeName.chop(n: 8);
2035 }
2036 childRep->setDynamicMetaObject(dynamicTypeManager.metaObjectForType(type: typeName));
2037 } else {
2038 QDataStream in(childInfo.classDefinition);
2039 childRep->setDynamicMetaObject(dynamicTypeManager.addDynamicType(connection: rep->connectionToSource, in));
2040 }
2041 if (!childInfo.parameters.isEmpty())
2042 ds >> parameters;
2043 handlePointerToQObjectProperties(rep: childRep.data(), properties&: parameters);
2044 childRep->setDynamicProperties(parameters);
2045 } else {
2046 if (!childInfo.parameters.isEmpty())
2047 ds >> parameters;
2048 handlePointerToQObjectProperties(rep: childRep.data(), properties&: parameters);
2049 childRep->initialize(values&: parameters);
2050 }
2051
2052 return retval;
2053}
2054
2055void QRemoteObjectNodePrivate::handlePointerToQObjectProperties(QConnectedReplicaImplementation *rep, QVariantList &properties)
2056{
2057 for (const int index : rep->childIndices())
2058 properties[index] = handlePointerToQObjectProperty(rep, index, property: properties.at(i: index));
2059}
2060
2061/*!
2062 Blocks until this Node's \l Registry is initialized or \a timeout (in
2063 milliseconds) expires. Returns \c true if the \l Registry is successfully
2064 initialized upon return, or \c false otherwise.
2065*/
2066bool QRemoteObjectNode::waitForRegistry(int timeout)
2067{
2068 Q_D(QRemoteObjectNode);
2069 if (!d->registry) {
2070 qCWarning(QT_REMOTEOBJECT) << qPrintable(objectName()) << "waitForRegistry() error: No valid registry url set";
2071 return false;
2072 }
2073
2074 return d->registry->waitForSource(timeout);
2075}
2076
2077/*!
2078 Connects a client node to the host node at \a address.
2079
2080 Connections will remain valid until the host node is deleted or no longer
2081 accessible over a network.
2082
2083 Once a client is connected to a host, valid Replicas can then be acquired
2084 if the corresponding Source is being remoted.
2085
2086 Return \c true on success, \c false otherwise (usually an unrecognized url,
2087 or connecting to already connected address).
2088*/
2089bool QRemoteObjectNode::connectToNode(const QUrl &address)
2090{
2091 Q_D(QRemoteObjectNode);
2092 if (!d->initConnection(address)) {
2093 d->setLastError(RegistryNotAcquired);
2094 return false;
2095 }
2096 return true;
2097}
2098
2099/*!
2100 \since 5.12
2101
2102 In order to \l QRemoteObjectNode::acquire() \l Replica objects over \l
2103 {External QIODevices}, Qt Remote Objects needs access to the communications
2104 channel (a \l QIODevice) between the respective nodes. It is the
2105 addClientSideConnection() call that enables this, taking the \a ioDevice as
2106 input. Any acquire() call made without calling addClientSideConnection will
2107 still work, but the Node will not be able to initialize the \l Replica
2108 without being provided the connection to the Host node.
2109
2110 \sa {QRemoteObjectHostBase::addHostSideConnection}
2111*/
2112void QRemoteObjectNode::addClientSideConnection(QIODevice *ioDevice)
2113{
2114 Q_D(QRemoteObjectNode);
2115 if (!ioDevice || !ioDevice->isOpen()) {
2116 qWarning() << "A null or closed QIODevice was passed to addClientSideConnection(). Ignoring.";
2117 return;
2118 }
2119 ExternalIoDevice *device = new ExternalIoDevice(ioDevice, this);
2120 connect(sender: device, signal: &IoDeviceBase::readyRead, context: this, slot: [d, device]() {
2121 d->onClientRead(obj: device);
2122 });
2123 if (device->bytesAvailable())
2124 d->onClientRead(obj: device);
2125}
2126
2127/*!
2128 \fn void QRemoteObjectNode::remoteObjectAdded(const QRemoteObjectSourceLocation &loc)
2129
2130 This signal is emitted whenever a new \l {Source} object is added to
2131 the Registry. The signal will not be emitted if there is no Registry set
2132 (i.e., Sources over connections made via connectToNode directly). The \a
2133 loc parameter contains the information about the added Source, including
2134 name, type and the QUrl of the hosting Node.
2135
2136 \sa remoteObjectRemoved(), instances()
2137*/
2138
2139/*!
2140 \fn void QRemoteObjectNode::remoteObjectRemoved(const QRemoteObjectSourceLocation &loc)
2141
2142 This signal is emitted whenever there is a known \l {Source} object is
2143 removed from the Registry. The signal will not be emitted if there is no
2144 Registry set (i.e., Sources over connections made via connectToNode
2145 directly). The \a loc parameter contains the information about the removed
2146 Source, including name, type and the QUrl of the hosting Node.
2147
2148 \sa remoteObjectAdded, instances
2149*/
2150
2151/*!
2152 \fn QStringList QRemoteObjectNode::instances() const
2153
2154 This templated function (taking a \l repc generated type as the template parameter) will
2155 return the list of names of every instance of that type on the Remote
2156 Objects network. For example, if you have a Shape class defined in a .rep file,
2157 and Circle and Square classes inherit from the Source definition, they can
2158 be shared on the Remote Objects network using \l {QRemoteObjectHostBase::enableRemoting} {enableRemoting}.
2159 \code
2160 Square square;
2161 Circle circle;
2162 myHost.enableRemoting(&square, "Square");
2163 myHost.enableRemoting(&circle, "Circle");
2164 \endcode
2165 Then instance can be used to find the available instances of Shape.
2166 \code
2167 QStringList instances = clientNode.instances<Shape>();
2168 // will return a QStringList containing "Circle" and "Square"
2169 auto instance1 = clientNode.acquire<Shape>("Circle");
2170 auto instance2 = clientNode.acquire<Shape>("Square");
2171 ...
2172 \endcode
2173*/
2174
2175/*!
2176 \overload instances()
2177
2178 This convenience function provides the same result as the templated
2179 version, but takes the name of the \l {Source} class as a parameter (\a
2180 typeName) rather than deriving it from the class type.
2181*/
2182QStringList QRemoteObjectNode::instances(const QString &typeName) const
2183{
2184 Q_D(const QRemoteObjectNode);
2185 QStringList names;
2186 for (auto it = d->connectedSources.cbegin(), end = d->connectedSources.cend(); it != end; ++it) {
2187 if (it.value().typeName == typeName) {
2188 names << it.key();
2189 }
2190 }
2191 return names;
2192}
2193
2194/*!
2195 \keyword dynamic acquire
2196 Returns a QRemoteObjectDynamicReplica of the Source \a name.
2197*/
2198QRemoteObjectDynamicReplica *QRemoteObjectNode::acquireDynamic(const QString &name)
2199{
2200 return new QRemoteObjectDynamicReplica(this, name);
2201}
2202
2203/*!
2204 \qmlmethod bool Host::enableRemoting(object object, string name)
2205 Enables a host node to dynamically provide remote access to the QObject \a
2206 object. Client nodes connected to the node hosting this object may obtain
2207 Replicas of this Source.
2208
2209 The optional \a name defines the lookup-name under which the QObject can be acquired
2210 using \l QRemoteObjectNode::acquire() . If not explicitly set then the name
2211 given in the QCLASSINFO_REMOTEOBJECT_TYPE will be used. If no such macro
2212 was defined for the QObject then the \l QObject::objectName() is used.
2213
2214 Returns \c false if the current node is a client node, or if the QObject is already
2215 registered to be remoted, and \c true if remoting is successfully enabled
2216 for the dynamic QObject.
2217
2218 \sa disableRemoting()
2219*/
2220
2221/*!
2222 Enables a host node to dynamically provide remote access to the QObject \a
2223 object. Client nodes connected to the node
2224 hosting this object may obtain Replicas of this Source.
2225
2226 The optional \a name defines the lookup-name under which the QObject can be acquired
2227 using \l QRemoteObjectNode::acquire() . If not explicitly set then the name
2228 given in the QCLASSINFO_REMOTEOBJECT_TYPE will be used. If no such macro
2229 was defined for the QObject then the \l QObject::objectName() is used.
2230
2231 Returns \c false if the current node is a client node, or if the QObject is already
2232 registered to be remoted, and \c true if remoting is successfully enabled
2233 for the dynamic QObject.
2234
2235 \sa disableRemoting()
2236*/
2237bool QRemoteObjectHostBase::enableRemoting(QObject *object, const QString &name)
2238{
2239 Q_D(QRemoteObjectHostBase);
2240 if (!d->remoteObjectIo) {
2241 d->setLastError(OperationNotValidOnClientNode);
2242 return false;
2243 }
2244
2245 const QMetaObject *meta = object->metaObject();
2246 QString _name = name;
2247 QString typeName = getTypeNameAndMetaobjectFromClassInfo(meta);
2248 if (typeName.isEmpty()) { //This is a passed in QObject, use its API
2249 if (_name.isEmpty()) {
2250 _name = object->objectName();
2251 if (_name.isEmpty()) {
2252 d->setLastError(MissingObjectName);
2253 qCWarning(QT_REMOTEOBJECT) << qPrintable(objectName()) << "enableRemoting() Error: Unable to Replicate an object that does not have objectName() set.";
2254 return false;
2255 }
2256 }
2257 } else if (_name.isEmpty())
2258 _name = typeName;
2259 return d->remoteObjectIo->enableRemoting(object, meta, name: _name, typeName);
2260}
2261
2262/*!
2263 This overload of enableRemoting() is specific to \l QAbstractItemModel types
2264 (or any type derived from \l QAbstractItemModel). This is useful if you want
2265 to have a model and the HMI for the model in different processes.
2266
2267 The three required parameters are the \a model itself, the \a name by which
2268 to lookup the model, and the \a roles that should be exposed on the Replica
2269 side. If you want to synchronize selection between \l Source and \l
2270 Replica, the optional \a selectionModel parameter can be used. This is only
2271 recommended when using a single Replica.
2272
2273 Behind the scenes, Qt Remote Objects batches data() lookups and prefetches
2274 data when possible to make the model interaction as responsive as possible.
2275
2276 Returns \c false if the current node is a client node, or if the QObject is already
2277 registered to be remoted, and \c true if remoting is successfully enabled
2278 for the QAbstractItemModel.
2279
2280 \sa disableRemoting()
2281 */
2282bool QRemoteObjectHostBase::enableRemoting(QAbstractItemModel *model, const QString &name, const QVector<int> roles, QItemSelectionModel *selectionModel)
2283{
2284 //This looks complicated, but hopefully there is a way to have an adapter be a template
2285 //parameter and this makes sure that is supported.
2286 QObject *adapter = QAbstractItemModelSourceAdapter::staticMetaObject.newInstance(Q_ARG(QAbstractItemModel*, model),
2287 Q_ARG(QItemSelectionModel*, selectionModel),
2288 Q_ARG(QVector<int>, roles));
2289 QAbstractItemAdapterSourceAPI<QAbstractItemModel, QAbstractItemModelSourceAdapter> *api =
2290 new QAbstractItemAdapterSourceAPI<QAbstractItemModel, QAbstractItemModelSourceAdapter>(name);
2291 if (!this->objectName().isEmpty())
2292 adapter->setObjectName(this->objectName().append(s: QLatin1String("Adapter")));
2293 return enableRemoting(object: model, api, adapter);
2294}
2295
2296/*!
2297 \fn template <template <typename> class ApiDefinition, typename ObjectType> bool QRemoteObjectHostBase::enableRemoting(ObjectType *object)
2298
2299 This templated function overload enables a host node to provide remote
2300 access to a QObject \a object with a specified (and compile-time checked)
2301 interface. Client nodes connected to the node hosting this object may
2302 obtain Replicas of this Source.
2303
2304 This is best illustrated by example:
2305 \code
2306 #include "rep_TimeModel_source.h"
2307 MinuteTimer timer;
2308 hostNode.enableRemoting<MinuteTimerSourceAPI>(&timer);
2309 \endcode
2310
2311 Here the MinuteTimerSourceAPI is the set of Signals/Slots/Properties
2312 defined by the TimeModel.rep file. Compile time checks are made to verify
2313 the input QObject can expose the requested API, it will fail to compile
2314 otherwise. This allows a subset of \a object 's interface to be exposed,
2315 and allows the types of conversions supported by Signal/Slot connections.
2316
2317 Returns \c false if the current node is a client node, or if the QObject is
2318 already registered to be remoted, and \c true if remoting is successfully
2319 enabled for the QObject.
2320
2321 \sa disableRemoting()
2322*/
2323
2324/*!
2325 \internal
2326 Enables a host node to provide remote access to a QObject \a object
2327 with the API defined by \a api. Client nodes connected to the node
2328 hosting this object may obtain Replicas of this Source.
2329
2330 Returns \c false if the current node is a client node, or if the QObject is
2331 already registered to be remoted, and \c true if remoting is successfully
2332 enabled for the QObject.
2333
2334 \sa disableRemoting()
2335*/
2336bool QRemoteObjectHostBase::enableRemoting(QObject *object, const SourceApiMap *api, QObject *adapter)
2337{
2338 Q_D(QRemoteObjectHostBase);
2339 return d->remoteObjectIo->enableRemoting(object, api, adapter);
2340}
2341
2342/*!
2343 \qmlmethod bool Host::disableRemoting(object remoteObject)
2344 Disables remote access for the QObject \a remoteObject. Returns \c false if
2345 the current node is a client node or if the \a remoteObject is not
2346 registered, and returns \c true if remoting is successfully disabled for
2347 the Source object.
2348
2349 \warning Replicas of this object will no longer be valid after calling this method.
2350
2351 \sa enableRemoting()
2352*/
2353
2354/*!
2355 Disables remote access for the QObject \a remoteObject. Returns \c false if
2356 the current node is a client node or if the \a remoteObject is not
2357 registered, and returns \c true if remoting is successfully disabled for
2358 the Source object.
2359
2360 \warning Replicas of this object will no longer be valid after calling this method.
2361
2362 \sa enableRemoting()
2363*/
2364bool QRemoteObjectHostBase::disableRemoting(QObject *remoteObject)
2365{
2366 Q_D(QRemoteObjectHostBase);
2367 if (!d->remoteObjectIo) {
2368 d->setLastError(OperationNotValidOnClientNode);
2369 return false;
2370 }
2371
2372 if (!d->remoteObjectIo->disableRemoting(object: remoteObject)) {
2373 d->setLastError(SourceNotRegistered);
2374 return false;
2375 }
2376
2377 return true;
2378}
2379
2380/*!
2381 \since 5.12
2382
2383 In order to \l QRemoteObjectHost::enableRemoting() \l Source objects over
2384 \l {External QIODevices}, Qt Remote Objects needs access to the
2385 communications channel (a \l QIODevice) between the respective nodes. It is
2386 the addHostSideConnection() call that enables this on the \l Source side,
2387 taking the \a ioDevice as input. Any enableRemoting() call will still work
2388 without calling addHostSideConnection, but the Node will not be able to
2389 share the \l Source objects without being provided the connection to
2390 the Replica node. Before calling this function you must call
2391 \l {QRemoteObjectHost::}{setHostUrl}() with a unique URL and
2392 \l {QRemoteObjectHost::}{AllowExternalRegistration}.
2393
2394 \sa addClientSideConnection
2395*/
2396void QRemoteObjectHostBase::addHostSideConnection(QIODevice *ioDevice)
2397{
2398 Q_D(QRemoteObjectHostBase);
2399 if (!ioDevice || !ioDevice->isOpen()) {
2400 qWarning() << "A null or closed QIODevice was passed to addHostSideConnection(). Ignoring.";
2401 return;
2402 }
2403 if (!d->remoteObjectIo)
2404 d->remoteObjectIo = new QRemoteObjectSourceIo(this);
2405 ExternalIoDevice *device = new ExternalIoDevice(ioDevice, this);
2406 return d->remoteObjectIo->newConnection(conn: device);
2407}
2408
2409/*!
2410 Returns a pointer to a \l Replica which is specifically derived from \l
2411 QAbstractItemModel. The \a name provided must match the name used with the
2412 matching \l {QRemoteObjectHostBase::}{enableRemoting} that put
2413 the \l Model on the network. \a action specifies whether the model should
2414 fetch data before the \l {QRemoteObjectReplica::}{initialized} signal is
2415 emitted. If it's set to QtRemoteObjects::PrefetchData, then the data for
2416 roles in the \a rolesHint will be prefetched. If \a rolesHint is empty, then
2417 the data for all the roles exposed by \l Source will be prefetched.
2418
2419 The returned model will be empty until it is initialized with the \l Source.
2420*/
2421QAbstractItemModelReplica *QRemoteObjectNode::acquireModel(const QString &name, QtRemoteObjects::InitialAction action, const QVector<int> &rolesHint)
2422{
2423 QAbstractItemModelReplicaImplementation *rep = acquire<QAbstractItemModelReplicaImplementation>(name);
2424 return new QAbstractItemModelReplica(rep, action, rolesHint);
2425}
2426
2427QRemoteObjectHostBasePrivate::QRemoteObjectHostBasePrivate()
2428 : QRemoteObjectNodePrivate()
2429 , remoteObjectIo(nullptr)
2430{ }
2431
2432QRemoteObjectHostBasePrivate::~QRemoteObjectHostBasePrivate()
2433{ }
2434
2435QRemoteObjectHostPrivate::QRemoteObjectHostPrivate()
2436 : QRemoteObjectHostBasePrivate()
2437{ }
2438
2439QRemoteObjectHostPrivate::~QRemoteObjectHostPrivate()
2440{ }
2441
2442QRemoteObjectRegistryHostPrivate::QRemoteObjectRegistryHostPrivate()
2443 : QRemoteObjectHostBasePrivate()
2444 , registrySource(nullptr)
2445{ }
2446
2447QRemoteObjectRegistryHostPrivate::~QRemoteObjectRegistryHostPrivate()
2448{ }
2449
2450ProxyInfo::ProxyInfo(QRemoteObjectNode *node, QRemoteObjectHostBase *parent,
2451 QRemoteObjectHostBase::RemoteObjectNameFilter filter)
2452 : QObject(parent)
2453 , proxyNode(node)
2454 , parentNode(parent)
2455 , proxyFilter(filter)
2456{
2457 const auto registry = node->registry();
2458 proxyNode->setObjectName(QString::fromLatin1(str: "_ProxyNode"));
2459
2460 connect(sender: registry, signal: &QRemoteObjectRegistry::remoteObjectAdded, context: this,
2461 slot: [this](const QRemoteObjectSourceLocation &entry)
2462 {
2463 this->proxyObject(entry, direction: ProxyDirection::Forward);
2464 });
2465 connect(sender: registry, signal: &QRemoteObjectRegistry::remoteObjectRemoved, receiver: this,
2466 slot: &ProxyInfo::unproxyObject);
2467 connect(sender: registry, signal: &QRemoteObjectRegistry::initialized, context: this, slot: [registry, this]() {
2468 QRemoteObjectSourceLocations locations = registry->sourceLocations();
2469 QRemoteObjectSourceLocations::const_iterator i = locations.constBegin();
2470 while (i != locations.constEnd()) {
2471 proxyObject(entry: QRemoteObjectSourceLocation(i.key(), i.value()));
2472 ++i;
2473 }
2474 });
2475
2476 connect(sender: registry, signal: &QRemoteObjectRegistry::stateChanged, context: this,
2477 slot: [this](QRemoteObjectRegistry::State state, QRemoteObjectRegistry::State /*oldState*/) {
2478 if (state != QRemoteObjectRegistry::Suspect)
2479 return;
2480 // unproxy all objects
2481 for (ProxyReplicaInfo* info : proxiedReplicas)
2482 disableAndDeleteObject(info);
2483 proxiedReplicas.clear();
2484 });
2485}
2486
2487ProxyInfo::~ProxyInfo() {
2488 for (ProxyReplicaInfo* info : proxiedReplicas)
2489 delete info;
2490}
2491
2492bool ProxyInfo::setReverseProxy(QRemoteObjectHostBase::RemoteObjectNameFilter filter)
2493{
2494 if (qobject_cast<QRemoteObjectRegistryHost *>(object: parentNode) == nullptr) {
2495 qWarning() << "Setting up reverseProxy() can only be done on a Registry node.";
2496 return false;
2497 }
2498 const auto registry = parentNode->registry();
2499 this->reverseFilter = filter;
2500
2501 connect(sender: registry, signal: &QRemoteObjectRegistry::remoteObjectAdded, context: this,
2502 slot: [this](const QRemoteObjectSourceLocation &entry)
2503 {
2504 this->proxyObject(entry, direction: ProxyDirection::Reverse);
2505 });
2506 connect(sender: registry, signal: &QRemoteObjectRegistry::remoteObjectRemoved, receiver: this,
2507 slot: &ProxyInfo::unproxyObject);
2508 connect(sender: registry, signal: &QRemoteObjectRegistry::initialized, context: this, slot: [registry, this]() {
2509 QRemoteObjectSourceLocations locations = registry->sourceLocations();
2510 QRemoteObjectSourceLocations::const_iterator i = locations.constBegin();
2511 while (i != locations.constEnd()) {
2512 proxyObject(entry: QRemoteObjectSourceLocation(i.key(), i.value()), direction: ProxyDirection::Reverse);
2513 ++i;
2514 }
2515 });
2516
2517 return true;
2518}
2519
2520void ProxyInfo::proxyObject(const QRemoteObjectSourceLocation &entry, ProxyDirection direction)
2521{
2522 const QString name = entry.first;
2523 const QString typeName = entry.second.typeName;
2524
2525 if (direction == ProxyDirection::Forward) {
2526 // If we are using the reverse proxy, this can be called when reverse proxy objects are added
2527 // Don't try to proxy those back. We can detect this because the hosting node will be our proxyNode.
2528 auto host = qobject_cast<QRemoteObjectHost *>(object: proxyNode);
2529 if (host && entry.second.hostUrl == host->hostUrl())
2530 return;
2531 if (!proxyFilter(name, typeName))
2532 return;
2533 Q_ASSERT(!proxiedReplicas.contains(name));
2534
2535 qCDebug(QT_REMOTEOBJECT) << "Starting proxy for" << name << "from" << entry.second.hostUrl;
2536
2537 if (entry.second.typeName == QAIMADAPTER()) {
2538 QAbstractItemModelReplica *rep = proxyNode->acquireModel(name);
2539 proxiedReplicas.insert(akey: name, avalue: new ProxyReplicaInfo{.replica: rep, .direction: direction});
2540 connect(sender: rep, signal: &QAbstractItemModelReplica::initialized, context: this,
2541 slot: [rep, name, this]() { this->parentNode->enableRemoting(model: rep, name, roles: QVector<int>()); });
2542 } else {
2543 QRemoteObjectDynamicReplica *rep = proxyNode->acquireDynamic(name);
2544 proxiedReplicas.insert(akey: name, avalue: new ProxyReplicaInfo{.replica: rep, .direction: direction});
2545 connect(sender: rep, signal: &QRemoteObjectDynamicReplica::initialized, context: this,
2546 slot: [rep, name, this]() { this->parentNode->enableRemoting(object: rep, name); });
2547 }
2548 } else {
2549 // If we are using the reverse proxy, this can be called when proxy objects are added
2550 // Don't try to proxy those back. We can detect this because the hosting node will be the parentNode.
2551 // Since we know the parentNode has to be a RegistryNode for reverse proxy to work, we compare against
2552 // the registryUrl().
2553 if (entry.second.hostUrl == parentNode->registryUrl())
2554 return;
2555 if (!reverseFilter(name, typeName))
2556 return;
2557 Q_ASSERT(!proxiedReplicas.contains(name));
2558
2559 qCDebug(QT_REMOTEOBJECT) << "Starting reverse proxy for" << name << "from" << entry.second.hostUrl;
2560
2561 if (entry.second.typeName == QAIMADAPTER()) {
2562 QAbstractItemModelReplica *rep = this->parentNode->acquireModel(name);
2563 proxiedReplicas.insert(akey: name, avalue: new ProxyReplicaInfo{.replica: rep, .direction: direction});
2564 connect(sender: rep, signal: &QAbstractItemModelReplica::initialized, context: this,
2565 slot: [rep, name, this]()
2566 {
2567 QRemoteObjectHostBase *host = qobject_cast<QRemoteObjectHostBase *>(object: this->proxyNode);
2568 Q_ASSERT(host);
2569 host->enableRemoting(model: rep, name, roles: QVector<int>());
2570 });
2571 } else {
2572 QRemoteObjectDynamicReplica *rep = this->parentNode->acquireDynamic(name);
2573 proxiedReplicas.insert(akey: name, avalue: new ProxyReplicaInfo{.replica: rep, .direction: direction});
2574 connect(sender: rep, signal: &QRemoteObjectDynamicReplica::initialized, context: this,
2575 slot: [rep, name, this]()
2576 {
2577 QRemoteObjectHostBase *host = qobject_cast<QRemoteObjectHostBase *>(object: this->proxyNode);
2578 Q_ASSERT(host);
2579 host->enableRemoting(object: rep, name);
2580 });
2581 }
2582 }
2583
2584}
2585
2586void ProxyInfo::unproxyObject(const QRemoteObjectSourceLocation &entry)
2587{
2588 const QString name = entry.first;
2589
2590 if (proxiedReplicas.contains(akey: name)) {
2591 qCDebug(QT_REMOTEOBJECT) << "Stopping proxy for" << name;
2592 auto const info = proxiedReplicas.take(akey: name);
2593 disableAndDeleteObject(info);
2594 }
2595}
2596
2597void ProxyInfo::disableAndDeleteObject(ProxyReplicaInfo* info)
2598{
2599 if (info->direction == ProxyDirection::Forward)
2600 this->parentNode->disableRemoting(remoteObject: info->replica);
2601 else {
2602 QRemoteObjectHostBase *host = qobject_cast<QRemoteObjectHostBase *>(object: this->proxyNode);
2603 Q_ASSERT(host);
2604 host->disableRemoting(remoteObject: info->replica);
2605 }
2606 delete info;
2607}
2608
2609
2610QT_END_NAMESPACE
2611
2612#include "moc_qremoteobjectnode.cpp"
2613

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