FIRST QuasselTopia version that actually has a chatwidget that _looks_ like
[quassel.git] / src / common / signalproxy.cpp
1 /***************************************************************************
2  *   Copyright (C) 2005-07 by the Quassel IRC Team                         *
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) any later version.                                   *
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  *   59 Temple Place - Suite 330, Boston, MA  02111-1307, USA.             *
19  ***************************************************************************
20  *   SignalProxy has been inspired by QxtRPCPeer, part of libqxt,          *
21  *   the Qt eXTension Library <http://www.libqxt.org>. We would like to    *
22  *   thank Arvid "aep" Picciani and Adam "ahigerd" Higerd for providing    *
23  *   QxtRPCPeer, valuable input and the genius idea to (ab)use Qt's        *
24  *   Meta Object System for transmitting signals over the network.         *
25  *                                                                         *
26  *   To make contribution back into libqxt possible, redistribution and    *
27  *   modification of this file is additionally allowed under the terms of  *
28  *   the Common Public License, version 1.0, as published by IBM.          *
29  ***************************************************************************/
30
31 #include "signalproxy.h"
32 #include <QObject>
33 #include <QIODevice>
34 #include <QAbstractSocket>
35 #include <QHash>
36 #include <QMultiHash>
37 #include <QList>
38 #include <QSet>
39 #include <QDebug>
40 #include <QMetaMethod>
41
42 class SignalRelay: public QObject {
43
44 /* Q_OBJECT is not necessary or even allowed, because we implement
45    qt_metacall ourselves (and don't use any other features of the meta
46    object system)
47 */
48 public:
49   SignalRelay(SignalProxy* parent, QObject* source);
50   int qt_metacall(QMetaObject::Call _c, int _id, void **_a);
51
52   void attachSignal(int methodId, const QByteArray &func);
53
54   int sigCount() const;
55   
56 private:
57   SignalProxy* proxy;
58   QObject* caller;
59   QMultiHash<int, QByteArray> sigNames;
60 };
61
62 SignalRelay::SignalRelay(SignalProxy* parent, QObject* source)
63   : QObject(parent),
64     proxy(parent),
65     caller(source)
66 {
67   QObject::connect(source, SIGNAL(destroyed()), parent, SLOT(detachSender()));
68 }
69
70 int SignalRelay::qt_metacall(QMetaObject::Call _c, int _id, void **_a) {
71   _id = QObject::qt_metacall(_c, _id, _a);
72   if(_id < 0)
73     return _id;
74   if(_c == QMetaObject::InvokeMetaMethod) {
75     if(sigNames.contains(_id)) {
76       const QList<int> &argTypes = proxy->argTypes(caller, _id);
77       QVariantList params;
78       int n = argTypes.size();
79       for(int i=0; i<n; i++)
80         params.append(QVariant(argTypes[i], _a[i+1]));
81       QMultiHash<int, QByteArray>::const_iterator funcIter = sigNames.constFind(_id);
82       while(funcIter != sigNames.constEnd() && funcIter.key() == _id) {
83         proxy->call(funcIter.value(), params);
84         funcIter++;
85       }
86     }
87     _id -= 1;
88   }
89   return _id;
90 }
91
92 int SignalRelay::sigCount() const {
93   // only for debuging purpose
94   return sigNames.count();
95 }
96
97 void SignalRelay::attachSignal(int methodId, const QByteArray &func) {
98   // we ride without safetybelts here... all checking for valid method etc pp has to be done by the caller
99   // all connected methodIds are offset by the standard methodCount of QObject
100   if(!sigNames.contains(methodId))
101     QMetaObject::connect(caller, methodId, this, QObject::staticMetaObject.methodCount() + methodId);
102
103   QByteArray fn;
104   if(!func.isEmpty()) {
105     fn = QMetaObject::normalizedSignature(func);
106   } else {
107     fn = QByteArray("2") + caller->metaObject()->method(methodId).signature();
108   }
109   sigNames.insert(methodId, fn);
110 }
111 // ====================
112 // END SIGNALRELAY
113 // ====================
114
115
116 // ====================
117 //  SignalProxy
118 // ====================
119 SignalProxy::SignalProxy(QObject* parent)
120   : QObject(parent),
121     _proxyMode(Client)
122 {
123 }
124
125 SignalProxy::SignalProxy(ProxyMode mode, QObject* parent)
126   : QObject(parent),
127     _proxyMode(mode)
128 {
129 }
130
131 SignalProxy::SignalProxy(ProxyMode mode, QIODevice* device, QObject* parent)
132   : QObject(parent),
133     _proxyMode(mode)
134 {
135   addPeer(device);
136
137
138 SignalProxy::~SignalProxy() {
139   QList<QObject*> senders = _relayHash.keys();
140   foreach(QObject* sender, senders)
141     detachObject(sender);
142 }
143
144 void SignalProxy::setProxyMode(ProxyMode mode) {
145   foreach(QIODevice* peer, _peerByteCount.keys()) {
146     if(peer->isOpen()) {
147       qWarning() << "SignalProxy: Cannot change proxy mode while connected";
148       return;
149     }
150   }
151   _proxyMode = mode;
152 }
153
154
155 SignalProxy::ProxyMode SignalProxy::proxyMode() const {
156   return _proxyMode;
157 }
158
159 bool SignalProxy::addPeer(QIODevice* iodev) {
160   if(!iodev)
161     return false;
162   
163   if(_peerByteCount.contains(iodev))
164     return true;
165
166   if(proxyMode() == Client && !_peerByteCount.isEmpty()) {
167     qWarning("SignalProxy: only one peer allowed in client mode!");
168     return false;
169   }
170
171   if(!iodev->isOpen())
172     qWarning("SignalProxy::the device you passed is not open!");
173
174   connect(iodev, SIGNAL(disconnected()), this, SLOT(removePeerBySender()));
175   connect(iodev, SIGNAL(readyRead()), this, SLOT(dataAvailable()));
176
177   QAbstractSocket* sock  = qobject_cast<QAbstractSocket*>(iodev);
178   if(sock) {
179     connect(sock, SIGNAL(disconnected()), this, SLOT(removePeerBySender()));
180   }
181
182   _peerByteCount[iodev] = 0;
183
184   if(_peerByteCount.count() == 1)
185     emit connected();
186
187   return true;
188 }
189
190 void SignalProxy::removePeerBySender() {
191   // OK we're brutal here... but since it's a private slot we know what we've got connected to it...
192   QIODevice *ioDev = (QIODevice *)(sender());
193   removePeer(ioDev);
194 }
195
196 void SignalProxy::removePeer(QIODevice* iodev) {
197   if(_peerByteCount.isEmpty()) {
198     qWarning() << "SignalProxy: No peers in use!";
199     return;
200   }
201
202   if(proxyMode() == Server && !iodev) {
203     // disconnect all
204     QList<QIODevice *> peers = _peerByteCount.keys();
205     foreach(QIODevice *peer, peers)
206       removePeer(peer);
207   }
208
209   if(proxyMode() != Server && !iodev)
210     iodev = _peerByteCount.keys().first();
211
212   Q_ASSERT(iodev);
213
214   if(!_peerByteCount.contains(iodev)) {
215     qWarning() << "SignalProxy: unknown QIODevice" << iodev;
216     return;
217   }
218      
219   // take a last gasp
220   while(true) {
221     QVariant var;
222     if(readDataFromDevice(iodev, _peerByteCount[iodev], var))
223       receivePeerSignal(var);
224     else
225       break;
226   }
227   _peerByteCount.remove(iodev);
228
229   disconnect(iodev, 0, this, 0);
230   emit peerRemoved(iodev);
231
232   if(_peerByteCount.isEmpty())
233     emit disconnected();
234 }
235
236 void SignalProxy::setArgTypes(QObject* obj, int methodId) {
237   QList<QByteArray> p = obj->metaObject()->method(methodId).parameterTypes();
238   QList<int> argTypes;
239   int ct = p.count();
240   for(int i=0; i<ct; i++)
241     argTypes.append(QMetaType::type(p.value(i)));
242
243   const QByteArray &className(obj->metaObject()->className());
244   Q_ASSERT(_classInfo.contains(className));
245   Q_ASSERT(!_classInfo[className]->argTypes.contains(methodId));
246   _classInfo[className]->argTypes[methodId] = argTypes;
247 }
248
249 const QList<int> &SignalProxy::argTypes(QObject *obj, int methodId) {
250   const QByteArray &className(obj->metaObject()->className());
251   Q_ASSERT(_classInfo.contains(className));
252   if(!_classInfo[className]->argTypes.contains(methodId))
253     setArgTypes(obj, methodId);
254   return _classInfo[className]->argTypes[methodId];
255 }
256
257 void SignalProxy::setMethodName(QObject *obj, int methodId) {
258   const QByteArray &className(obj->metaObject()->className());
259   QByteArray method = obj->metaObject()->method(methodId).signature();
260   method = method.left(method.indexOf('('));
261
262   Q_ASSERT(_classInfo.contains(className));
263   Q_ASSERT(!_classInfo[className]->methodNames.contains(methodId));
264   _classInfo[className]->methodNames[methodId] = method;
265 }
266
267 const QByteArray &SignalProxy::methodName(QObject *obj, int methodId) {
268   QByteArray className(obj->metaObject()->className());
269   Q_ASSERT(_classInfo.contains(className));
270   if(!_classInfo[className]->methodNames.contains(methodId))
271     setMethodName(obj, methodId);
272   return _classInfo[className]->methodNames[methodId];
273 }
274
275
276 void SignalProxy::createClassInfo(QObject *obj) {
277   QByteArray className(obj->metaObject()->className());
278   if(!_classInfo.contains(className))
279     _classInfo[className] = new ClassInfo();
280 }
281
282 bool SignalProxy::attachSignal(QObject* sender, const char* signal, const QByteArray& sigName) {
283   const QMetaObject* meta = sender->metaObject();
284   QByteArray sig(meta->normalizedSignature(signal).mid(1));
285   int methodId = meta->indexOfMethod(sig.constData());
286   if(methodId == -1 || meta->method(methodId).methodType() != QMetaMethod::Signal) {
287     qWarning() << "SignalProxy::attachSignal(): No such signal" << signal;
288     return false;
289   }
290
291   createClassInfo(sender);
292
293   SignalRelay* relay;
294   if(_relayHash.contains(sender))
295     relay = _relayHash[sender];
296   else
297     relay = _relayHash[sender] = new SignalRelay(this, sender);
298
299   relay->attachSignal(methodId, sigName);
300
301   return true;
302 }
303
304
305 bool SignalProxy::attachSlot(const QByteArray& sigName, QObject* recv, const char* slot) {
306   const QMetaObject* meta = recv->metaObject();
307   int methodId = meta->indexOfMethod(meta->normalizedSignature(slot).mid(1));
308   if(methodId == -1 || meta->method(methodId).methodType() == QMetaMethod::Method) {
309     qWarning() << "SignalProxy::attachSlot(): No such slot" << slot;
310     return false;
311   }
312
313   createClassInfo(recv);
314
315   QByteArray funcName = QMetaObject::normalizedSignature(sigName.constData());
316   _attachedSlots.insert(funcName, qMakePair(recv, methodId));
317
318   QObject::disconnect(recv, SIGNAL(destroyed()), this, SLOT(detachSender()));
319   QObject::connect(recv, SIGNAL(destroyed()), this, SLOT(detachSender()));
320   return true;
321 }
322
323
324 void SignalProxy::detachSender() {
325   // this is a slot so we can bypass the QueuedConnection
326   // and if someone is forcing direct connection in a multithreaded environment he's just nuts...
327   _detachSignals(sender());
328   _detachSlots(sender());
329 }
330
331 // detachObject/Signals/Slots() can be called as a result of an incoming call
332 // this might destroy our the iterator used for delivery
333 // thus we wrap the actual disconnection by using QueuedConnections
334 void SignalProxy::detachObject(QObject* obj) {
335   detachSignals(obj);
336   detachSlots(obj);
337 }
338
339 void SignalProxy::detachSignals(QObject* sender) {
340   QMetaObject::invokeMethod(this, "_detachSignals",
341                             Qt::QueuedConnection,
342                             Q_ARG(QObject*, sender));
343 }
344
345 void SignalProxy::detachSlots(QObject* receiver) {
346   QMetaObject::invokeMethod(this, "_detachSlots",
347                             Qt::QueuedConnection,
348                             Q_ARG(QObject*, receiver));
349 }
350
351 void SignalProxy::_detachSignals(QObject* sender) {
352   if(!_relayHash.contains(sender))
353     return;
354   _relayHash.take(sender)->deleteLater();
355 }
356
357 void SignalProxy::_detachSlots(QObject* receiver) {
358   SlotHash::iterator slotIter = _attachedSlots.begin();
359   while(slotIter != _attachedSlots.end()) {
360     if(slotIter.value().first == receiver) {
361       slotIter = _attachedSlots.erase(slotIter);
362     } else
363       slotIter++;
364   }
365 }
366
367 void SignalProxy::call(const char*  signal, QVariant p1, QVariant p2, QVariant p3, QVariant p4, QVariant p5, QVariant p6, QVariant p7, QVariant p8, QVariant p9) {
368   QByteArray func = QMetaObject::normalizedSignature(signal);
369   QVariantList params;
370   params << p1 << p2 << p3 << p4 << p5 << p6 << p7 << p8 << p9;
371   call(func, 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   // yet again. it's a private slot. no need for checks.
406   QIODevice* ioDev = qobject_cast<QIODevice* >(sender());
407   QVariant var;
408   while(readDataFromDevice(ioDev, _peerByteCount[ioDev], var))
409     receivePeerSignal(var);
410 }
411
412 void SignalProxy::writeDataToDevice(QIODevice *dev, const QVariant &item) {
413   QAbstractSocket* sock  = qobject_cast<QAbstractSocket*>(dev);
414   if(!dev->isOpen() || (sock && sock->state()!=QAbstractSocket::ConnectedState)) {
415     qWarning("SignalProxy: Can't call on a closed device");
416     return;
417   }
418   QByteArray block;
419   QDataStream out(&block, QIODevice::WriteOnly);
420   out.setVersion(QDataStream::Qt_4_2);
421   out << (quint32)0 << item;
422   out.device()->seek(0);
423   out << (quint32)(block.size() - sizeof(quint32));
424   dev->write(block);
425 }
426
427 bool SignalProxy::readDataFromDevice(QIODevice *dev, quint32 &blockSize, QVariant &item) {
428   QDataStream in(dev);
429   in.setVersion(QDataStream::Qt_4_2);
430
431   if(blockSize == 0) {
432     if(dev->bytesAvailable() < (int)sizeof(quint32)) return false;
433     in >> blockSize;
434   }
435
436   if(dev->bytesAvailable() < blockSize)
437     return false;
438   in >> item;
439   blockSize = 0;
440   return true;
441 }
442
443 void SignalProxy::dumpProxyStats() {
444   QString mode;
445   if(proxyMode() == Server)
446     mode = "Server";
447   else
448     mode = "Client";
449
450   int sigCount = 0;
451   foreach(SignalRelay *relay, _relayHash.values())
452     sigCount += relay->sigCount();
453
454   qDebug() << this;
455   qDebug() << "              Proxy Mode:" << mode;
456   qDebug() << "attached sending Objects:" << _relayHash.count();
457   qDebug() << "       Number of Signals:" << sigCount;
458   qDebug() << "          attached Slots:" << _attachedSlots.count();
459   qDebug() << "number of Classes cached:" << _classInfo.count();
460 }