/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. #include "ControlConnection_p.h"
  21. #include "database/Database.h"
  22. #include "database/DatabaseCommand_CollectionStats.h"
  23. #include "network/DbSyncConnection.h"
  24. #include "network/Msg.h"
  25. #include "network/MsgProcessor.h"
  26. #include "network/Servent.h"
  27. #include "sip/PeerInfo.h"
  28. #include "utils/Logger.h"
  29. #include "PlaylistEntry.h"
  30. #include "StreamConnection.h"
  31. #include "SourceList.h"
  32. #define TCP_TIMEOUT 600
  33. using namespace Tomahawk;
  34. ControlConnection::ControlConnection( Servent* parent )
  35. : Connection( parent )
  36. , d_ptr( new ControlConnectionPrivate( this ) )
  37. {
  38. qDebug() << "CTOR controlconnection";
  39. setId("ControlConnection()");
  40. // auto delete when connection closes:
  41. connect( this, SIGNAL( finished() ), SLOT( deleteLater() ) );
  42. this->setMsgProcessorModeIn( MsgProcessor::UNCOMPRESS_ALL | MsgProcessor::PARSE_JSON );
  43. this->setMsgProcessorModeOut( MsgProcessor::COMPRESS_IF_LARGE );
  44. }
  45. ControlConnection::~ControlConnection()
  46. {
  47. Q_D( ControlConnection );
  48. tDebug( LOGVERBOSE ) << Q_FUNC_INFO << id() << name();
  49. {
  50. QReadLocker locker( &d->sourceLock );
  51. if ( !d->source.isNull() )
  52. {
  53. d->source->setOffline();
  54. }
  55. }
  56. delete d->pingtimer;
  57. servent()->unregisterControlConnection( this );
  58. if ( d->dbsyncconn )
  59. d->dbsyncconn->deleteLater();
  60. delete d_ptr;
  61. }
  62. source_ptr
  63. ControlConnection::source() const
  64. {
  65. Q_D( const ControlConnection );
  66. // We return a copy of the shared pointer, no need for a longer lock
  67. QReadLocker locker( &d->sourceLock );
  68. return d->source;
  69. }
  70. void
  71. ControlConnection::unbindFromSource()
  72. {
  73. Q_D( ControlConnection );
  74. QWriteLocker locker( &d->sourceLock );
  75. d->source = Tomahawk::source_ptr();
  76. }
  77. Connection*
  78. ControlConnection::clone()
  79. {
  80. ControlConnection* clone = new ControlConnection( servent() );
  81. clone->setOnceOnly( onceOnly() );
  82. clone->setName( name() );
  83. return clone;
  84. }
  85. void
  86. ControlConnection::setup()
  87. {
  88. Q_D( ControlConnection );
  89. qDebug() << Q_FUNC_INFO << id() << name();
  90. // We need to manually lock, so that we can release before the end of the function
  91. d->sourceLock.lockForWrite();
  92. if ( !d->source.isNull() )
  93. {
  94. qDebug() << "This source seems to be online already.";
  95. Q_ASSERT( false );
  96. d->sourceLock.unlock();
  97. return;
  98. }
  99. QString friendlyName = name();
  100. tDebug() << "Detected name:" << name() << friendlyName;
  101. // setup source and remote collection for this peer
  102. d->source = SourceList::instance()->get( id(), friendlyName, true );
  103. QSharedPointer<QMutexLocker> locker = d->source->acquireLock();
  104. if ( d->source->setControlConnection( this ) )
  105. {
  106. // We are the new ControlConnection for this source
  107. // delay setting up collection/etc until source is synced.
  108. // we need it DB synced so it has an ID + exists in DB.
  109. connect( d->source.data(), SIGNAL( syncedWithDatabase() ),
  110. SLOT( registerSource() ), Qt::QueuedConnection );
  111. d->source->setOnline( true );
  112. d->pingtimer = new QTimer;
  113. d->pingtimer->setInterval( 5000 );
  114. connect( d->pingtimer, SIGNAL( timeout() ), SLOT( onPingTimer() ) );
  115. d->pingtimer->start();
  116. d->pingtimer_mark.start();
  117. d->sourceLock.unlock();
  118. }
  119. else
  120. {
  121. tLog() << Q_FUNC_INFO << "We are a duplicate secondary connection, so dropping.";
  122. // We are not responsible for this source anymore, so do not keep a reference.
  123. d->source = Tomahawk::source_ptr();
  124. // Unlock before we delete ourselves
  125. d->sourceLock.unlock();
  126. // There is already another ControlConnection in use, we are useless.
  127. deleteLater();
  128. }
  129. }
  130. // source was synced to DB, set it up properly:
  131. void
  132. ControlConnection::registerSource()
  133. {
  134. Q_D( ControlConnection );
  135. QReadLocker sourceLocker( &d->sourceLock );
  136. if ( d->source.isNull() )
  137. {
  138. // Not connected to a source anymore, nothing to do.
  139. return;
  140. }
  141. QSharedPointer<QMutexLocker> locker = d->source->acquireLock();
  142. // Only continue if we are still the ControlConnection associated with this source.
  143. if ( d->source->controlConnection() == this )
  144. {
  145. tLog( LOGVERBOSE ) << Q_FUNC_INFO << d->source->id();
  146. Source* source = (Source*) sender();
  147. Q_UNUSED( source )
  148. Q_ASSERT( source == d->source.data() );
  149. d->registered = true;
  150. setupDbSyncConnection();
  151. }
  152. }
  153. void
  154. ControlConnection::setupDbSyncConnection( bool ondemand )
  155. {
  156. Q_D( ControlConnection );
  157. QReadLocker locker( &d->sourceLock );
  158. if ( d->source.isNull() )
  159. {
  160. // We were unbind from the Source, nothing to do here, just waiting to be deleted.
  161. return;
  162. }
  163. qDebug() << Q_FUNC_INFO << ondemand << d->source->id() << d->dbconnkey << d->dbsyncconn << d->registered;
  164. if ( d->dbsyncconn || !d->registered )
  165. return;
  166. Q_ASSERT( d->source->id() > 0 );
  167. if ( !d->dbconnkey.isEmpty() )
  168. {
  169. qDebug() << "Connecting to DBSync offer from peer...";
  170. d->dbsyncconn = new DBSyncConnection( servent(), d->source );
  171. servent()->createParallelConnection( this, d->dbsyncconn, d->dbconnkey );
  172. d->dbconnkey.clear();
  173. }
  174. else if ( !outbound() || ondemand ) // only one end makes the offer
  175. {
  176. qDebug() << "Offering a DBSync key to peer...";
  177. d->dbsyncconn = new DBSyncConnection( servent(), d->source );
  178. QString key = uuid();
  179. servent()->registerOffer( key, d->dbsyncconn );
  180. QVariantMap m;
  181. m.insert( "method", "dbsync-offer" );
  182. m.insert( "key", key );
  183. sendMsg( m );
  184. }
  185. if ( d->dbsyncconn )
  186. {
  187. connect( d->dbsyncconn, SIGNAL( finished() ),
  188. d->dbsyncconn, SLOT( deleteLater() ) );
  189. connect( d->dbsyncconn, SIGNAL( destroyed( QObject* ) ),
  190. SLOT( dbSyncConnFinished( QObject* ) ), Qt::DirectConnection );
  191. }
  192. }
  193. void
  194. ControlConnection::dbSyncConnFinished( QObject* c )
  195. {
  196. Q_D( ControlConnection );
  197. qDebug() << Q_FUNC_INFO << "DBSync connection closed (for now)";
  198. if ( (DBSyncConnection*)c == d->dbsyncconn )
  199. {
  200. //qDebug() << "Setting m_dbsyncconn to NULL";
  201. d->dbsyncconn = NULL;
  202. }
  203. else
  204. qDebug() << "Old DbSyncConn destroyed?!";
  205. }
  206. DBSyncConnection*
  207. ControlConnection::dbSyncConnection()
  208. {
  209. Q_D( ControlConnection );
  210. if ( !d->dbsyncconn )
  211. {
  212. setupDbSyncConnection( true );
  213. // Q_ASSERT( m_dbsyncconn );
  214. }
  215. return d->dbsyncconn;
  216. }
  217. void
  218. ControlConnection::handleMsg( msg_ptr msg )
  219. {
  220. Q_D( ControlConnection );
  221. if ( msg->is( Msg::PING ) )
  222. {
  223. // qDebug() << "Received Connection PING, nice." << m_pingtimer_mark.elapsed();
  224. d->pingtimer_mark.restart();
  225. return;
  226. }
  227. // if small and not compresed, print it out for debug
  228. if ( msg->length() < 1024 && !msg->is( Msg::COMPRESSED ) )
  229. {
  230. qDebug() << id() << "got msg:" << QString::fromLatin1( msg->payload() );
  231. }
  232. // All control connection msgs are JSON
  233. if ( !msg->is( Msg::JSON ) )
  234. {
  235. tLog( LOGVERBOSE ) << Q_FUNC_INFO << "Received message was not in JSON format:" << qPrintable( msg->payload() );
  236. Q_ASSERT( msg->is( Msg::JSON ) );
  237. markAsFailed();
  238. return;
  239. }
  240. QVariantMap m = msg->json().toMap();
  241. if ( !m.isEmpty() )
  242. {
  243. if ( m.value( "conntype" ).toString() == "request-offer" )
  244. {
  245. QString theirkey = m["key"].toString();
  246. QString ourkey = m["offer"].toString();
  247. QString theirdbid = m["controlid"].toString();
  248. servent()->reverseOfferRequest( this, theirdbid, ourkey, theirkey );
  249. }
  250. else if ( m.value( "method" ).toString() == "dbsync-offer" )
  251. {
  252. d->dbconnkey = m.value( "key" ).toString() ;
  253. setupDbSyncConnection();
  254. }
  255. else if ( m.value( "method" ) == "protovercheckfail" )
  256. {
  257. qDebug() << "*** Remote peer protocol version mismatch, connection closed";
  258. shutdown( true );
  259. return;
  260. }
  261. else
  262. {
  263. tDebug() << id() << "Unhandled msg:" << QString::fromLatin1( msg->payload() );
  264. }
  265. return;
  266. }
  267. tDebug() << id() << "Invalid msg:" << QString::fromLatin1( msg->payload() );
  268. }
  269. void
  270. ControlConnection::authCheckTimeout()
  271. {
  272. if ( isReady() )
  273. return;
  274. Q_D( ControlConnection );
  275. Servent::instance()->queueForAclResult( bareName(), d->peerInfos );
  276. tDebug( LOGVERBOSE ) << "Closing connection, not authed in time.";
  277. shutdown();
  278. }
  279. void
  280. ControlConnection::onPingTimer()
  281. {
  282. Q_D( ControlConnection );
  283. if ( d->pingtimer_mark.elapsed() >= TCP_TIMEOUT * 1000 )
  284. {
  285. QReadLocker locker( &d->sourceLock );
  286. qDebug() << "Timeout reached! Shutting down connection to" << d->source->friendlyName();
  287. shutdown( true );
  288. }
  289. sendMsg( Msg::factory( QByteArray(), Msg::PING ) );
  290. }
  291. void
  292. ControlConnection::addPeerInfo( const peerinfo_ptr& peerInfo )
  293. {
  294. Q_D( ControlConnection );
  295. peerInfo->setControlConnection( this );
  296. d->peerInfos.insert( peerInfo );
  297. }
  298. void
  299. ControlConnection::removePeerInfo( const peerinfo_ptr& peerInfo )
  300. {
  301. Q_D( ControlConnection );
  302. peerInfoDebug( peerInfo ) << "Remove peer from control connection:" << name();
  303. Q_ASSERT( peerInfo->controlConnection() == this );
  304. // TODO: find out why this happens
  305. // Q_ASSERT( m_peerInfos.contains( peerInfo ) );
  306. d->peerInfos.remove( peerInfo );
  307. if ( d->peerInfos.isEmpty() && d->shutdownOnEmptyPeerInfos )
  308. {
  309. shutdown( true );
  310. }
  311. }
  312. void
  313. ControlConnection::setShutdownOnEmptyPeerInfos( bool shutdownOnEmptyPeerInfos )
  314. {
  315. Q_D( ControlConnection );
  316. d->shutdownOnEmptyPeerInfos = shutdownOnEmptyPeerInfos;
  317. if ( d->peerInfos.isEmpty() && d->shutdownOnEmptyPeerInfos )
  318. {
  319. shutdown( true );
  320. }
  321. }
  322. const QSet< peerinfo_ptr >
  323. ControlConnection::peerInfos() const
  324. {
  325. Q_D( const ControlConnection );
  326. return d->peerInfos;
  327. }