- New SignalProxy which is kinda sorta based on the idea of the QxtRPCPeer, though...
[quassel.git] / src / common / signalproxy.cpp
1 /****************************************************************************
2  **
3  ** Copyright (C) Qxt Foundation. Some rights reserved.
4  **
5  ** This file is part of the QxtNetwork module of the Qt eXTension library
6  **
7  ** This library is free software; you can redistribute it and/or modify it
8  ** under the terms of th Common Public License, version 1.0, as published by
9  ** IBM.
10  **
11  ** This file is provided "AS IS", without WARRANTIES OR CONDITIONS OF ANY
12  ** KIND, EITHER EXPRESS OR IMPLIED INCLUDING, WITHOUT LIMITATION, ANY
13  ** WARRANTIES OR CONDITIONS OF TITLE, NON-INFRINGEMENT, MERCHANTABILITY OR
14  ** FITNESS FOR A PARTICULAR PURPOSE.
15  **
16  ** You should have received a copy of the CPL along with this file.
17  ** See the LICENSE file and the cpl1.0.txt file included with the source
18  ** distribution for more information. If you did not receive a copy of the
19  ** license, contact the Qxt Foundation.
20  **
21  ** <http://libqxt.sourceforge.net>  <foundation@libqxt.org>
22  **
23  ****************************************************************************/
24
25 #include "signalproxy.h"
26 #include <QObject>
27 #include <QIODevice>
28 #include <QAbstractSocket>
29 #include <QHash>
30 #include <QMultiHash>
31 #include <QList>
32 #include <QSet>
33 #include <QDebug>
34 #include <QMetaMethod>
35
36 class ClassIntrospector: public QObject {
37 // This class MANUALLY implements the necessary parts of QObject.
38 // Do NOT add the Q_OBJECT macro. As this class isn't intended
39 // for direct use, it doesn't offer any sort of useful meta-object.
40 public:
41   ClassIntrospector(SignalProxy* parent, QObject* source);
42   int qt_metacall(QMetaObject::Call _c, int _id, void **_a);
43
44   void attachSignal(int methodId, const QByteArray &func);
45   
46 private:
47   SignalProxy* proxy;
48   QObject* caller;
49   QMultiHash<int, QByteArray> rpcFunction;
50 };
51
52 ClassIntrospector::ClassIntrospector(SignalProxy* parent, QObject* source)
53   : QObject(parent),
54     proxy(parent),
55     caller(source)
56 {
57   QObject::connect(source, SIGNAL(destroyed()), parent, SLOT(detachSender()));
58 }
59
60 int ClassIntrospector::qt_metacall(QMetaObject::Call _c, int _id, void **_a) {
61   _id = QObject::qt_metacall(_c, _id, _a);
62   if(_id < 0)
63     return _id;
64   if(_c == QMetaObject::InvokeMetaMethod) {
65     if(rpcFunction.contains(_id)) {
66       const QList<int> &argTypes = proxy->argTypes(caller, _id);
67       QVariantList params;
68       int n = argTypes.size();
69       for(int i=0; i<n; i++)
70         params.append(QVariant(argTypes[i], _a[i+1]));
71       QMultiHash<int, QByteArray>::const_iterator funcIter = rpcFunction.constFind(_id);
72       while(funcIter != rpcFunction.constEnd() && funcIter.key() == _id) {
73         proxy->call(funcIter.value(), params);
74         funcIter++;
75       }
76     }
77     _id -= 1;
78   }
79   return _id;
80 }
81
82 void ClassIntrospector::attachSignal(int methodId, const QByteArray &func) {
83   // we ride without safetybelts here... all checking for valid method etc pp has to be done by the caller
84   // all connected methodIds are offset by the standard methodCount of QObject
85   if(!rpcFunction.contains(methodId))
86     QMetaObject::connect(caller, methodId, this, QObject::staticMetaObject.methodCount() + methodId);
87
88   QByteArray fn;
89   if(!func.isEmpty()) {
90     fn = QMetaObject::normalizedSignature(func);
91   } else {
92     fn = QByteArray("2") + caller->metaObject()->method(methodId).signature();
93   }
94   rpcFunction.insert(methodId, fn);
95 }
96 // ====================
97 // END INTROSPECTOR
98 // ====================
99
100
101 // ====================
102 //  SignalProxy
103 // ====================
104 SignalProxy::SignalProxy(QObject* parent)
105   : QObject(parent),
106     _rpcType(Peer),
107     _maxClients(-1)
108 {
109 }
110
111 SignalProxy::SignalProxy(RPCTypes type, QObject* parent)
112   : QObject(parent),
113     _rpcType(type),
114     _maxClients(-1)
115 {
116 }
117
118 SignalProxy::SignalProxy(RPCTypes type, QIODevice* device, QObject* parent)
119   : QObject(parent),
120     _rpcType(type),
121     _maxClients(-1)
122 {
123   addPeer(device);
124 }  
125
126 SignalProxy::~SignalProxy() {
127   QList<QObject*> senders = _specHash.keys();
128   foreach(QObject* sender, senders)
129     detachObject(sender);
130 }
131
132 void SignalProxy::setRPCType(RPCTypes type) {
133   foreach(QIODevice* peer, _peerByteCount.keys()) {
134     if(peer->isOpen()) {
135       qWarning() << "SignalProxy: Cannot change RPC types while connected";
136       return;
137     }
138   }
139   _rpcType = type;
140 }
141
142
143 SignalProxy::RPCTypes SignalProxy::rpcType() const {
144   return (RPCTypes)(_rpcType);
145 }
146
147 bool SignalProxy::maxPeersReached() {
148   if(_peerByteCount.empty())
149     return false;
150   if(rpcType() != Server)
151     return true;
152   if(_maxClients == -1)
153     return false;
154
155   return (_maxClients <= _peerByteCount.count());
156 }
157
158 bool SignalProxy::addPeer(QIODevice* iodev) {
159   if(!iodev)
160     return false;
161   
162   if(_peerByteCount.contains(iodev))
163     return true;
164
165   if(maxPeersReached()) {
166     qWarning("SignalProxy: max peercount reached");
167     return false;
168   }
169      
170   if(!iodev->isOpen())
171     qWarning("SignalProxy::the device you passed is not open!");
172   
173   connect(iodev, SIGNAL(disconnected()), this, SLOT(removePeerBySender()));
174   connect(iodev, SIGNAL(readyRead()), this, SLOT(dataAvailable()));
175   
176   QAbstractSocket* sock  = qobject_cast<QAbstractSocket*>(iodev);
177   if(sock) {
178     connect(sock, SIGNAL(disconnected()), this, SLOT(removePeerBySender()));
179   }
180
181   _peerByteCount[iodev] = 0;
182
183   if(_peerByteCount.count() == 1)
184     emit connected();
185   
186   return true;
187 }
188
189 void SignalProxy::removePeerBySender() {
190   // OK we're brutal here... but since it's a private slot we know what we've got connected to it...
191   QIODevice *ioDev = (QIODevice *)(sender());
192   removePeer(ioDev);
193 }
194
195 void SignalProxy::removePeer(QIODevice* iodev) {
196   if(_peerByteCount.isEmpty()) {
197     qWarning() << "No Peers in use!";
198     return;
199   }
200      
201   if(_rpcType == Server && !iodev) {
202     // disconnect all
203     QList<QIODevice *> peers = _peerByteCount.keys();
204     foreach(QIODevice *peer, peers)
205       removePeer(peer);
206   }
207
208   if(_rpcType != Server && !iodev)
209     iodev = _peerByteCount.keys().first();
210   
211   Q_ASSERT(iodev);
212      
213   if(!_peerByteCount.contains(iodev)) {
214     qWarning() << "SignalProxy: unknown QIODevice" << iodev;
215     return;
216   }
217      
218   // take a last gasp
219   while(true) {
220     QVariant var;
221     if(readDataFromDevice(iodev, _peerByteCount[iodev], var))
222       receivePeerSignal(var);
223     else
224       break;
225   }
226   _peerByteCount.remove(iodev);
227
228   disconnect(iodev, 0, this, 0);
229   emit peerRemoved(iodev);
230
231   if(_peerByteCount.isEmpty())
232     emit disconnected();
233 }
234
235 void SignalProxy::setArgTypes(QObject* obj, int methodId) {
236   QList<QByteArray> p = obj->metaObject()->method(methodId).parameterTypes();
237   QList<int> argTypes;
238   int ct = p.count();
239   for(int i=0; i<ct; i++)
240     argTypes.append(QMetaType::type(p.value(i)));
241
242   const QByteArray &className(obj->metaObject()->className());
243   Q_ASSERT(_classInfo.contains(className));
244   Q_ASSERT(!_classInfo[className]->argTypes.contains(methodId));
245   _classInfo[className]->argTypes[methodId] = argTypes;
246 }
247
248 const QList<int> &SignalProxy::argTypes(QObject *obj, int methodId) {
249   const QByteArray &className(obj->metaObject()->className());
250   Q_ASSERT(_classInfo.contains(className));
251   if(!_classInfo[className]->argTypes.contains(methodId))
252     setArgTypes(obj, methodId);
253   return _classInfo[className]->argTypes[methodId];
254 }
255
256 void SignalProxy::setMethodName(QObject *obj, int methodId) {
257   const QByteArray &className(obj->metaObject()->className());
258   QByteArray method = obj->metaObject()->method(methodId).signature();
259   method = method.left(method.indexOf('('));
260
261   Q_ASSERT(_classInfo.contains(className));
262   Q_ASSERT(!_classInfo[className]->methodNames.contains(methodId));
263   _classInfo[className]->methodNames[methodId] = method;
264 }
265
266 const QByteArray &SignalProxy::methodName(QObject *obj, int methodId) {
267   QByteArray className(obj->metaObject()->className());
268   Q_ASSERT(_classInfo.contains(className));
269   if(!_classInfo[className]->methodNames.contains(methodId))
270     setMethodName(obj, methodId);
271   return _classInfo[className]->methodNames[methodId];
272 }
273
274
275 void SignalProxy::createClassInfo(QObject *obj) {
276   QByteArray className(obj->metaObject()->className());
277   if(!_classInfo.contains(className))
278     _classInfo[className] = new ClassInfo();
279 }
280
281 bool SignalProxy::attachSignal(QObject* sender, const char* signal, const QByteArray& rpcFunction) {
282   const QMetaObject* meta = sender->metaObject();
283   QByteArray sig(meta->normalizedSignature(signal).mid(1));
284   int methodId = meta->indexOfMethod(sig.constData());
285   if(methodId == -1 || meta->method(methodId).methodType() != QMetaMethod::Signal) {
286     qWarning() << "SignalProxy::attachSignal: No such signal " << signal;
287     return false;
288   }
289
290   createClassInfo(sender);
291
292   ClassIntrospector* spec;
293   if(_specHash.contains(sender))
294     spec = _specHash[sender];
295   else
296     spec = _specHash[sender] = new ClassIntrospector(this, sender);
297
298   spec->attachSignal(methodId, rpcFunction);
299
300   return true;
301 }
302
303
304 bool SignalProxy::attachSlot(const QByteArray& rpcFunction, QObject* recv, const char* slot) {
305   const QMetaObject* meta = recv->metaObject();
306   int methodId = meta->indexOfMethod(meta->normalizedSignature(slot).mid(1));
307   if(methodId == -1 || meta->method(methodId).methodType() == QMetaMethod::Method) {
308     qWarning() << "SignalProxy::attachSlot: No such slot " << slot;
309     return false;
310   }
311
312   createClassInfo(recv);
313   
314   QByteArray funcName = QMetaObject::normalizedSignature(rpcFunction.constData());
315   _attachedSlots.insert(funcName, qMakePair(recv, methodId));
316
317   QObject::disconnect(recv, SIGNAL(destroyed()), this, SLOT(detachSender()));
318   QObject::connect(recv, SIGNAL(destroyed()), this, SLOT(detachSender()));
319   return true;
320 }
321
322
323 void SignalProxy::detachSender() {
324   // this is a slot so we can bypass the QueuedConnection
325   _detachSignals(sender());
326   _detachSlots(sender());
327 }
328
329 // detachObject/Signals/Slots() can be called as a result of an incomming call
330 // this might destroy our the iterator used for delivery
331 // thus we wrap the actual disconnection by using QueuedConnections
332 void SignalProxy::detachObject(QObject* obj) {
333   detachSignals(obj);
334   detachSlots(obj);
335 }
336
337 void SignalProxy::detachSignals(QObject* sender) {
338   QMetaObject::invokeMethod(this, "_detachSignals",
339                             Qt::QueuedConnection,
340                             Q_ARG(QObject*, sender));
341 }
342
343 void SignalProxy::_detachSignals(QObject* sender) {
344   if(!_specHash.contains(sender))
345     return;
346   _specHash.take(sender)->deleteLater();
347 }
348
349 void SignalProxy::detachSlots(QObject* receiver) {
350   QMetaObject::invokeMethod(this, "_detachSlots",
351                             Qt::QueuedConnection,
352                             Q_ARG(QObject*, receiver));
353 }
354
355 void SignalProxy::_detachSlots(QObject* receiver) {
356   SlotHash::iterator slotIter = _attachedSlots.begin();
357   while(slotIter != _attachedSlots.end()) {
358     if(slotIter.value().first == receiver) {
359       slotIter = _attachedSlots.erase(slotIter);
360     } else
361       slotIter++;
362   }
363 }
364
365
366 void SignalProxy::call(const char*  signal , QVariant p1, QVariant p2, QVariant p3, QVariant p4, QVariant p5, QVariant p6, QVariant p7, QVariant p8, QVariant p9) {
367   QByteArray sig=QMetaObject::normalizedSignature(signal);
368   QVariantList params;
369   params << p1 << p2 << p3 << p4 << p5
370          << p6 << p7 << p8 << p9;
371   call(sig, params);
372 }
373
374 void SignalProxy::call(const QByteArray &funcName, const QVariantList &params) {
375   QVariantList packedFunc;
376   packedFunc << funcName;
377   packedFunc << params;
378   foreach(QIODevice* dev, _peerByteCount.keys())
379     writeDataToDevice(dev, QVariant(packedFunc));
380 }
381
382 void SignalProxy::receivePeerSignal(const QVariant &packedFunc) {
383   QVariantList params(packedFunc.toList());
384   QByteArray funcName = params.takeFirst().toByteArray();
385   int numParams, methodId;
386   QObject* receiver;
387
388   SlotHash::const_iterator slot = _attachedSlots.constFind(funcName);
389   while(slot != _attachedSlots.constEnd() && slot.key() == funcName) {
390     receiver = (*slot).first;
391     methodId = (*slot).second;
392     numParams = argTypes(receiver, methodId).count();
393     QGenericArgument args[9];
394     for(int i = 0; i < numParams; i++)
395       args[i] = QGenericArgument(params[i].typeName(), params[i].constData());
396     if(!QMetaObject::invokeMethod(receiver, methodName(receiver, methodId),
397                                   args[0], args[1], args[2], args[3], args[4], args[5], args[6], args[7], args[8])) {
398       qWarning("SignalProxy::receivePeerSignal: invokeMethod for \"%s\" failed ", methodName(receiver, methodId).constData());
399     }
400     slot++;
401   }
402 }
403
404 void SignalProxy::dataAvailable() {
405   QIODevice* ioDev = qobject_cast<QIODevice* >(sender());
406   Q_ASSERT(ioDev);
407   if(!_peerByteCount.contains(ioDev)) {
408     qWarning() << "SignalProxy: Unrecognized client object connected to dataAvailable";
409     return;
410   } else {
411     QVariant var;
412     while(readDataFromDevice(ioDev, _peerByteCount[ioDev], var))
413       receivePeerSignal(var);
414   }
415 }
416
417 void SignalProxy::writeDataToDevice(QIODevice *dev, const QVariant &item) {
418   QAbstractSocket* sock  = qobject_cast<QAbstractSocket*>(dev);
419   if(!dev->isOpen() || (sock && sock->state()!=QAbstractSocket::ConnectedState)) {
420     qWarning("can't call on a closed device");
421     return;
422   }
423   QByteArray block;
424   QDataStream out(&block, QIODevice::WriteOnly);
425   out.setVersion(QDataStream::Qt_4_2);
426   out << (quint32)0 << item;
427   out.device()->seek(0);
428   out << (quint32)(block.size() - sizeof(quint32));
429   dev->write(block);
430 }
431
432 bool SignalProxy::readDataFromDevice(QIODevice *dev, quint32 &blockSize, QVariant &item) {
433   QDataStream in(dev);
434   in.setVersion(QDataStream::Qt_4_2);
435
436   if(blockSize == 0) {
437     if(dev->bytesAvailable() < (int)sizeof(quint32)) return false;
438     in >> blockSize;
439   }
440
441   if(dev->bytesAvailable() < blockSize)
442     return false;
443   in >> item;
444   blockSize = 0;
445   return true;
446 }
447
448