+#include "syncableobject.h"
+#include "types.h"
+#include "util.h"
+
+using namespace Protocol;
+
+class RemovePeerEvent : public QEvent
+{
+public:
+ RemovePeerEvent(Peer* peer)
+ : QEvent(QEvent::Type(SignalProxy::RemovePeerEvent))
+ , peer(peer)
+ {}
+ Peer* peer;
+};
+
+// ==================================================
+// SignalProxy
+// ==================================================
+
+namespace {
+thread_local SignalProxy* _current{nullptr};
+}
+
+SignalProxy::SignalProxy(QObject* parent)
+ : QObject(parent)
+{
+ setProxyMode(Client);
+ init();
+}
+
+SignalProxy::SignalProxy(ProxyMode mode, QObject* parent)
+ : QObject(parent)
+{
+ setProxyMode(mode);
+ init();
+}
+
+SignalProxy::~SignalProxy()
+{
+ QHash<QByteArray, ObjectId>::iterator classIter = _syncSlave.begin();
+ while (classIter != _syncSlave.end()) {
+ ObjectId::iterator objIter = classIter->begin();
+ while (objIter != classIter->end()) {
+ SyncableObject* obj = objIter.value();
+ objIter = classIter->erase(objIter);
+ obj->stopSynchronize(this);
+ }
+ ++classIter;
+ }
+ _syncSlave.clear();
+
+ removeAllPeers();
+
+ // Ensure that we don't try to clean up while destroying ourselves
+ disconnect(this, &QObject::destroyed, this, &SignalProxy::detachSlotObjects);
+
+ _current = nullptr;
+}
+
+SignalProxy* SignalProxy::current()
+{
+ return _current;
+}
+
+void SignalProxy::setProxyMode(ProxyMode mode)
+{
+ if (!_peerMap.empty()) {
+ qWarning() << Q_FUNC_INFO << "Cannot change proxy mode while connected";
+ return;
+ }
+
+ _proxyMode = mode;
+ if (mode == Server)
+ initServer();
+ else
+ initClient();
+}
+
+void SignalProxy::init()
+{
+ _heartBeatInterval = 0;
+ _maxHeartBeatCount = 0;
+ setHeartBeatInterval(30);
+ setMaxHeartBeatCount(2);
+ _secure = false;
+ _current = this;
+ updateSecureState();
+}
+
+void SignalProxy::initServer() {}
+
+void SignalProxy::initClient()
+{
+ attachSlot("__objectRenamed__", this, &SignalProxy::objectRenamed);
+}
+
+void SignalProxy::setHeartBeatInterval(int secs)
+{
+ if (_heartBeatInterval != secs) {
+ _heartBeatInterval = secs;
+ emit heartBeatIntervalChanged(secs);
+ }
+}
+
+void SignalProxy::setMaxHeartBeatCount(int max)
+{
+ if (_maxHeartBeatCount != max) {
+ _maxHeartBeatCount = max;
+ emit maxHeartBeatCountChanged(max);
+ }
+}
+
+bool SignalProxy::addPeer(Peer* peer)
+{
+ if (!peer)
+ return false;
+
+ if (_peerMap.values().contains(peer))
+ return true;
+
+ if (!peer->isOpen()) {
+ qWarning("SignalProxy: peer needs to be open!");
+ return false;
+ }
+
+ if (proxyMode() == Client) {
+ if (!_peerMap.isEmpty()) {
+ qWarning("SignalProxy: only one peer allowed in client mode!");
+ return false;
+ }
+ connect(peer, &Peer::lagUpdated, this, &SignalProxy::lagUpdated);
+ }
+
+ connect(peer, &Peer::disconnected, this, &SignalProxy::removePeerBySender);
+ connect(peer, &Peer::secureStateChanged, this, &SignalProxy::updateSecureState);
+
+ if (!peer->parent())
+ peer->setParent(this);
+
+ if (peer->id() < 0) {
+ peer->setId(nextPeerId());
+ peer->setConnectedSince(QDateTime::currentDateTimeUtc());
+ }
+
+ _peerMap[peer->id()] = peer;
+
+ peer->setSignalProxy(this);
+
+ if (peerCount() == 1)
+ emit connected();
+
+ updateSecureState();
+ return true;
+}
+
+void SignalProxy::removeAllPeers()
+{
+ Q_ASSERT(proxyMode() == Server || peerCount() <= 1);
+ // wee need to copy that list since we modify it in the loop
+ QList<Peer*> peers = _peerMap.values();
+ for (auto peer : peers) {
+ removePeer(peer);
+ }
+}
+
+void SignalProxy::removePeer(Peer* peer)
+{
+ if (!peer) {
+ qWarning() << Q_FUNC_INFO << "Trying to remove a null peer!";
+ return;
+ }
+
+ if (_peerMap.isEmpty()) {
+ qWarning() << "SignalProxy::removePeer(): No peers in use!";
+ return;
+ }
+
+ if (!_peerMap.values().contains(peer)) {
+ qWarning() << "SignalProxy: unknown Peer" << peer;
+ return;
+ }
+
+ disconnect(peer, nullptr, this, nullptr);
+ peer->setSignalProxy(nullptr);
+
+ _peerMap.remove(peer->id());
+ emit peerRemoved(peer);
+
+ if (peer->parent() == this)
+ peer->deleteLater();
+
+ updateSecureState();
+
+ if (_peerMap.isEmpty())
+ emit disconnected();
+}
+
+void SignalProxy::removePeerBySender()
+{
+ removePeer(qobject_cast<Peer*>(sender()));
+}
+
+void SignalProxy::renameObject(const SyncableObject* obj, const QString& newname, const QString& oldname)
+{
+ if (proxyMode() == Client)
+ return;
+
+ const QMetaObject* meta = obj->syncMetaObject();
+ const QByteArray className(meta->className());
+ objectRenamed(className, newname, oldname);
+
+ dispatch(RpcCall("__objectRenamed__", QVariantList() << className << newname << oldname));
+}
+
+void SignalProxy::objectRenamed(const QByteArray& classname, const QString& newname, const QString& oldname)
+{
+ if (newname != oldname) {
+ if (_syncSlave.contains(classname) && _syncSlave[classname].contains(oldname)) {
+ SyncableObject* obj = _syncSlave[classname][newname] = _syncSlave[classname].take(oldname);
+ obj->setObjectName(newname);
+ requestInit(obj);
+ }
+ }
+}
+
+const QMetaObject* SignalProxy::metaObject(const QObject* obj)
+{
+ if (const auto* syncObject = qobject_cast<const SyncableObject*>(obj))
+ return syncObject->syncMetaObject();
+ else
+ return obj->metaObject();
+}
+
+SignalProxy::ExtendedMetaObject* SignalProxy::extendedMetaObject(const QMetaObject* meta) const
+{
+ if (_extendedMetaObjects.contains(meta))
+ return _extendedMetaObjects[meta];
+ else
+ return nullptr;
+}
+
+SignalProxy::ExtendedMetaObject* SignalProxy::createExtendedMetaObject(const QMetaObject* meta, bool checkConflicts)
+{
+ if (!_extendedMetaObjects.contains(meta)) {
+ _extendedMetaObjects[meta] = new ExtendedMetaObject(meta, checkConflicts);
+ }
+ return _extendedMetaObjects[meta];
+}
+
+void SignalProxy::attachSlotObject(const QByteArray& signalName, std::unique_ptr<SlotObjectBase> slotObject)
+{
+ // Remove all attached slots related to the context upon its destruction
+ connect(slotObject->context(), &QObject::destroyed, this, &SignalProxy::detachSlotObjects, Qt::UniqueConnection);
+
+ _attachedSlots.emplace(QMetaObject::normalizedSignature(signalName.constData()), std::move(slotObject));
+}
+
+void SignalProxy::detachSlotObjects(const QObject *context)
+{
+ for (auto&& it = _attachedSlots.begin(); it != _attachedSlots.end(); ) {
+ if (it->second->context() == context) {
+ it = _attachedSlots.erase(it);
+ }
+ else {
+ ++it;
+ }
+ }
+}
+
+void SignalProxy::synchronize(SyncableObject* obj)
+{
+ createExtendedMetaObject(obj, true);
+
+ // attaching as slave to receive sync Calls
+ QByteArray className(obj->syncMetaObject()->className());
+ _syncSlave[className][obj->objectName()] = obj;
+
+ if (proxyMode() == Server) {
+ obj->setInitialized();
+ emit objectInitialized(obj);
+ }
+ else {
+ if (obj->isInitialized())
+ emit objectInitialized(obj);
+ else
+ requestInit(obj);
+ }
+
+ obj->synchronize(this);
+}
+
+void SignalProxy::stopSynchronize(SyncableObject* obj)
+{
+ // we can't use a className here, since it might be effed up, if we receive the call as a result of a decon
+ // gladly the objectName() is still valid. So we have only to iterate over the classes not each instance! *sigh*
+ QHash<QByteArray, ObjectId>::iterator classIter = _syncSlave.begin();
+ while (classIter != _syncSlave.end()) {
+ if (classIter->contains(obj->objectName()) && classIter.value()[obj->objectName()] == obj) {
+ classIter->remove(obj->objectName());
+ break;
+ }
+ ++classIter;
+ }
+ obj->stopSynchronize(this);
+}
+
+void SignalProxy::dispatchSignal(QByteArray sigName, QVariantList params)
+{
+ RpcCall rpcCall{std::move(sigName), std::move(params)};
+ if (_restrictMessageTarget) {
+ for (auto&& peer : _restrictedTargets) {
+ dispatch(peer, rpcCall);
+ }
+ }
+ else {
+ dispatch(rpcCall);
+ }
+}
+
+template<class T>
+void SignalProxy::dispatch(const T& protoMessage)
+{
+ for (auto&& peer : _peerMap.values()) {
+ dispatch(peer, protoMessage);
+ }
+}
+
+template<class T>
+void SignalProxy::dispatch(Peer* peer, const T& protoMessage)
+{
+ _targetPeer = peer;
+
+ if (peer && peer->isOpen())
+ peer->dispatch(protoMessage);
+ else
+ QCoreApplication::postEvent(this, new ::RemovePeerEvent(peer));
+
+ _targetPeer = nullptr;
+}
+
+void SignalProxy::handle(Peer* peer, const SyncMessage& syncMessage)
+{
+ if (!_syncSlave.contains(syncMessage.className) || !_syncSlave[syncMessage.className].contains(syncMessage.objectName)) {
+ qWarning() << QString("no registered receiver for sync call: %1::%2 (objectName=\"%3\"). Params are:")
+ .arg(syncMessage.className, syncMessage.slotName, syncMessage.objectName)
+ << syncMessage.params;
+ return;
+ }
+
+ SyncableObject* receiver = _syncSlave[syncMessage.className][syncMessage.objectName];
+ ExtendedMetaObject* eMeta = extendedMetaObject(receiver);
+ if (!eMeta->slotMap().contains(syncMessage.slotName)) {
+ qWarning() << QString("no matching slot for sync call: %1::%2 (objectName=\"%3\"). Params are:")
+ .arg(syncMessage.className, syncMessage.slotName, syncMessage.objectName)
+ << syncMessage.params;
+ return;
+ }
+
+ int slotId = eMeta->slotMap()[syncMessage.slotName];
+ if (proxyMode() != eMeta->receiverMode(slotId)) {
+ qWarning("SignalProxy::handleSync(): invokeMethod for \"%s\" failed. Wrong ProxyMode!", eMeta->methodName(slotId).constData());
+ return;
+ }
+
+ // We can no longer construct a QVariant from QMetaType::Void
+ QVariant returnValue;
+ int returnType = eMeta->returnType(slotId);
+ if (returnType != QMetaType::Void)
+ returnValue = QVariant(static_cast<QVariant::Type>(returnType));
+
+ if (!invokeSlot(receiver, slotId, syncMessage.params, returnValue, peer)) {
+ qWarning("SignalProxy::handleSync(): invokeMethod for \"%s\" failed ", eMeta->methodName(slotId).constData());
+ return;
+ }