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