cmake: avoid de-duplication of user's CXXFLAGS
[quassel.git] / src / common / signalproxy.cpp
1 /***************************************************************************
2  *   Copyright (C) 2005-2022 by the Quassel Project                        *
3  *   devel@quassel-irc.org                                                 *
4  *                                                                         *
5  *   This program is free software; you can redistribute it and/or modify  *
6  *   it under the terms of the GNU General Public License as published by  *
7  *   the Free Software Foundation; either version 2 of the License, or     *
8  *   (at your option) version 3.                                           *
9  *                                                                         *
10  *   This program is distributed in the hope that it will be useful,       *
11  *   but WITHOUT ANY WARRANTY; without even the implied warranty of        *
12  *   MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the         *
13  *   GNU General Public License for more details.                          *
14  *                                                                         *
15  *   You should have received a copy of the GNU General Public License     *
16  *   along with this program; if not, write to the                         *
17  *   Free Software Foundation, Inc.,                                       *
18  *   51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.         *
19  ***************************************************************************/
20
21 #include <algorithm>
22 #include <utility>
23
24 #include <QCoreApplication>
25 #include <QHostAddress>
26 #include <QMetaMethod>
27 #include <QMetaProperty>
28 #include <QSslSocket>
29 #include <QThread>
30
31 #include "peer.h"
32 #include "protocol.h"
33 #include "signalproxy.h"
34 #include "syncableobject.h"
35 #include "types.h"
36 #include "util.h"
37
38 using namespace Protocol;
39
40 class RemovePeerEvent : public QEvent
41 {
42 public:
43     RemovePeerEvent(Peer* peer)
44         : QEvent(QEvent::Type(SignalProxy::RemovePeerEvent))
45         , peer(peer)
46     {}
47     Peer* peer;
48 };
49
50 // ==================================================
51 //  SignalProxy
52 // ==================================================
53
54 namespace {
55 thread_local SignalProxy* _current{nullptr};
56 }
57
58 SignalProxy::SignalProxy(QObject* parent)
59     : QObject(parent)
60 {
61     setProxyMode(Client);
62     init();
63 }
64
65 SignalProxy::SignalProxy(ProxyMode mode, QObject* parent)
66     : QObject(parent)
67 {
68     setProxyMode(mode);
69     init();
70 }
71
72 SignalProxy::~SignalProxy()
73 {
74     QHash<QByteArray, ObjectId>::iterator classIter = _syncSlave.begin();
75     while (classIter != _syncSlave.end()) {
76         ObjectId::iterator objIter = classIter->begin();
77         while (objIter != classIter->end()) {
78             SyncableObject* obj = objIter.value();
79             objIter = classIter->erase(objIter);
80             obj->stopSynchronize(this);
81         }
82         ++classIter;
83     }
84     _syncSlave.clear();
85
86     removeAllPeers();
87
88     // Ensure that we don't try to clean up while destroying ourselves
89     disconnect(this, &QObject::destroyed, this, &SignalProxy::detachSlotObjects);
90
91     _current = nullptr;
92 }
93
94 SignalProxy* SignalProxy::current()
95 {
96     return _current;
97 }
98
99 void SignalProxy::setProxyMode(ProxyMode mode)
100 {
101     if (!_peerMap.empty()) {
102         qWarning() << Q_FUNC_INFO << "Cannot change proxy mode while connected";
103         return;
104     }
105
106     _proxyMode = mode;
107     if (mode == Server)
108         initServer();
109     else
110         initClient();
111 }
112
113 void SignalProxy::init()
114 {
115     _heartBeatInterval = 0;
116     _maxHeartBeatCount = 0;
117     setHeartBeatInterval(30);
118     setMaxHeartBeatCount(2);
119     _secure = false;
120     _current = this;
121     updateSecureState();
122 }
123
124 void SignalProxy::initServer() {}
125
126 void SignalProxy::initClient()
127 {
128     attachSlot("__objectRenamed__", this, &SignalProxy::objectRenamed);
129 }
130
131 void SignalProxy::setHeartBeatInterval(int secs)
132 {
133     if (_heartBeatInterval != secs) {
134         _heartBeatInterval = secs;
135         emit heartBeatIntervalChanged(secs);
136     }
137 }
138
139 void SignalProxy::setMaxHeartBeatCount(int max)
140 {
141     if (_maxHeartBeatCount != max) {
142         _maxHeartBeatCount = max;
143         emit maxHeartBeatCountChanged(max);
144     }
145 }
146
147 bool SignalProxy::addPeer(Peer* peer)
148 {
149     if (!peer)
150         return false;
151
152     if (_peerMap.values().contains(peer))
153         return true;
154
155     if (!peer->isOpen()) {
156         qWarning("SignalProxy: peer needs to be open!");
157         return false;
158     }
159
160     if (proxyMode() == Client) {
161         if (!_peerMap.isEmpty()) {
162             qWarning("SignalProxy: only one peer allowed in client mode!");
163             return false;
164         }
165         connect(peer, &Peer::lagUpdated, this, &SignalProxy::lagUpdated);
166     }
167
168     connect(peer, &Peer::disconnected, this, &SignalProxy::removePeerBySender);
169     connect(peer, &Peer::secureStateChanged, this, &SignalProxy::updateSecureState);
170
171     if (!peer->parent())
172         peer->setParent(this);
173
174     if (peer->id() < 0) {
175         peer->setId(nextPeerId());
176         peer->setConnectedSince(QDateTime::currentDateTimeUtc());
177     }
178
179     _peerMap[peer->id()] = peer;
180
181     peer->setSignalProxy(this);
182
183     if (peerCount() == 1)
184         emit connected();
185
186     updateSecureState();
187     return true;
188 }
189
190 void SignalProxy::removeAllPeers()
191 {
192     Q_ASSERT(proxyMode() == Server || peerCount() <= 1);
193     // we need to copy that list since we modify it in the loop
194     QList<Peer*> peers = _peerMap.values();
195     for (auto peer : peers) {
196         removePeer(peer);
197     }
198 }
199
200 void SignalProxy::removePeer(Peer* peer)
201 {
202     if (!peer) {
203         qWarning() << Q_FUNC_INFO << "Trying to remove a null peer!";
204         return;
205     }
206
207     if (_peerMap.isEmpty()) {
208         qWarning() << "SignalProxy::removePeer(): No peers in use!";
209         return;
210     }
211
212     if (!_peerMap.values().contains(peer)) {
213         qWarning() << "SignalProxy: unknown Peer" << peer;
214         return;
215     }
216
217     disconnect(peer, nullptr, this, nullptr);
218     peer->setSignalProxy(nullptr);
219
220     _peerMap.remove(peer->id());
221     emit peerRemoved(peer);
222
223     if (peer->parent() == this)
224         peer->deleteLater();
225
226     updateSecureState();
227
228     if (_peerMap.isEmpty())
229         emit disconnected();
230 }
231
232 void SignalProxy::removePeerBySender()
233 {
234     removePeer(qobject_cast<Peer*>(sender()));
235 }
236
237 void SignalProxy::renameObject(const SyncableObject* obj, const QString& newname, const QString& oldname)
238 {
239     if (proxyMode() == Client)
240         return;
241
242     const QMetaObject* meta = obj->syncMetaObject();
243     const QByteArray className(meta->className());
244     objectRenamed(className, newname, oldname);
245
246     dispatch(RpcCall("__objectRenamed__", QVariantList() << className << newname << oldname));
247 }
248
249 void SignalProxy::objectRenamed(const QByteArray& classname, const QString& newname, const QString& oldname)
250 {
251     if (newname != oldname) {
252         if (_syncSlave.contains(classname) && _syncSlave[classname].contains(oldname)) {
253             SyncableObject* obj = _syncSlave[classname][newname] = _syncSlave[classname].take(oldname);
254             obj->setObjectName(newname);
255             requestInit(obj);
256         }
257     }
258 }
259
260 const QMetaObject* SignalProxy::metaObject(const QObject* obj)
261 {
262     if (const auto* syncObject = qobject_cast<const SyncableObject*>(obj))
263         return syncObject->syncMetaObject();
264     else
265         return obj->metaObject();
266 }
267
268 SignalProxy::ExtendedMetaObject* SignalProxy::extendedMetaObject(const QMetaObject* meta) const
269 {
270     if (_extendedMetaObjects.contains(meta))
271         return _extendedMetaObjects[meta];
272     else
273         return nullptr;
274 }
275
276 SignalProxy::ExtendedMetaObject* SignalProxy::createExtendedMetaObject(const QMetaObject* meta, bool checkConflicts)
277 {
278     if (!_extendedMetaObjects.contains(meta)) {
279         _extendedMetaObjects[meta] = new ExtendedMetaObject(meta, checkConflicts);
280     }
281     return _extendedMetaObjects[meta];
282 }
283
284 void SignalProxy::attachSlotObject(const QByteArray& signalName, std::unique_ptr<SlotObjectBase> slotObject)
285 {
286     // Remove all attached slots related to the context upon its destruction
287     connect(slotObject->context(), &QObject::destroyed, this, &SignalProxy::detachSlotObjects, Qt::UniqueConnection);
288
289     _attachedSlots.emplace(QMetaObject::normalizedSignature(signalName.constData()), std::move(slotObject));
290 }
291
292 void SignalProxy::detachSlotObjects(const QObject *context)
293 {
294     for (auto&& it = _attachedSlots.begin(); it != _attachedSlots.end(); ) {
295         if (it->second->context() == context) {
296             it = _attachedSlots.erase(it);
297         }
298         else {
299             ++it;
300         }
301     }
302 }
303
304 void SignalProxy::synchronize(SyncableObject* obj)
305 {
306     createExtendedMetaObject(obj, true);
307
308     // attaching as slave to receive sync Calls
309     QByteArray className(obj->syncMetaObject()->className());
310     _syncSlave[className][obj->objectName()] = obj;
311
312     if (proxyMode() == Server) {
313         obj->setInitialized();
314         emit objectInitialized(obj);
315     }
316     else {
317         if (obj->isInitialized())
318             emit objectInitialized(obj);
319         else
320             requestInit(obj);
321     }
322
323     obj->synchronize(this);
324 }
325
326 void SignalProxy::stopSynchronize(SyncableObject* obj)
327 {
328     // we can't use a className here, since it might be effed up, if we receive the call as a result of a decon
329     // gladly the objectName() is still valid. So we have only to iterate over the classes not each instance! *sigh*
330     QHash<QByteArray, ObjectId>::iterator classIter = _syncSlave.begin();
331     while (classIter != _syncSlave.end()) {
332         if (classIter->contains(obj->objectName()) && classIter.value()[obj->objectName()] == obj) {
333             classIter->remove(obj->objectName());
334             break;
335         }
336         ++classIter;
337     }
338     obj->stopSynchronize(this);
339 }
340
341 void SignalProxy::dispatchSignal(QByteArray sigName, QVariantList params)
342 {
343     RpcCall rpcCall{std::move(sigName), std::move(params)};
344     if (_restrictMessageTarget) {
345         for (auto&& peer : _restrictedTargets) {
346             dispatch(peer, rpcCall);
347         }
348     }
349     else {
350         dispatch(rpcCall);
351     }
352 }
353
354 template<class T>
355 void SignalProxy::dispatch(const T& protoMessage)
356 {
357     for (auto&& peer : _peerMap.values()) {
358         dispatch(peer, protoMessage);
359     }
360 }
361
362 template<class T>
363 void SignalProxy::dispatch(Peer* peer, const T& protoMessage)
364 {
365     _targetPeer = peer;
366
367     if (peer && peer->isOpen())
368         peer->dispatch(protoMessage);
369     else
370         QCoreApplication::postEvent(this, new ::RemovePeerEvent(peer));
371
372     _targetPeer = nullptr;
373 }
374
375 void SignalProxy::handle(Peer* peer, const SyncMessage& syncMessage)
376 {
377     if (!_syncSlave.contains(syncMessage.className) || !_syncSlave[syncMessage.className].contains(syncMessage.objectName)) {
378         qWarning() << QString("no registered receiver for sync call: %1::%2 (objectName=\"%3\"). Params are:")
379                           .arg(syncMessage.className, syncMessage.slotName, syncMessage.objectName)
380                    << syncMessage.params;
381         return;
382     }
383
384     SyncableObject* receiver = _syncSlave[syncMessage.className][syncMessage.objectName];
385     ExtendedMetaObject* eMeta = extendedMetaObject(receiver);
386     if (!eMeta->slotMap().contains(syncMessage.slotName)) {
387         qWarning() << QString("no matching slot for sync call: %1::%2 (objectName=\"%3\"). Params are:")
388                           .arg(syncMessage.className, syncMessage.slotName, syncMessage.objectName)
389                    << syncMessage.params;
390         return;
391     }
392
393     int slotId = eMeta->slotMap()[syncMessage.slotName];
394     if (proxyMode() != eMeta->receiverMode(slotId)) {
395         qWarning("SignalProxy::handleSync(): invokeMethod for \"%s\" failed. Wrong ProxyMode!", eMeta->methodName(slotId).constData());
396         return;
397     }
398
399     // We can no longer construct a QVariant from QMetaType::Void
400     QVariant returnValue;
401     int returnType = eMeta->returnType(slotId);
402     if (returnType != QMetaType::Void)
403         returnValue = QVariant(static_cast<QVariant::Type>(returnType));
404
405     if (!invokeSlot(receiver, slotId, syncMessage.params, returnValue, peer)) {
406         qWarning("SignalProxy::handleSync(): invokeMethod for \"%s\" failed ", eMeta->methodName(slotId).constData());
407         return;
408     }
409
410     if (returnValue.type() != QVariant::Invalid && eMeta->receiveMap().contains(slotId)) {
411         int receiverId = eMeta->receiveMap()[slotId];
412         QVariantList returnParams;
413         if (eMeta->argTypes(receiverId).count() > 1)
414             returnParams << syncMessage.params;
415         returnParams << returnValue;
416         _targetPeer = peer;
417         peer->dispatch(SyncMessage(syncMessage.className, syncMessage.objectName, eMeta->methodName(receiverId), returnParams));
418         _targetPeer = nullptr;
419     }
420
421     // send emit update signal
422     invokeSlot(receiver, eMeta->updatedRemotelyId());
423 }
424
425 void SignalProxy::handle(Peer* peer, const RpcCall& rpcCall)
426 {
427     Q_UNUSED(peer)
428
429     auto range = _attachedSlots.equal_range(rpcCall.signalName);
430     std::for_each(range.first, range.second, [&rpcCall](const auto& p) {
431         if (!p.second->invoke(rpcCall.params)) {
432             qWarning() << "Could not invoke slot for remote signal" << rpcCall.signalName;
433         }
434     });
435 }
436
437 void SignalProxy::handle(Peer* peer, const InitRequest& initRequest)
438 {
439     if (!_syncSlave.contains(initRequest.className)) {
440         qWarning() << "SignalProxy::handleInitRequest() received initRequest for unregistered Class:" << initRequest.className;
441         return;
442     }
443
444     if (!_syncSlave[initRequest.className].contains(initRequest.objectName)) {
445         qWarning() << "SignalProxy::handleInitRequest() received initRequest for unregistered Object:" << initRequest.className
446                    << initRequest.objectName;
447         return;
448     }
449
450     SyncableObject* obj = _syncSlave[initRequest.className][initRequest.objectName];
451     _targetPeer = peer;
452     peer->dispatch(InitData(initRequest.className, initRequest.objectName, initData(obj)));
453     _targetPeer = nullptr;
454 }
455
456 void SignalProxy::handle(Peer* peer, const InitData& initData)
457 {
458     Q_UNUSED(peer)
459
460     if (!_syncSlave.contains(initData.className)) {
461         qWarning() << "SignalProxy::handleInitData() received initData for unregistered Class:" << initData.className;
462         return;
463     }
464
465     if (!_syncSlave[initData.className].contains(initData.objectName)) {
466         qWarning() << "SignalProxy::handleInitData() received initData for unregistered Object:" << initData.className << initData.objectName;
467         return;
468     }
469
470     SyncableObject* obj = _syncSlave[initData.className][initData.objectName];
471     setInitData(obj, initData.initData);
472 }
473
474 bool SignalProxy::invokeSlot(QObject* receiver, int methodId, const QVariantList& params, QVariant& returnValue, Peer* peer)
475 {
476     ExtendedMetaObject* eMeta = extendedMetaObject(receiver);
477     const QList<int> args = eMeta->argTypes(methodId);
478     const int numArgs = params.count() < args.count() ? params.count() : args.count();
479
480     if (eMeta->minArgCount(methodId) > params.count()) {
481         qWarning() << "SignalProxy::invokeSlot(): not enough params to invoke" << eMeta->methodName(methodId);
482         return false;
483     }
484
485     void* _a[] = {nullptr,  // return type...
486                   nullptr,
487                   nullptr,
488                   nullptr,
489                   nullptr,
490                   nullptr,  // and 10 args - that's the max size qt can handle with signals and slots
491                   nullptr,
492                   nullptr,
493                   nullptr,
494                   nullptr,
495                   nullptr};
496
497     // check for argument compatibility and build params array
498     for (int i = 0; i < numArgs; i++) {
499         if (!params[i].isValid()) {
500             qWarning() << "SignalProxy::invokeSlot(): received invalid data for argument number" << i << "of method"
501                        << QString("%1::%2()")
502                               .arg(receiver->metaObject()->className())
503                               .arg(receiver->metaObject()->method(methodId).methodSignature().constData());
504             qWarning() << "                            - make sure all your data types are known by the Qt MetaSystem";
505             return false;
506         }
507         if (args[i] != QMetaType::type(params[i].typeName())) {
508             qWarning() << "SignalProxy::invokeSlot(): incompatible param types to invoke" << eMeta->methodName(methodId);
509             return false;
510         }
511
512         _a[i + 1] = const_cast<void*>(params[i].constData());
513     }
514
515     if (returnValue.type() != QVariant::Invalid)
516         _a[0] = const_cast<void*>(returnValue.constData());
517
518     Qt::ConnectionType type = QThread::currentThread() == receiver->thread() ? Qt::DirectConnection : Qt::QueuedConnection;
519
520     if (type == Qt::DirectConnection) {
521         _sourcePeer = peer;
522         auto result = receiver->qt_metacall(QMetaObject::InvokeMetaMethod, methodId, _a) < 0;
523         _sourcePeer = nullptr;
524         return result;
525     }
526     else {
527         qWarning() << "Queued Connections are not implemented yet";
528         // note to self: qmetaobject.cpp:990 ff
529         return false;
530     }
531 }
532
533 bool SignalProxy::invokeSlot(QObject* receiver, int methodId, const QVariantList& params, Peer* peer)
534 {
535     QVariant ret;
536     return invokeSlot(receiver, methodId, params, ret, peer);
537 }
538
539 void SignalProxy::requestInit(SyncableObject* obj)
540 {
541     if (proxyMode() == Server || obj->isInitialized())
542         return;
543
544     dispatch(InitRequest(obj->syncMetaObject()->className(), obj->objectName()));
545 }
546
547 QVariantMap SignalProxy::initData(SyncableObject* obj) const
548 {
549     return obj->toVariantMap();
550 }
551
552 void SignalProxy::setInitData(SyncableObject* obj, const QVariantMap& properties)
553 {
554     if (obj->isInitialized())
555         return;
556     obj->fromVariantMap(properties);
557     obj->setInitialized();
558     emit objectInitialized(obj);
559     invokeSlot(obj, extendedMetaObject(obj)->updatedRemotelyId());
560 }
561
562 void SignalProxy::customEvent(QEvent* event)
563 {
564     switch ((int)event->type()) {
565     case RemovePeerEvent: {
566         auto* e = static_cast<::RemovePeerEvent*>(event);
567         removePeer(e->peer);
568         event->accept();
569         break;
570     }
571
572     default:
573         qWarning() << Q_FUNC_INFO << "Received unknown custom event:" << event->type();
574         return;
575     }
576 }
577
578 void SignalProxy::sync_call__(const SyncableObject* obj, SignalProxy::ProxyMode modeType, const char* funcname, va_list ap)
579 {
580     // qDebug() << obj << modeType << "(" << _proxyMode << ")" << funcname;
581     if (modeType != _proxyMode)
582         return;
583
584     ExtendedMetaObject* eMeta = extendedMetaObject(obj);
585
586     QVariantList params;
587
588     const QList<int>& argTypes = eMeta->argTypes(eMeta->methodId(QByteArray(funcname)));
589
590     for (int i = 0; i < argTypes.size(); i++) {
591         if (argTypes[i] == 0) {
592             qWarning() << Q_FUNC_INFO << "received invalid data for argument number" << i << "of signal"
593                        << QString("%1::%2").arg(eMeta->metaObject()->className()).arg(funcname);
594             qWarning() << "        - make sure all your data types are known by the Qt MetaSystem";
595             return;
596         }
597         params << QVariant(argTypes[i], va_arg(ap, void*));
598     }
599
600     if (_restrictMessageTarget) {
601         for (auto peer : _restrictedTargets) {
602             if (peer != nullptr)
603                 dispatch(peer, SyncMessage(eMeta->metaObject()->className(), obj->objectName(), QByteArray(funcname), params));
604         }
605     }
606     else
607         dispatch(SyncMessage(eMeta->metaObject()->className(), obj->objectName(), QByteArray(funcname), params));
608 }
609
610 void SignalProxy::disconnectDevice(QIODevice* dev, const QString& reason)
611 {
612     if (!reason.isEmpty())
613         qWarning() << qPrintable(reason);
614     auto* sock = qobject_cast<QAbstractSocket*>(dev);
615     if (sock)
616         qWarning() << qPrintable(tr("Disconnecting")) << qPrintable(sock->peerAddress().toString());
617     dev->close();
618 }
619
620 void SignalProxy::dumpProxyStats()
621 {
622     QString mode;
623     if (proxyMode() == Server)
624         mode = "Server";
625     else
626         mode = "Client";
627
628     int slaveCount = 0;
629     foreach (ObjectId oid, _syncSlave.values())
630         slaveCount += oid.count();
631
632     qDebug() << this;
633     qDebug() << "              Proxy Mode:" << mode;
634     qDebug() << "          attached Slots:" << _attachedSlots.size();
635     qDebug() << " number of synced Slaves:" << slaveCount;
636     qDebug() << "number of Classes cached:" << _extendedMetaObjects.count();
637 }
638
639 void SignalProxy::updateSecureState()
640 {
641     bool wasSecure = _secure;
642
643     _secure = !_peerMap.isEmpty();
644     for (auto peer : _peerMap.values()) {
645         _secure &= peer->isSecure();
646     }
647
648     if (wasSecure != _secure)
649         emit secureStateChanged(_secure);
650 }
651
652 QVariantList SignalProxy::peerData()
653 {
654     QVariantList result;
655     for (auto&& peer : _peerMap.values()) {
656         QVariantMap data;
657         data["id"] = peer->id();
658         data["clientVersion"] = peer->clientVersion();
659         // We explicitly rename this, as, due to the Debian reproducibility changes, buildDate isn’t actually the build
660         // date anymore, but on newer clients the date of the last git commit
661         data["clientVersionDate"] = peer->buildDate();
662         data["remoteAddress"] = peer->address();
663         data["connectedSince"] = peer->connectedSince();
664         data["secure"] = peer->isSecure();
665         data["features"] = static_cast<quint32>(peer->features().toLegacyFeatures());
666         data["featureList"] = peer->features().toStringList();
667         result << data;
668     }
669     return result;
670 }
671
672 Peer* SignalProxy::peerById(int peerId)
673 {
674     // We use ::value() here instead of the [] operator because the latter has the side-effect
675     // of automatically inserting a null value with the passed key into the map.  See
676     // https://doc.qt.io/qt-5/qhash.html#operator-5b-5d and https://doc.qt.io/qt-5/qhash.html#value.
677     return _peerMap.value(peerId);
678 }
679
680 void SignalProxy::restrictTargetPeers(QSet<Peer*> peers, std::function<void()> closure)
681 {
682     auto previousRestrictMessageTarget = _restrictMessageTarget;
683     auto previousRestrictedTargets = _restrictedTargets;
684     _restrictMessageTarget = true;
685     _restrictedTargets = peers;
686
687     closure();
688
689     _restrictMessageTarget = previousRestrictMessageTarget;
690     _restrictedTargets = previousRestrictedTargets;
691 }
692
693 Peer* SignalProxy::sourcePeer()
694 {
695     return _sourcePeer;
696 }
697
698 void SignalProxy::setSourcePeer(Peer* sourcePeer)
699 {
700     _sourcePeer = sourcePeer;
701 }
702
703 Peer* SignalProxy::targetPeer()
704 {
705     return _targetPeer;
706 }
707
708 void SignalProxy::setTargetPeer(Peer* targetPeer)
709 {
710     _targetPeer = targetPeer;
711 }
712
713 // ---- SlotObjectBase ---------------------------------------------------------------------------------------------------------------------
714
715 SignalProxy::SlotObjectBase::SlotObjectBase(const QObject* context)
716     : _context{context}
717 {}
718
719 const QObject* SignalProxy::SlotObjectBase::context() const
720 {
721     return _context;
722 }
723
724 //  ---- ExtendedMetaObject ----------------------------------------------------------------------------------------------------------------
725
726 SignalProxy::ExtendedMetaObject::ExtendedMetaObject(const QMetaObject* meta, bool checkConflicts)
727     : _meta(meta)
728     , _updatedRemotelyId(_meta->indexOfSignal("updatedRemotely()"))
729 {
730     for (int i = 0; i < _meta->methodCount(); i++) {
731         if (_meta->method(i).methodType() != QMetaMethod::Slot)
732             continue;
733
734         if (_meta->method(i).methodSignature().contains('*'))
735             continue;  // skip methods with ptr params
736
737         QByteArray method = methodName(_meta->method(i));
738         if (method.startsWith("init"))
739             continue;  // skip initializers
740
741         if (_methodIds.contains(method)) {
742             /* funny... moc creates for methods containing default parameters multiple metaMethod with separate methodIds.
743                we don't care... we just need the full fledged version
744              */
745             const QMetaMethod& current = _meta->method(_methodIds[method]);
746             const QMetaMethod& candidate = _meta->method(i);
747             if (current.parameterTypes().count() > candidate.parameterTypes().count()) {
748                 int minCount = candidate.parameterTypes().count();
749                 QList<QByteArray> commonParams = current.parameterTypes().mid(0, minCount);
750                 if (commonParams == candidate.parameterTypes())
751                     continue;  // we already got the full featured version
752             }
753             else {
754                 int minCount = current.parameterTypes().count();
755                 QList<QByteArray> commonParams = candidate.parameterTypes().mid(0, minCount);
756                 if (commonParams == current.parameterTypes()) {
757                     _methodIds[method] = i;  // use the new one
758                     continue;
759                 }
760             }
761             if (checkConflicts) {
762                 qWarning() << "class" << meta->className() << "contains overloaded methods which is currently not supported!";
763                 qWarning() << " - " << _meta->method(i).methodSignature() << "conflicts with"
764                            << _meta->method(_methodIds[method]).methodSignature();
765             }
766             continue;
767         }
768         _methodIds[method] = i;
769     }
770 }
771
772 const SignalProxy::ExtendedMetaObject::MethodDescriptor& SignalProxy::ExtendedMetaObject::methodDescriptor(int methodId)
773 {
774     if (!_methods.contains(methodId)) {
775         _methods[methodId] = MethodDescriptor(_meta->method(methodId));
776     }
777     return _methods[methodId];
778 }
779
780 const QHash<int, int>& SignalProxy::ExtendedMetaObject::receiveMap()
781 {
782     if (_receiveMap.isEmpty()) {
783         QHash<int, int> receiveMap;
784
785         QMetaMethod requestSlot;
786         QByteArray returnTypeName;
787         QByteArray signature;
788         QByteArray methodName;
789         QByteArray params;
790         int paramsPos;
791         int receiverId;
792         const int methodCount = _meta->methodCount();
793         for (int i = 0; i < methodCount; i++) {
794             requestSlot = _meta->method(i);
795             if (requestSlot.methodType() != QMetaMethod::Slot)
796                 continue;
797
798             returnTypeName = requestSlot.typeName();
799             if (QMetaType::Void == (QMetaType::Type)returnType(i))
800                 continue;
801
802             signature = requestSlot.methodSignature();
803             if (!signature.startsWith("request"))
804                 continue;
805
806             paramsPos = signature.indexOf('(');
807             if (paramsPos == -1)
808                 continue;
809
810             methodName = signature.left(paramsPos);
811             params = signature.mid(paramsPos);
812
813             methodName = methodName.replace("request", "receive");
814             params = params.left(params.count() - 1) + ", " + returnTypeName + ")";
815
816             signature = QMetaObject::normalizedSignature(methodName + params);
817             receiverId = _meta->indexOfSlot(signature);
818
819             if (receiverId == -1) {
820                 signature = QMetaObject::normalizedSignature(methodName + "(" + returnTypeName + ")");
821                 receiverId = _meta->indexOfSlot(signature);
822             }
823
824             if (receiverId != -1) {
825                 receiveMap[i] = receiverId;
826             }
827         }
828         _receiveMap = receiveMap;
829     }
830     return _receiveMap;
831 }
832
833 QByteArray SignalProxy::ExtendedMetaObject::methodName(const QMetaMethod& method)
834 {
835     QByteArray sig(method.methodSignature());
836     return sig.left(sig.indexOf("("));
837 }
838
839 QString SignalProxy::ExtendedMetaObject::methodBaseName(const QMetaMethod& method)
840 {
841     QString methodname = QString(method.methodSignature()).section("(", 0, 0);
842
843     // determine where we have to chop:
844     int upperCharPos;
845     if (method.methodType() == QMetaMethod::Slot) {
846         // we take evertyhing from the first uppercase char if it's slot
847         upperCharPos = methodname.indexOf(QRegExp("[A-Z]"));
848         if (upperCharPos == -1)
849             return QString();
850         methodname = methodname.mid(upperCharPos);
851     }
852     else {
853         // and if it's a signal we discard everything from the last uppercase char
854         upperCharPos = methodname.lastIndexOf(QRegExp("[A-Z]"));
855         if (upperCharPos == -1)
856             return QString();
857         methodname = methodname.left(upperCharPos);
858     }
859
860     methodname[0] = methodname[0].toUpper();
861
862     return methodname;
863 }
864
865 SignalProxy::ExtendedMetaObject::MethodDescriptor::MethodDescriptor(const QMetaMethod& method)
866     : _methodName(SignalProxy::ExtendedMetaObject::methodName(method))
867     , _returnType(QMetaType::type(method.typeName()))
868 {
869     // determine argTypes
870     QList<QByteArray> paramTypes = method.parameterTypes();
871     QList<int> argTypes;
872     for (int i = 0; i < paramTypes.count(); i++) {
873         argTypes.append(QMetaType::type(paramTypes[i]));
874     }
875     _argTypes = argTypes;
876
877     // determine minArgCount
878     QString signature(method.methodSignature());
879     _minArgCount = method.parameterTypes().count() - signature.count("=");
880
881     _receiverMode = (_methodName.startsWith("request")) ? SignalProxy::Server : SignalProxy::Client;
882 }