1 /***************************************************************************
2 * Copyright (C) 2005-2020 by the Quassel Project *
3 * devel@quassel-irc.org *
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. *
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. *
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 ***************************************************************************/
23 #include "common-export.h"
26 #include <initializer_list>
28 #include <type_traits>
29 #include <unordered_map>
34 #include <QMetaMethod>
38 #include "funchelpers.h"
48 class COMMON_EXPORT SignalProxy : public QObject
52 template<typename Slot, typename Callable = typename FunctionTraits<Slot>::FunctionType>
65 RemovePeerEvent = QEvent::User
68 SignalProxy(QObject* parent);
69 SignalProxy(ProxyMode mode, QObject* parent);
70 ~SignalProxy() override;
72 void setProxyMode(ProxyMode mode);
73 inline ProxyMode proxyMode() const { return _proxyMode; }
75 void setHeartBeatInterval(int secs);
76 inline int heartBeatInterval() const { return _heartBeatInterval; }
77 void setMaxHeartBeatCount(int max);
78 inline int maxHeartBeatCount() const { return _maxHeartBeatCount; }
80 bool addPeer(Peer* peer);
83 * Attaches a signal for remote emission.
85 * After calling this method, whenever the sender emits the given signal, an RpcCall message is sent to connected peers.
86 * On the other end, a slot can be attached to handle this message by calling attachSlot().
88 * By default, the signal name being sent is as if the SIGNAL() macro had been used, i.e. the normalized signature prefixed with a '2'.
89 * This can be overridden by explicitly providing the signalName argument.
93 * @param sender The sender of the signal
94 * @param signal The signal itself (given as a member function pointer)
95 * @param signalName Optional string to be used instead of the actual signal name. Will be normalized.
96 * @returns true if attaching the signal was successful
98 template<typename Signal>
99 bool attachSignal(const typename FunctionTraits<Signal>::ClassType* sender, Signal signal, const QByteArray& signalName = {});
102 * Attaches a slot to a remote signal.
104 * After calling this method, upon receipt of an RpcCall message with a signalName matching the signalName parameter, the given slot
105 * is called with the parameters contained in the message. This is intended to be used in conjunction with attachSignal() on the other
106 * end of the connection.
108 * Normally, the signalName should be given using the SIGNAL() macro; it will be normalized automatically.
112 * @param signalName Name of the signal as stored in the RpcCall message
113 * @param receiver Receiver of the signal
114 * @param slot Slot to be called (given as a member function pointer)
115 * @returns true if attaching the slot was successful
117 template<typename Slot, typename = std::enable_if_t<std::is_member_function_pointer<Slot>::value>>
118 bool attachSlot(const QByteArray& signalName, typename FunctionTraits<Slot>::ClassType* receiver, Slot slot);
123 * Attaches a functor to a remote signal.
125 * After calling this method, upon receipt of an RpcCall message with a signalName matching the signalName parameter, the given functor
126 * is invoked with the parameters contained in the message. This is intended to be used in conjunction with attachSignal() on the other
127 * end of the connection. This overload can be used, for example, with a lambda that accepts arguments matching the RpcCall parameter
130 * The context parameter controls the lifetime of the connection; if the context is deleted, the functor is deleted as well.
134 * @param signalName Name of the signal as stored in the RpcCall message
135 * @param context QObject context controlling the lifetime of the callable
136 * @param slot The functor to be invoked
137 * @returns true if attaching the functor was successful
139 template<typename Slot, typename = std::enable_if_t<!std::is_member_function_pointer<Slot>::value>>
140 bool attachSlot(const QByteArray& signalName, const QObject* context, Slot slot);
142 void synchronize(SyncableObject* obj);
143 void stopSynchronize(SyncableObject* obj);
145 class ExtendedMetaObject;
146 ExtendedMetaObject* extendedMetaObject(const QMetaObject* meta) const;
147 ExtendedMetaObject* createExtendedMetaObject(const QMetaObject* meta, bool checkConflicts = false);
148 inline ExtendedMetaObject* extendedMetaObject(const QObject* obj) const { return extendedMetaObject(metaObject(obj)); }
149 inline ExtendedMetaObject* createExtendedMetaObject(const QObject* obj, bool checkConflicts = false)
151 return createExtendedMetaObject(metaObject(obj), checkConflicts);
154 bool isSecure() const { return _secure; }
155 void dumpProxyStats();
156 void dumpSyncMap(SyncableObject* object);
158 static SignalProxy* current();
162 * This method allows to send a signal only to a limited set of peers
163 * @param peers A list of peers that should receive it
164 * @param closure Code you want to execute within of that restricted environment
166 void restrictTargetPeers(QSet<Peer*> peers, std::function<void()> closure);
167 void restrictTargetPeers(Peer* peer, std::function<void()> closure)
171 restrictTargetPeers(set, std::move(closure));
174 // A better version, but only implemented on Qt5 if Initializer Lists exist
175 #ifdef Q_COMPILER_INITIALIZER_LISTS
176 void restrictTargetPeers(std::initializer_list<Peer*> peers, std::function<void()> closure)
178 restrictTargetPeers(QSet<Peer*>(peers), std::move(closure));
183 inline int peerCount() const { return _peerMap.size(); }
184 QVariantList peerData();
186 Peer* peerById(int peerId);
189 * @return If handling a signal, the Peer from which the current signal originates
192 void setSourcePeer(Peer* sourcePeer);
195 * @return If sending a signal, the Peer to which the current signal is directed
198 void setTargetPeer(Peer* targetPeer);
201 void customEvent(QEvent* event) override;
202 void sync_call__(const SyncableObject* obj, ProxyMode modeType, const char* funcname, va_list ap);
203 void renameObject(const SyncableObject* obj, const QString& newname, const QString& oldname);
206 void removePeerBySender();
207 void objectRenamed(const QByteArray& classname, const QString& newname, const QString& oldname);
208 void updateSecureState();
211 void peerRemoved(Peer* peer);
214 void objectInitialized(SyncableObject*);
215 void heartBeatIntervalChanged(int secs);
216 void maxHeartBeatCountChanged(int max);
217 void lagUpdated(int lag);
218 void secureStateChanged(bool);
222 class PeerMessageEvent;
228 static const QMetaObject* metaObject(const QObject* obj);
230 void removePeer(Peer* peer);
231 void removeAllPeers();
233 int nextPeerId() { return _lastPeerId++; }
236 * Attaches a SlotObject for the given signal name.
238 * @param signalName Signal name to be associated with the SlotObject
239 * @param slotObject The SlotObject instance to be invoked for incoming and matching RpcCall messages
241 void attachSlotObject(const QByteArray& signalName, std::unique_ptr<SlotObjectBase> slotObject);
244 * Deletes all SlotObjects associated with the given context.
246 * @param context The context
248 void detachSlotObjects(const QObject* context);
251 * Dispatches an RpcMessage for the given signal and parameters.
253 * @param signalName The signal
254 * @param params The parameters
256 void dispatchSignal(QByteArray signalName, QVariantList params);
259 void dispatch(const T& protoMessage);
261 void dispatch(Peer* peer, const T& protoMessage);
263 void handle(Peer* peer, const Protocol::SyncMessage& syncMessage);
264 void handle(Peer* peer, const Protocol::RpcCall& rpcCall);
265 void handle(Peer* peer, const Protocol::InitRequest& initRequest);
266 void handle(Peer* peer, const Protocol::InitData& initData);
269 void handle(Peer*, T)
274 bool invokeSlot(QObject* receiver, int methodId, const QVariantList& params, QVariant& returnValue, Peer* peer = nullptr);
275 bool invokeSlot(QObject* receiver, int methodId, const QVariantList& params = QVariantList(), Peer* peer = nullptr);
277 void requestInit(SyncableObject* obj);
278 QVariantMap initData(SyncableObject* obj) const;
279 void setInitData(SyncableObject* obj, const QVariantMap& properties);
281 static void disconnectDevice(QIODevice* dev, const QString& reason = QString());
284 QHash<int, Peer*> _peerMap;
286 // containg a list of argtypes for fast access
287 QHash<const QMetaObject*, ExtendedMetaObject*> _extendedMetaObjects;
289 std::unordered_multimap<QByteArray, std::unique_ptr<SlotObjectBase>, Hash<QByteArray>> _attachedSlots; ///< Attached slot objects
292 using ObjectId = QHash<QString, SyncableObject*>;
293 QHash<QByteArray, ObjectId> _syncSlave;
295 ProxyMode _proxyMode;
296 int _heartBeatInterval;
297 int _maxHeartBeatCount;
299 bool _secure; // determines if all connections are in a secured state (using ssl or internal connections)
303 QSet<Peer*> _restrictedTargets;
304 bool _restrictMessageTarget = false;
306 Peer* _sourcePeer = nullptr;
307 Peer* _targetPeer = nullptr;
309 friend class SyncableObject;
313 // ---- Template function implementations ---------------------------------------
315 template<typename Signal>
316 bool SignalProxy::attachSignal(const typename FunctionTraits<Signal>::ClassType* sender, Signal signal, const QByteArray& signalName)
318 static_assert(std::is_member_function_pointer<Signal>::value, "Signal must be given as member function pointer");
320 // Determine the signalName to be stored in the RpcCall
322 if (signalName.isEmpty()) {
323 auto method = QMetaMethod::fromSignal(signal);
324 if (!method.isValid()) {
325 qWarning().nospace() << Q_FUNC_INFO << ": Function pointer is not a signal";
328 name = "2" + method.methodSignature(); // SIGNAL() prefixes the signature with "2"
331 name = QMetaObject::normalizedSignature(signalName.constData());
334 // Upon signal emission, marshall the signal's arguments into a QVariantList and dispatch an RpcCall message
335 connect(sender, signal, this, [this, signalName = std::move(name)](auto&&... args) {
336 this->dispatchSignal(std::move(signalName), {QVariant::fromValue(args)...});
342 template<typename Slot, typename>
343 bool SignalProxy::attachSlot(const QByteArray& signalName, typename FunctionTraits<Slot>::ClassType* receiver, Slot slot)
345 // Create a wrapper function that invokes the member function pointer for the receiver instance
346 attachSlotObject(signalName, std::make_unique<SlotObject<Slot>>(receiver, [receiver, slot = std::move(slot)](auto&&... args) {
347 (receiver->*slot)(std::forward<decltype(args)>(args)...);
352 template<typename Slot, typename>
353 bool SignalProxy::attachSlot(const QByteArray& signalName, const QObject* context, Slot slot)
355 static_assert(!std::is_same<Slot, const char*>::value, "Old-style slots not supported");
357 attachSlotObject(signalName, std::make_unique<SlotObject<Slot>>(context, std::move(slot)));
362 * Base object for storing a slot (or functor) to be invoked with a list of arguments.
364 * @note Having this untemplated base class for SlotObject allows for handling slots in the implementation rather than in the header.
366 class COMMON_EXPORT SignalProxy::SlotObjectBase
369 virtual ~SlotObjectBase() = default;
372 * @returns The context associated with the slot
374 const QObject* context() const;
377 * Invokes the slot with the given list of parameters.
379 * If the parameters cannot all be converted to the slot's argument types, or there is a mismatch in argument count,
380 * the invocation will fail.
382 * @param params List of arguments marshalled as QVariants
383 * @returns true if the invocation was successful
385 virtual bool invoke(const QVariantList& params) const = 0;
388 SlotObjectBase(const QObject* context);
391 const QObject* _context;
395 * Specialization of SlotObjectBase for a particular type of slot.
397 * Callable may be a function wrapper around a member function pointer of type Slot,
398 * or a functor that can be invoked directly.
400 * @tparam Slot Type of the slot, used for determining the callable's signature
401 * @tparam Callable Type of the actual callable to be invoked
403 template<typename Slot, typename Callable>
404 class SignalProxy::SlotObject : public SlotObjectBase
408 * Constructs a SlotObject for the given callable, whose lifetime is controlled by context.
410 * @param context Context object; if destroyed, the slot object will be destroyed as well by SignalProxy.
411 * @param callable Callable to be invoked
413 SlotObject(const QObject* context, Callable callable)
414 : SlotObjectBase(context)
415 , _callable(std::move(callable))
419 bool invoke(const QVariantList& params) const override
421 if (QThread::currentThread() != context()->thread()) {
422 qWarning() << "Cannot call slot in different thread!";
425 return invokeWithArgsList(_callable, params) ? true : false;
432 // ==================================================
433 // ExtendedMetaObject
434 // ==================================================
435 class SignalProxy::ExtendedMetaObject
437 class MethodDescriptor
440 MethodDescriptor(const QMetaMethod& method);
441 MethodDescriptor() = default;
443 inline const QByteArray& methodName() const { return _methodName; }
444 inline const QList<int>& argTypes() const { return _argTypes; }
445 inline int returnType() const { return _returnType; }
446 inline int minArgCount() const { return _minArgCount; }
447 inline SignalProxy::ProxyMode receiverMode() const { return _receiverMode; }
450 QByteArray _methodName;
451 QList<int> _argTypes;
453 int _minArgCount{-1};
454 SignalProxy::ProxyMode _receiverMode{
455 SignalProxy::Client}; // Only acceptable as a Sync Call if the receiving SignalProxy is in this mode.
459 ExtendedMetaObject(const QMetaObject* meta, bool checkConflicts);
461 inline const QByteArray& methodName(int methodId) { return methodDescriptor(methodId).methodName(); }
462 inline const QList<int>& argTypes(int methodId) { return methodDescriptor(methodId).argTypes(); }
463 inline int returnType(int methodId) { return methodDescriptor(methodId).returnType(); }
464 inline int minArgCount(int methodId) { return methodDescriptor(methodId).minArgCount(); }
465 inline SignalProxy::ProxyMode receiverMode(int methodId) { return methodDescriptor(methodId).receiverMode(); }
467 inline int methodId(const QByteArray& methodName) { return _methodIds.contains(methodName) ? _methodIds[methodName] : -1; }
469 inline int updatedRemotelyId() { return _updatedRemotelyId; }
471 inline const QHash<QByteArray, int>& slotMap() { return _methodIds; }
472 const QHash<int, int>& receiveMap();
474 const QMetaObject* metaObject() const { return _meta; }
476 static QByteArray methodName(const QMetaMethod& method);
477 static QString methodBaseName(const QMetaMethod& method);
480 const MethodDescriptor& methodDescriptor(int methodId);
482 const QMetaObject* _meta;
483 int _updatedRemotelyId; // id of the updatedRemotely() signal - makes things faster
485 QHash<int, MethodDescriptor> _methods;
486 QHash<QByteArray, int> _methodIds;
487 QHash<int, int> _receiveMap; // if slot x is called then hand over the result to slot y