Prettified SignalProxy some more, updated license headers.
[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 private:
55   SignalProxy* proxy;
56   QObject* caller;
57   QMultiHash<int, QByteArray> sigNames;
58 };
59
60 SignalRelay::SignalRelay(SignalProxy* parent, QObject* source)
61   : QObject(parent),
62     proxy(parent),
63     caller(source)
64 {
65   QObject::connect(source, SIGNAL(destroyed()), parent, SLOT(detachSender()));
66 }
67
68 int SignalRelay::qt_metacall(QMetaObject::Call _c, int _id, void **_a) {
69   _id = QObject::qt_metacall(_c, _id, _a);
70   if(_id < 0)
71     return _id;
72   if(_c == QMetaObject::InvokeMetaMethod) {
73     if(sigNames.contains(_id)) {
74       const QList<int> &argTypes = proxy->argTypes(caller, _id);
75       QVariantList params;
76       int n = argTypes.size();
77       for(int i=0; i<n; i++)
78         params.append(QVariant(argTypes[i], _a[i+1]));
79       QMultiHash<int, QByteArray>::const_iterator funcIter = sigNames.constFind(_id);
80       while(funcIter != sigNames.constEnd() && funcIter.key() == _id) {
81         proxy->call(funcIter.value(), params);
82         funcIter++;
83       }
84     }
85     _id -= 1;
86   }
87   return _id;
88 }
89
90 void SignalRelay::attachSignal(int methodId, const QByteArray &func) {
91   // we ride without safetybelts here... all checking for valid method etc pp has to be done by the caller
92   // all connected methodIds are offset by the standard methodCount of QObject
93   if(!sigNames.contains(methodId))
94     QMetaObject::connect(caller, methodId, this, QObject::staticMetaObject.methodCount() + methodId);
95
96   QByteArray fn;
97   if(!func.isEmpty()) {
98     fn = QMetaObject::normalizedSignature(func);
99   } else {
100     fn = QByteArray("2") + caller->metaObject()->method(methodId).signature();
101   }
102   sigNames.insert(methodId, fn);
103 }
104 // ====================
105 // END SIGNALRELAY
106 // ====================
107
108
109 // ====================
110 //  SignalProxy
111 // ====================
112 SignalProxy::SignalProxy(QObject* parent)
113   : QObject(parent),
114     _proxyMode(Client),
115     _maxClients(-1)
116 {
117 }
118
119 SignalProxy::SignalProxy(ProxyMode mode, QObject* parent)
120   : QObject(parent),
121     _proxyMode(mode),
122     _maxClients(-1)
123 {
124 }
125
126 SignalProxy::SignalProxy(ProxyMode mode, QIODevice* device, QObject* parent)
127   : QObject(parent),
128     _proxyMode(mode),
129     _maxClients(-1)
130 {
131   addPeer(device);
132
133
134 SignalProxy::~SignalProxy() {
135   QList<QObject*> senders = _relayHash.keys();
136   foreach(QObject* sender, senders)
137     detachObject(sender);
138 }
139
140 void SignalProxy::setProxyMode(ProxyMode mode) {
141   foreach(QIODevice* peer, _peerByteCount.keys()) {
142     if(peer->isOpen()) {
143       qWarning() << "SignalProxy: Cannot change proxy mode while connected";
144       return;
145     }
146   }
147   _proxyMode = mode;
148 }
149
150
151 SignalProxy::ProxyMode SignalProxy::proxyMode() const {
152   return _proxyMode;
153 }
154
155 bool SignalProxy::maxPeersReached() {
156   if(_peerByteCount.empty())
157     return false;
158   if(proxyMode() != Server)
159     return true;
160   if(_maxClients == -1)
161     return false;
162
163   return (_maxClients <= _peerByteCount.count());
164 }
165
166 bool SignalProxy::addPeer(QIODevice* iodev) {
167   if(!iodev)
168     return false;
169   
170   if(_peerByteCount.contains(iodev))
171     return true;
172
173   if(maxPeersReached()) {
174     qWarning("SignalProxy: max peer count reached");
175     return false;
176   }
177
178   if(!iodev->isOpen())
179     qWarning("SignalProxy::the device you passed is not open!");
180
181   connect(iodev, SIGNAL(disconnected()), this, SLOT(removePeerBySender()));
182   connect(iodev, SIGNAL(readyRead()), this, SLOT(dataAvailable()));
183
184   QAbstractSocket* sock  = qobject_cast<QAbstractSocket*>(iodev);
185   if(sock) {
186     connect(sock, SIGNAL(disconnected()), this, SLOT(removePeerBySender()));
187   }
188
189   _peerByteCount[iodev] = 0;
190
191   if(_peerByteCount.count() == 1)
192     emit connected();
193
194   return true;
195 }
196
197 void SignalProxy::removePeerBySender() {
198   // OK we're brutal here... but since it's a private slot we know what we've got connected to it...
199   QIODevice *ioDev = (QIODevice *)(sender());
200   removePeer(ioDev);
201 }
202
203 void SignalProxy::removePeer(QIODevice* iodev) {
204   if(_peerByteCount.isEmpty()) {
205     qWarning() << "SignalProxy: No peers in use!";
206     return;
207   }
208
209   if(proxyMode() == Server && !iodev) {
210     // disconnect all
211     QList<QIODevice *> peers = _peerByteCount.keys();
212     foreach(QIODevice *peer, peers)
213       removePeer(peer);
214   }
215
216   if(proxyMode() != Server && !iodev)
217     iodev = _peerByteCount.keys().first();
218
219   Q_ASSERT(iodev);
220
221   if(!_peerByteCount.contains(iodev)) {
222     qWarning() << "SignalProxy: unknown QIODevice" << iodev;
223     return;
224   }
225      
226   // take a last gasp
227   while(true) {
228     QVariant var;
229     if(readDataFromDevice(iodev, _peerByteCount[iodev], var))
230       receivePeerSignal(var);
231     else
232       break;
233   }
234   _peerByteCount.remove(iodev);
235
236   disconnect(iodev, 0, this, 0);
237   emit peerRemoved(iodev);
238
239   if(_peerByteCount.isEmpty())
240     emit disconnected();
241 }
242
243 void SignalProxy::setArgTypes(QObject* obj, int methodId) {
244   QList<QByteArray> p = obj->metaObject()->method(methodId).parameterTypes();
245   QList<int> argTypes;
246   int ct = p.count();
247   for(int i=0; i<ct; i++)
248     argTypes.append(QMetaType::type(p.value(i)));
249
250   const QByteArray &className(obj->metaObject()->className());
251   Q_ASSERT(_classInfo.contains(className));
252   Q_ASSERT(!_classInfo[className]->argTypes.contains(methodId));
253   _classInfo[className]->argTypes[methodId] = argTypes;
254 }
255
256 const QList<int> &SignalProxy::argTypes(QObject *obj, int methodId) {
257   const QByteArray &className(obj->metaObject()->className());
258   Q_ASSERT(_classInfo.contains(className));
259   if(!_classInfo[className]->argTypes.contains(methodId))
260     setArgTypes(obj, methodId);
261   return _classInfo[className]->argTypes[methodId];
262 }
263
264 void SignalProxy::setMethodName(QObject *obj, int methodId) {
265   const QByteArray &className(obj->metaObject()->className());
266   QByteArray method = obj->metaObject()->method(methodId).signature();
267   method = method.left(method.indexOf('('));
268
269   Q_ASSERT(_classInfo.contains(className));
270   Q_ASSERT(!_classInfo[className]->methodNames.contains(methodId));
271   _classInfo[className]->methodNames[methodId] = method;
272 }
273
274 const QByteArray &SignalProxy::methodName(QObject *obj, int methodId) {
275   QByteArray className(obj->metaObject()->className());
276   Q_ASSERT(_classInfo.contains(className));
277   if(!_classInfo[className]->methodNames.contains(methodId))
278     setMethodName(obj, methodId);
279   return _classInfo[className]->methodNames[methodId];
280 }
281
282
283 void SignalProxy::createClassInfo(QObject *obj) {
284   QByteArray className(obj->metaObject()->className());
285   if(!_classInfo.contains(className))
286     _classInfo[className] = new ClassInfo();
287 }
288
289 bool SignalProxy::attachSignal(QObject* sender, const char* signal, const QByteArray& sigName) {
290   const QMetaObject* meta = sender->metaObject();
291   QByteArray sig(meta->normalizedSignature(signal).mid(1));
292   int methodId = meta->indexOfMethod(sig.constData());
293   if(methodId == -1 || meta->method(methodId).methodType() != QMetaMethod::Signal) {
294     qWarning() << "SignalProxy::attachSignal(): No such signal" << signal;
295     return false;
296   }
297
298   createClassInfo(sender);
299
300   SignalRelay* relay;
301   if(_relayHash.contains(sender))
302     relay = _relayHash[sender];
303   else
304     relay = _relayHash[sender] = new SignalRelay(this, sender);
305
306   relay->attachSignal(methodId, sigName);
307
308   return true;
309 }
310
311
312 bool SignalProxy::attachSlot(const QByteArray& sigName, QObject* recv, const char* slot) {
313   const QMetaObject* meta = recv->metaObject();
314   int methodId = meta->indexOfMethod(meta->normalizedSignature(slot).mid(1));
315   if(methodId == -1 || meta->method(methodId).methodType() == QMetaMethod::Method) {
316     qWarning() << "SignalProxy::attachSlot(): No such slot" << slot;
317     return false;
318   }
319
320   createClassInfo(recv);
321
322   QByteArray funcName = QMetaObject::normalizedSignature(sigName.constData());
323   _attachedSlots.insert(funcName, qMakePair(recv, methodId));
324
325   QObject::disconnect(recv, SIGNAL(destroyed()), this, SLOT(detachSender()));
326   QObject::connect(recv, SIGNAL(destroyed()), this, SLOT(detachSender()));
327   return true;
328 }
329
330
331 void SignalProxy::detachSender() {
332   // this is a slot so we can bypass the QueuedConnection
333   _detachSignals(sender());
334   _detachSlots(sender());
335 }
336
337 // detachObject/Signals/Slots() can be called as a result of an incoming call
338 // this might destroy our the iterator used for delivery
339 // thus we wrap the actual disconnection by using QueuedConnections
340 void SignalProxy::detachObject(QObject* obj) {
341   detachSignals(obj);
342   detachSlots(obj);
343 }
344
345 void SignalProxy::detachSignals(QObject* sender) {
346   QMetaObject::invokeMethod(this, "_detachSignals",
347                             Qt::QueuedConnection,
348                             Q_ARG(QObject*, sender));
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   QMetaObject::invokeMethod(this, "_detachSlots",
359                             Qt::QueuedConnection,
360                             Q_ARG(QObject*, receiver));
361 }
362
363 void SignalProxy::_detachSlots(QObject* receiver) {
364   SlotHash::iterator slotIter = _attachedSlots.begin();
365   while(slotIter != _attachedSlots.end()) {
366     if(slotIter.value().first == receiver) {
367       slotIter = _attachedSlots.erase(slotIter);
368     } else
369       slotIter++;
370   }
371 }
372
373
374 void SignalProxy::call(const char*  signal, QVariant p1, QVariant p2, QVariant p3, QVariant p4, QVariant p5, QVariant p6, QVariant p7, QVariant p8, QVariant p9) {
375   QByteArray func = QMetaObject::normalizedSignature(signal);
376   QVariantList params;
377   params << p1 << p2 << p3 << p4 << p5 << p6 << p7 << p8 << p9;
378   call(func, params);
379 }
380
381 void SignalProxy::call(const QByteArray &funcName, const QVariantList &params) {
382   QVariantList packedFunc;
383   packedFunc << funcName;
384   packedFunc << params;
385   foreach(QIODevice* dev, _peerByteCount.keys())
386     writeDataToDevice(dev, QVariant(packedFunc));
387 }
388
389 void SignalProxy::receivePeerSignal(const QVariant &packedFunc) {
390   QVariantList params(packedFunc.toList());
391   QByteArray funcName = params.takeFirst().toByteArray();
392   int numParams, methodId;
393   QObject* receiver;
394
395   SlotHash::const_iterator slot = _attachedSlots.constFind(funcName);
396   while(slot != _attachedSlots.constEnd() && slot.key() == funcName) {
397     receiver = (*slot).first;
398     methodId = (*slot).second;
399     numParams = argTypes(receiver, methodId).count();
400     QGenericArgument args[9];
401     for(int i = 0; i < numParams; i++)
402       args[i] = QGenericArgument(params[i].typeName(), params[i].constData());
403     if(!QMetaObject::invokeMethod(receiver, methodName(receiver, methodId),
404                                   args[0], args[1], args[2], args[3], args[4], args[5], args[6], args[7], args[8])) {
405       qWarning("SignalProxy::receivePeerSignal(): invokeMethod for \"%s\" failed ", methodName(receiver, methodId).constData());
406     }
407     slot++;
408   }
409 }
410
411 void SignalProxy::dataAvailable() {
412   QIODevice* ioDev = qobject_cast<QIODevice* >(sender());
413   Q_ASSERT(ioDev);
414   if(!_peerByteCount.contains(ioDev)) {
415     qWarning() << "SignalProxy: Unrecognized client object connected to dataAvailable";
416     return;
417   } else {
418     QVariant var;
419     while(readDataFromDevice(ioDev, _peerByteCount[ioDev], var))
420       receivePeerSignal(var);
421   }
422 }
423
424 void SignalProxy::writeDataToDevice(QIODevice *dev, const QVariant &item) {
425   QAbstractSocket* sock  = qobject_cast<QAbstractSocket*>(dev);
426   if(!dev->isOpen() || (sock && sock->state()!=QAbstractSocket::ConnectedState)) {
427     qWarning("SignalProxy: Can't call on a closed device");
428     return;
429   }
430   QByteArray block;
431   QDataStream out(&block, QIODevice::WriteOnly);
432   out.setVersion(QDataStream::Qt_4_2);
433   out << (quint32)0 << item;
434   out.device()->seek(0);
435   out << (quint32)(block.size() - sizeof(quint32));
436   dev->write(block);
437 }
438
439 bool SignalProxy::readDataFromDevice(QIODevice *dev, quint32 &blockSize, QVariant &item) {
440   QDataStream in(dev);
441   in.setVersion(QDataStream::Qt_4_2);
442
443   if(blockSize == 0) {
444     if(dev->bytesAvailable() < (int)sizeof(quint32)) return false;
445     in >> blockSize;
446   }
447
448   if(dev->bytesAvailable() < blockSize)
449     return false;
450   in >> item;
451   blockSize = 0;
452   return true;
453 }