/src/libtomahawk/network/ControlConnection.cpp
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}