cmake: avoid de-duplication of user's CXXFLAGS
[quassel.git] / src / common / signalproxy.h
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 #pragma once
22
23 #include "common-export.h"
24
25 #include <functional>
26 #include <initializer_list>
27 #include <memory>
28 #include <type_traits>
29 #include <unordered_map>
30 #include <utility>
31
32 #include <QDebug>
33 #include <QEvent>
34 #include <QMetaMethod>
35 #include <QSet>
36 #include <QThread>
37
38 #include "funchelpers.h"
39 #include "protocol.h"
40 #include "types.h"
41
42 struct QMetaObject;
43 class QIODevice;
44
45 class Peer;
46 class SyncableObject;
47
48 class COMMON_EXPORT SignalProxy : public QObject
49 {
50     Q_OBJECT
51
52     template<typename Slot, typename Callable = typename FunctionTraits<Slot>::FunctionType>
53     class SlotObject;
54     class SlotObjectBase;
55
56 public:
57     enum ProxyMode
58     {
59         Server,
60         Client
61     };
62
63     enum EventType
64     {
65         RemovePeerEvent = QEvent::User
66     };
67
68     SignalProxy(QObject* parent);
69     SignalProxy(ProxyMode mode, QObject* parent);
70     ~SignalProxy() override;
71
72     void setProxyMode(ProxyMode mode);
73     inline ProxyMode proxyMode() const { return _proxyMode; }
74
75     void setHeartBeatInterval(int secs);
76     inline int heartBeatInterval() const { return _heartBeatInterval; }
77     void setMaxHeartBeatCount(int max);
78     inline int maxHeartBeatCount() const { return _maxHeartBeatCount; }
79
80     bool addPeer(Peer* peer);
81
82     /**
83      * Attaches a signal for remote emission.
84      *
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().
87
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.
90      *
91      * @sa attachSlot
92      *
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
97      */
98     template<typename Signal>
99     bool attachSignal(const typename FunctionTraits<Signal>::ClassType* sender, Signal signal, const QByteArray& signalName = {});
100
101     /**
102      * Attaches a slot to a remote signal.
103      *
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.
107      *
108      * Normally, the signalName should be given using the SIGNAL() macro; it will be normalized automatically.
109      *
110      * @sa attachSignal
111      *
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
116      */
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);
119
120     /**
121      * @overload
122      *
123      * Attaches a functor to a remote signal.
124      *
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
128      * list.
129      *
130      * The context parameter controls the lifetime of the connection; if the context is deleted, the functor is deleted as well.
131      *
132      * @sa attachSignal
133      *
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
138      */
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);
141
142     void synchronize(SyncableObject* obj);
143     void stopSynchronize(SyncableObject* obj);
144
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)
150     {
151         return createExtendedMetaObject(metaObject(obj), checkConflicts);
152     }
153
154     bool isSecure() const { return _secure; }
155     void dumpProxyStats();
156     void dumpSyncMap(SyncableObject* object);
157
158     static SignalProxy* current();
159
160     /**@{*/
161     /**
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
165      */
166     void restrictTargetPeers(QSet<Peer*> peers, std::function<void()> closure);
167     void restrictTargetPeers(Peer* peer, std::function<void()> closure)
168     {
169         QSet<Peer*> set;
170         set.insert(peer);
171         restrictTargetPeers(set, std::move(closure));
172     }
173
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)
177     {
178         restrictTargetPeers(QSet<Peer*>(peers), std::move(closure));
179     }
180 #endif
181     /**}@*/
182
183     inline int peerCount() const { return _peerMap.size(); }
184     QVariantList peerData();
185
186     Peer* peerById(int peerId);
187
188     /**
189      * @return If handling a signal, the Peer from which the current signal originates
190      */
191     Peer* sourcePeer();
192     void setSourcePeer(Peer* sourcePeer);
193
194     /**
195      * @return If sending a signal, the Peer to which the current signal is directed
196      */
197     Peer* targetPeer();
198     void setTargetPeer(Peer* targetPeer);
199
200 protected:
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);
204
205 private slots:
206     void removePeerBySender();
207     void objectRenamed(const QByteArray& classname, const QString& newname, const QString& oldname);
208     void updateSecureState();
209
210 signals:
211     void peerRemoved(Peer* peer);
212     void connected();
213     void disconnected();
214     void objectInitialized(SyncableObject*);
215     void heartBeatIntervalChanged(int secs);
216     void maxHeartBeatCountChanged(int max);
217     void lagUpdated(int lag);
218     void secureStateChanged(bool);
219
220 private:
221     template<class T>
222     class PeerMessageEvent;
223
224     void init();
225     void initServer();
226     void initClient();
227
228     static const QMetaObject* metaObject(const QObject* obj);
229
230     void removePeer(Peer* peer);
231     void removeAllPeers();
232
233     int nextPeerId() { return _lastPeerId++; }
234
235     /**
236      * Attaches a SlotObject for the given signal name.
237      *
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
240      */
241     void attachSlotObject(const QByteArray& signalName, std::unique_ptr<SlotObjectBase> slotObject);
242
243     /**
244      * Deletes all SlotObjects associated with the given context.
245      *
246      * @param context The context
247      */
248     void detachSlotObjects(const QObject* context);
249
250     /**
251      * Dispatches an RpcMessage for the given signal and parameters.
252      *
253      * @param signalName The signal
254      * @param params     The parameters
255      */
256     void dispatchSignal(QByteArray signalName, QVariantList params);
257
258     template<class T>
259     void dispatch(const T& protoMessage);
260     template<class T>
261     void dispatch(Peer* peer, const T& protoMessage);
262
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);
267
268     template<class T>
269     void handle(Peer*, T)
270     {
271         Q_ASSERT(0);
272     }
273
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);
276
277     void requestInit(SyncableObject* obj);
278     QVariantMap initData(SyncableObject* obj) const;
279     void setInitData(SyncableObject* obj, const QVariantMap& properties);
280
281     static void disconnectDevice(QIODevice* dev, const QString& reason = QString());
282
283 private:
284     QHash<int, Peer*> _peerMap;
285
286     // containg a list of argtypes for fast access
287     QHash<const QMetaObject*, ExtendedMetaObject*> _extendedMetaObjects;
288
289     std::unordered_multimap<QByteArray, std::unique_ptr<SlotObjectBase>, Hash<QByteArray>> _attachedSlots;  ///< Attached slot objects
290
291     // slaves for sync
292     using ObjectId = QHash<QString, SyncableObject*>;
293     QHash<QByteArray, ObjectId> _syncSlave;
294
295     ProxyMode _proxyMode;
296     int _heartBeatInterval;
297     int _maxHeartBeatCount;
298
299     bool _secure;  // determines if all connections are in a secured state (using ssl or internal connections)
300
301     int _lastPeerId = 0;
302
303     QSet<Peer*> _restrictedTargets;
304     bool _restrictMessageTarget = false;
305
306     Peer* _sourcePeer = nullptr;
307     Peer* _targetPeer = nullptr;
308
309     friend class SyncableObject;
310     friend class Peer;
311 };
312
313 // ---- Template function implementations ---------------------------------------
314
315 template<typename Signal>
316 bool SignalProxy::attachSignal(const typename FunctionTraits<Signal>::ClassType* sender, Signal signal, const QByteArray& signalName)
317 {
318     static_assert(std::is_member_function_pointer<Signal>::value, "Signal must be given as member function pointer");
319
320     // Determine the signalName to be stored in the RpcCall
321     QByteArray name;
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";
326             return false;
327         }
328         name = "2" + method.methodSignature();  // SIGNAL() prefixes the signature with "2"
329     }
330     else {
331         name = QMetaObject::normalizedSignature(signalName.constData());
332     }
333
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<decltype(args)>(args)...});
337     });
338
339     return true;
340 }
341
342 template<typename Slot, typename>
343 bool SignalProxy::attachSlot(const QByteArray& signalName, typename FunctionTraits<Slot>::ClassType* receiver, Slot slot)
344 {
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)...);
348     }));
349     return true;
350 }
351
352 template<typename Slot, typename>
353 bool SignalProxy::attachSlot(const QByteArray& signalName, const QObject* context, Slot slot)
354 {
355     static_assert(!std::is_same<Slot, const char*>::value, "Old-style slots not supported");
356
357     attachSlotObject(signalName, std::make_unique<SlotObject<Slot>>(context, std::move(slot)));
358     return true;
359 }
360
361 /**
362  * Base object for storing a slot (or functor) to be invoked with a list of arguments.
363  *
364  * @note Having this untemplated base class for SlotObject allows for handling slots in the implementation rather than in the header.
365  */
366 class COMMON_EXPORT SignalProxy::SlotObjectBase
367 {
368 public:
369     virtual ~SlotObjectBase() = default;
370
371     /**
372      * @returns The context associated with the slot
373      */
374     const QObject* context() const;
375
376     /**
377      * Invokes the slot with the given list of parameters.
378      *
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.
381      *
382      * @param params List of arguments marshalled as QVariants
383      * @returns true if the invocation was successful
384      */
385     virtual bool invoke(const QVariantList& params) const = 0;
386
387 protected:
388     SlotObjectBase(const QObject* context);
389
390 private:
391     const QObject* _context;
392 };
393
394 /**
395  * Specialization of SlotObjectBase for a particular type of slot.
396  *
397  * Callable may be a function wrapper around a member function pointer of type Slot,
398  * or a functor that can be invoked directly.
399  *
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
402  */
403 template<typename Slot, typename Callable>
404 class SignalProxy::SlotObject : public SlotObjectBase
405 {
406 public:
407     /**
408      * Constructs a SlotObject for the given callable, whose lifetime is controlled by context.
409      *
410      * @param context  Context object; if destroyed, the slot object will be destroyed as well by SignalProxy.
411      * @param callable Callable to be invoked
412      */
413     SlotObject(const QObject* context, Callable callable)
414         : SlotObjectBase(context)
415         , _callable(std::move(callable))
416     {}
417
418     // See base class
419     bool invoke(const QVariantList& params) const override
420     {
421         if (QThread::currentThread() != context()->thread()) {
422             qWarning() << "Cannot call slot in different thread!";
423             return false;
424         }
425         return invokeWithArgsList(_callable, params) ? true : false;
426     }
427
428 private:
429     Callable _callable;
430 };
431
432 // ==================================================
433 //  ExtendedMetaObject
434 // ==================================================
435 class SignalProxy::ExtendedMetaObject
436 {
437     class MethodDescriptor
438     {
439     public:
440         MethodDescriptor(const QMetaMethod& method);
441         MethodDescriptor() = default;
442
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; }
448
449     private:
450         QByteArray _methodName;
451         QList<int> _argTypes;
452         int _returnType{-1};
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.
456     };
457
458 public:
459     ExtendedMetaObject(const QMetaObject* meta, bool checkConflicts);
460
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(); }
466
467     inline int methodId(const QByteArray& methodName) { return _methodIds.contains(methodName) ? _methodIds[methodName] : -1; }
468
469     inline int updatedRemotelyId() { return _updatedRemotelyId; }
470
471     inline const QHash<QByteArray, int>& slotMap() { return _methodIds; }
472     const QHash<int, int>& receiveMap();
473
474     const QMetaObject* metaObject() const { return _meta; }
475
476     static QByteArray methodName(const QMetaMethod& method);
477     static QString methodBaseName(const QMetaMethod& method);
478
479 private:
480     const MethodDescriptor& methodDescriptor(int methodId);
481
482     const QMetaObject* _meta;
483     int _updatedRemotelyId;  // id of the updatedRemotely() signal - makes things faster
484
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
488 };