PageRenderTime 388ms CodeModel.GetById 78ms app.highlight 185ms RepoModel.GetById 105ms app.codeStats 0ms

/src/libtomahawk/network/ControlConnection.cpp

http://github.com/tomahawk-player/tomahawk
C++ | 398 lines | 287 code | 71 blank | 40 comment | 39 complexity | f504f0007230194a8714180ad237f028 MD5 | raw file
  1/* === This file is part of Tomahawk Player - <http://tomahawk-player.org> ===
  2 *
  3 *   Copyright 2010-2011, Christian Muehlhaeuser <muesli@tomahawk-player.org>
  4 *   Copyright 2010-2012, Jeff Mitchell <jeff@tomahawk-player.org>
  5 *   Copyright 2013, Uwe L. Korn <uwelk@xhochy.com>
  6 *
  7 *   Tomahawk is free software: you can redistribute it and/or modify
  8 *   it under the terms of the GNU General Public License as published by
  9 *   the Free Software Foundation, either version 3 of the License, or
 10 *   (at your option) any later version.
 11 *
 12 *   Tomahawk is distributed in the hope that it will be useful,
 13 *   but WITHOUT ANY WARRANTY; without even the implied warranty of
 14 *   MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
 15 *   GNU General Public License for more details.
 16 *
 17 *   You should have received a copy of the GNU General Public License
 18 *   along with Tomahawk. If not, see <http://www.gnu.org/licenses/>.
 19 */
 20
 21#include "ControlConnection_p.h"
 22
 23#include "database/Database.h"
 24#include "database/DatabaseCommand_CollectionStats.h"
 25#include "network/DbSyncConnection.h"
 26#include "network/Msg.h"
 27#include "network/MsgProcessor.h"
 28#include "network/Servent.h"
 29#include "sip/PeerInfo.h"
 30#include "utils/Logger.h"
 31
 32#include "PlaylistEntry.h"
 33#include "StreamConnection.h"
 34#include "SourceList.h"
 35
 36#define TCP_TIMEOUT 600
 37
 38using namespace Tomahawk;
 39
 40
 41ControlConnection::ControlConnection( Servent* parent )
 42    : Connection( parent )
 43    , d_ptr( new ControlConnectionPrivate( this ) )
 44{
 45    qDebug() << "CTOR controlconnection";
 46    setId("ControlConnection()");
 47
 48    // auto delete when connection closes:
 49    connect( this, SIGNAL( finished() ), SLOT( deleteLater() ) );
 50
 51    this->setMsgProcessorModeIn( MsgProcessor::UNCOMPRESS_ALL | MsgProcessor::PARSE_JSON );
 52    this->setMsgProcessorModeOut( MsgProcessor::COMPRESS_IF_LARGE );
 53}
 54
 55
 56ControlConnection::~ControlConnection()
 57{
 58    Q_D( ControlConnection );
 59    tDebug( LOGVERBOSE ) << Q_FUNC_INFO << id() << name();
 60
 61    {
 62        QReadLocker locker( &d->sourceLock );
 63        if ( !d->source.isNull() )
 64        {
 65            d->source->setOffline();
 66        }
 67    }
 68
 69    delete d->pingtimer;
 70    servent()->unregisterControlConnection( this );
 71    if ( d->dbsyncconn )
 72        d->dbsyncconn->deleteLater();
 73    delete d_ptr;
 74}
 75
 76
 77source_ptr
 78ControlConnection::source() const
 79{
 80    Q_D( const ControlConnection );
 81    // We return a copy of the shared pointer, no need for a longer lock
 82    QReadLocker locker( &d->sourceLock );
 83    return d->source;
 84}
 85
 86
 87void
 88ControlConnection::unbindFromSource()
 89{
 90    Q_D( ControlConnection );
 91    QWriteLocker locker( &d->sourceLock );
 92    d->source = Tomahawk::source_ptr();
 93}
 94
 95
 96Connection*
 97ControlConnection::clone()
 98{
 99    ControlConnection* clone = new ControlConnection( servent() );
100    clone->setOnceOnly( onceOnly() );
101    clone->setName( name() );
102    return clone;
103}
104
105
106void
107ControlConnection::setup()
108{
109    Q_D( ControlConnection );
110    qDebug() << Q_FUNC_INFO << id() << name();
111    // We need to manually lock, so that we can release before the end of the function
112    d->sourceLock.lockForWrite();
113
114    if ( !d->source.isNull() )
115    {
116        qDebug() << "This source seems to be online already.";
117        Q_ASSERT( false );
118        d->sourceLock.unlock();
119        return;
120    }
121
122    QString friendlyName = name();
123
124    tDebug() << "Detected name:" << name() << friendlyName;
125
126    // setup source and remote collection for this peer
127    d->source = SourceList::instance()->get( id(), friendlyName, true );
128    QSharedPointer<QMutexLocker> locker = d->source->acquireLock();
129    if ( d->source->setControlConnection( this ) )
130    {
131        // We are the new ControlConnection for this source
132
133        // delay setting up collection/etc until source is synced.
134        // we need it DB synced so it has an ID + exists in DB.
135        connect( d->source.data(), SIGNAL( syncedWithDatabase() ),
136                                    SLOT( registerSource() ), Qt::QueuedConnection );
137
138        d->source->setOnline( true );
139
140        d->pingtimer = new QTimer;
141        d->pingtimer->setInterval( 5000 );
142        connect( d->pingtimer, SIGNAL( timeout() ), SLOT( onPingTimer() ) );
143        d->pingtimer->start();
144        d->pingtimer_mark.start();
145        d->sourceLock.unlock();
146    }
147    else
148    {
149        tLog() << Q_FUNC_INFO << "We are a duplicate secondary connection, so dropping.";
150        // We are not responsible for this source anymore, so do not keep a reference.
151        d->source = Tomahawk::source_ptr();
152        // Unlock before we delete ourselves
153        d->sourceLock.unlock();
154        // There is already another ControlConnection in use, we are useless.
155        deleteLater();
156    }
157}
158
159
160// source was synced to DB, set it up properly:
161void
162ControlConnection::registerSource()
163{
164    Q_D( ControlConnection );
165    QReadLocker sourceLocker( &d->sourceLock );
166    if ( d->source.isNull() )
167    {
168        // Not connected to a source anymore, nothing to do.
169        return;
170    }
171
172    QSharedPointer<QMutexLocker> locker = d->source->acquireLock();
173    // Only continue if we are still the ControlConnection associated with this source.
174    if ( d->source->controlConnection() == this )
175    {
176        tLog( LOGVERBOSE ) << Q_FUNC_INFO << d->source->id();
177        Source* source = (Source*) sender();
178        Q_UNUSED( source )
179        Q_ASSERT( source == d->source.data() );
180
181        d->registered = true;
182        setupDbSyncConnection();
183    }
184}
185
186
187void
188ControlConnection::setupDbSyncConnection( bool ondemand )
189{
190    Q_D( ControlConnection );
191    QReadLocker locker( &d->sourceLock );
192    if ( d->source.isNull() )
193    {
194        // We were unbind from the Source, nothing to do here, just waiting to be deleted.
195        return;
196    }
197
198    qDebug() << Q_FUNC_INFO << ondemand << d->source->id() << d->dbconnkey << d->dbsyncconn << d->registered;
199
200    if ( d->dbsyncconn || !d->registered )
201        return;
202
203    Q_ASSERT( d->source->id() > 0 );
204
205    if ( !d->dbconnkey.isEmpty() )
206    {
207        qDebug() << "Connecting to DBSync offer from peer...";
208        d->dbsyncconn = new DBSyncConnection( servent(), d->source );
209
210        servent()->createParallelConnection( this, d->dbsyncconn, d->dbconnkey );
211        d->dbconnkey.clear();
212    }
213    else if ( !outbound() || ondemand ) // only one end makes the offer
214    {
215        qDebug() << "Offering a DBSync key to peer...";
216        d->dbsyncconn = new DBSyncConnection( servent(), d->source );
217
218        QString key = uuid();
219        servent()->registerOffer( key, d->dbsyncconn );
220        QVariantMap m;
221        m.insert( "method", "dbsync-offer" );
222        m.insert( "key", key );
223        sendMsg( m );
224    }
225
226    if ( d->dbsyncconn )
227    {
228        connect( d->dbsyncconn, SIGNAL( finished() ),
229                 d->dbsyncconn,   SLOT( deleteLater() ) );
230
231        connect( d->dbsyncconn, SIGNAL( destroyed( QObject* ) ),
232                                 SLOT( dbSyncConnFinished( QObject* ) ), Qt::DirectConnection );
233    }
234}
235
236
237void
238ControlConnection::dbSyncConnFinished( QObject* c )
239{
240    Q_D( ControlConnection );
241    qDebug() << Q_FUNC_INFO << "DBSync connection closed (for now)";
242    if ( (DBSyncConnection*)c == d->dbsyncconn )
243    {
244        //qDebug() << "Setting m_dbsyncconn to NULL";
245        d->dbsyncconn = NULL;
246    }
247    else
248        qDebug() << "Old DbSyncConn destroyed?!";
249}
250
251
252DBSyncConnection*
253ControlConnection::dbSyncConnection()
254{
255    Q_D( ControlConnection );
256    if ( !d->dbsyncconn )
257    {
258        setupDbSyncConnection( true );
259//        Q_ASSERT( m_dbsyncconn );
260    }
261
262    return d->dbsyncconn;
263}
264
265
266void
267ControlConnection::handleMsg( msg_ptr msg )
268{
269    Q_D( ControlConnection );
270    if ( msg->is( Msg::PING ) )
271    {
272        // qDebug() << "Received Connection PING, nice." << m_pingtimer_mark.elapsed();
273        d->pingtimer_mark.restart();
274        return;
275    }
276
277    // if small and not compresed, print it out for debug
278    if ( msg->length() < 1024 && !msg->is( Msg::COMPRESSED ) )
279    {
280        qDebug() << id() << "got msg:" << QString::fromLatin1( msg->payload() );
281    }
282
283    // All control connection msgs are JSON
284    if ( !msg->is( Msg::JSON ) )
285    {
286        tLog( LOGVERBOSE ) << Q_FUNC_INFO << "Received message was not in JSON format:" << qPrintable( msg->payload() );
287        Q_ASSERT( msg->is( Msg::JSON ) );
288        markAsFailed();
289        return;
290    }
291
292    QVariantMap m = msg->json().toMap();
293    if ( !m.isEmpty() )
294    {
295        if ( m.value( "conntype" ).toString() == "request-offer" )
296        {
297            QString theirkey = m["key"].toString();
298            QString ourkey   = m["offer"].toString();
299            QString theirdbid = m["controlid"].toString();
300            servent()->reverseOfferRequest( this, theirdbid, ourkey, theirkey );
301        }
302        else if ( m.value( "method" ).toString() == "dbsync-offer" )
303        {
304            d->dbconnkey = m.value( "key" ).toString() ;
305            setupDbSyncConnection();
306        }
307        else if ( m.value( "method" ) == "protovercheckfail" )
308        {
309            qDebug() << "*** Remote peer protocol version mismatch, connection closed";
310            shutdown( true );
311            return;
312        }
313        else
314        {
315            tDebug() << id() << "Unhandled msg:" << QString::fromLatin1( msg->payload() );
316        }
317
318        return;
319    }
320
321    tDebug() << id() << "Invalid msg:" << QString::fromLatin1( msg->payload() );
322}
323
324void
325ControlConnection::authCheckTimeout()
326{
327    if ( isReady() )
328        return;
329
330    Q_D( ControlConnection );
331    Servent::instance()->queueForAclResult( bareName(), d->peerInfos );
332
333    tDebug( LOGVERBOSE ) << "Closing connection, not authed in time.";
334    shutdown();
335}
336
337
338void
339ControlConnection::onPingTimer()
340{
341    Q_D( ControlConnection );
342    if ( d->pingtimer_mark.elapsed() >= TCP_TIMEOUT * 1000 )
343    {
344        QReadLocker locker( &d->sourceLock );
345        qDebug() << "Timeout reached! Shutting down connection to" << d->source->friendlyName();
346        shutdown( true );
347    }
348
349    sendMsg( Msg::factory( QByteArray(), Msg::PING ) );
350}
351
352
353void
354ControlConnection::addPeerInfo( const peerinfo_ptr& peerInfo )
355{
356    Q_D( ControlConnection );
357
358    peerInfo->setControlConnection( this );
359    d->peerInfos.insert( peerInfo );
360}
361
362
363void
364ControlConnection::removePeerInfo( const peerinfo_ptr& peerInfo )
365{
366    Q_D( ControlConnection );
367    peerInfoDebug( peerInfo ) << "Remove peer from control connection:" << name();
368
369    Q_ASSERT( peerInfo->controlConnection() == this );
370//     TODO: find out why this happens
371//     Q_ASSERT( m_peerInfos.contains( peerInfo ) );
372
373    d->peerInfos.remove( peerInfo );
374
375    if ( d->peerInfos.isEmpty() && d->shutdownOnEmptyPeerInfos )
376    {
377        shutdown( true );
378    }
379}
380
381void
382ControlConnection::setShutdownOnEmptyPeerInfos( bool shutdownOnEmptyPeerInfos )
383{
384    Q_D( ControlConnection );
385    d->shutdownOnEmptyPeerInfos = shutdownOnEmptyPeerInfos;
386    if ( d->peerInfos.isEmpty() && d->shutdownOnEmptyPeerInfos )
387    {
388        shutdown( true );
389    }
390}
391
392
393const QSet< peerinfo_ptr >
394ControlConnection::peerInfos() const
395{
396    Q_D( const ControlConnection );
397    return d->peerInfos;
398}