/src/libtomahawk/network/StreamConnection.cpp

http://github.com/tomahawk-player/tomahawk · C++ · 310 lines · 209 code · 64 blank · 37 comment · 27 complexity · a0e0537336491a13b738b7829fd1a342 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-2011, Jeff Mitchell <jeff@tomahawk-player.org>
  5. * Copyright 2013, Teo Mrnjavac <teo@kde.org>
  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 "StreamConnection.h"
  21. #include "database/DatabaseCommand_LoadFiles.h"
  22. #include "database/Database.h"
  23. #include "network/ControlConnection.h"
  24. #include "network/Servent.h"
  25. #include "utils/Logger.h"
  26. #include "BufferIoDevice.h"
  27. #include "Msg.h"
  28. #include "MsgProcessor.h"
  29. #include "Result.h"
  30. #include "SourceList.h"
  31. #include "UrlHandler.h"
  32. #include <QFile>
  33. #include <QTimer>
  34. using namespace Tomahawk;
  35. StreamConnection::StreamConnection( Servent* s, ControlConnection* cc, QString fid, const Tomahawk::result_ptr& result )
  36. : Connection( s )
  37. , m_cc( cc )
  38. , m_fid( fid )
  39. , m_type( RECEIVING )
  40. , m_curBlock( 0 )
  41. , m_badded( 0 )
  42. , m_bsent( 0 )
  43. , m_allok( false )
  44. , m_result( result )
  45. , m_transferRate( 0 )
  46. {
  47. qDebug() << Q_FUNC_INFO;
  48. BufferIODevice* bio = new BufferIODevice( result->size() );
  49. m_iodev = QSharedPointer<QIODevice>( bio, &QObject::deleteLater ); // device audio data gets written to
  50. m_iodev->open( QIODevice::ReadWrite );
  51. Servent::instance()->registerStreamConnection( this );
  52. // if the audioengine closes the iodev (skip/stop/etc) then kill the connection
  53. // immediately to avoid unnecessary network transfer
  54. connect( m_iodev.data(), SIGNAL( aboutToClose() ), SLOT( shutdown() ), Qt::QueuedConnection );
  55. connect( m_iodev.data(), SIGNAL( blockRequest( int ) ), SLOT( onBlockRequest( int ) ) );
  56. // auto delete when connection closes:
  57. connect( this, SIGNAL( finished() ), SLOT( deleteLater() ), Qt::QueuedConnection );
  58. // don't fuck with our messages at all. No compression, no parsing, nothing:
  59. this->setMsgProcessorModeIn ( MsgProcessor::NOTHING );
  60. this->setMsgProcessorModeOut( MsgProcessor::NOTHING );
  61. }
  62. StreamConnection::StreamConnection( Servent* s, ControlConnection* cc, QString fid )
  63. : Connection( s )
  64. , m_cc( cc )
  65. , m_fid( fid )
  66. , m_type( SENDING )
  67. , m_badded( 0 )
  68. , m_bsent( 0 )
  69. , m_allok( false )
  70. , m_transferRate( 0 )
  71. {
  72. Servent::instance()->registerStreamConnection( this );
  73. // auto delete when connection closes:
  74. connect( this, SIGNAL( finished() ), SLOT( deleteLater() ), Qt::QueuedConnection );
  75. }
  76. StreamConnection::~StreamConnection()
  77. {
  78. qDebug() << Q_FUNC_INFO << "TX/RX:" << bytesSent() << bytesReceived();
  79. if ( m_type == RECEIVING && !m_allok )
  80. {
  81. qDebug() << "FTConnection closing before last data msg received, shame.";
  82. //TODO log the fact that our peer was bad-mannered enough to not finish the upload
  83. // protected, we could expose it:
  84. //m_iodev->setErrorString("FTConnection providing data went away mid-transfer");
  85. if ( !m_iodev.isNull() )
  86. ((BufferIODevice*)m_iodev.data())->inputComplete();
  87. }
  88. Servent::instance()->onStreamFinished( this );
  89. }
  90. QString
  91. StreamConnection::id() const
  92. {
  93. return QString( "FTC[%1 %2]" )
  94. .arg( m_type == SENDING ? "TX" : "RX" )
  95. .arg( m_fid );
  96. }
  97. Tomahawk::source_ptr
  98. StreamConnection::source() const
  99. {
  100. return m_source;
  101. }
  102. void
  103. StreamConnection::showStats( qint64 tx, qint64 rx )
  104. {
  105. if ( tx > 0 || rx > 0 )
  106. {
  107. qDebug() << id()
  108. << QString( "Down: %L1 bytes/sec," ).arg( rx )
  109. << QString( "Up: %L1 bytes/sec" ).arg( tx );
  110. }
  111. m_transferRate = tx + rx;
  112. emit updated();
  113. }
  114. void
  115. StreamConnection::setup()
  116. {
  117. QList<source_ptr> sources = SourceList::instance()->sources();
  118. foreach ( const source_ptr& src, sources )
  119. {
  120. // local src doesnt have a control connection, skip it:
  121. if ( src.isNull() || src->isLocal() )
  122. continue;
  123. if ( src->controlConnection() == m_cc )
  124. {
  125. m_source = src;
  126. break;
  127. }
  128. }
  129. connect( this, SIGNAL( statsTick( qint64, qint64 ) ), SLOT( showStats( qint64, qint64 ) ) );
  130. if ( m_type == RECEIVING )
  131. {
  132. qDebug() << "in RX mode";
  133. emit updated();
  134. return;
  135. }
  136. qDebug() << "in TX mode, fid:" << m_fid;
  137. DatabaseCommand_LoadFiles* cmd = new DatabaseCommand_LoadFiles( m_fid.toUInt() );
  138. connect( cmd, SIGNAL( result( Tomahawk::result_ptr ) ), SLOT( startSending( Tomahawk::result_ptr ) ) );
  139. Database::instance()->enqueue( Tomahawk::dbcmd_ptr( cmd ) );
  140. }
  141. void
  142. StreamConnection::startSending( const Tomahawk::result_ptr& result )
  143. {
  144. if ( result.isNull() )
  145. {
  146. qDebug() << "Can't handle invalid result!";
  147. shutdown();
  148. return;
  149. }
  150. m_result = result;
  151. qDebug() << "Starting to transmit" << m_result->url();
  152. std::function< void ( const QString, QSharedPointer< QIODevice > ) > callback =
  153. std::bind( &StreamConnection::reallyStartSending, this, result,
  154. std::placeholders::_1, std::placeholders::_2 );
  155. Tomahawk::UrlHandler::getIODeviceForUrl( m_result, m_result->url(), callback );
  156. }
  157. void
  158. StreamConnection::reallyStartSending( const Tomahawk::result_ptr result, const QString url, QSharedPointer< QIODevice > io )
  159. {
  160. Q_UNUSED( url );
  161. // Note: We don't really need to pass in 'result' here, since we already have it stored
  162. // as a member variable. The callback-signature of getIODeviceForUrl requires it, though.
  163. if ( !io || io.isNull() )
  164. {
  165. qDebug() << "Couldn't read from source:" << result->url();
  166. shutdown();
  167. return;
  168. }
  169. m_readdev = QSharedPointer<QIODevice>( io );
  170. sendSome();
  171. emit updated();
  172. }
  173. void
  174. StreamConnection::handleMsg( msg_ptr msg )
  175. {
  176. Q_ASSERT( msg->is( Msg::RAW ) );
  177. if ( msg->payload().startsWith( "block" ) )
  178. {
  179. int block = QString( msg->payload() ).mid( 5 ).toInt();
  180. m_readdev->seek( block * BufferIODevice::blockSize() );
  181. qDebug() << "Seeked to block:" << block;
  182. QByteArray sm;
  183. sm.append( QString( "doneblock%1" ).arg( block ) );
  184. sendMsg( Msg::factory( sm, Msg::RAW | Msg::FRAGMENT ) );
  185. QTimer::singleShot( 0, this, SLOT( sendSome() ) );
  186. }
  187. else if ( msg->payload().startsWith( "doneblock" ) )
  188. {
  189. int block = QString( msg->payload() ).mid( 9 ).toInt();
  190. ( (BufferIODevice*)m_iodev.data() )->seeked( block );
  191. m_curBlock = block;
  192. qDebug() << "Next block is now:" << block;
  193. }
  194. else if ( msg->payload().startsWith( "data" ) )
  195. {
  196. m_badded += msg->payload().length() - 4;
  197. ( (BufferIODevice*)m_iodev.data() )->addData( m_curBlock++, msg->payload().mid( 4 ) );
  198. }
  199. //qDebug() << Q_FUNC_INFO << "flags" << (int) msg->flags()
  200. // << "payload len" << msg->payload().length()
  201. // << "written to device so far: " << m_badded;
  202. if ( m_iodev && ( (BufferIODevice*)m_iodev.data() )->nextEmptyBlock() < 0 )
  203. {
  204. m_allok = true;
  205. // tell our iodev there is no more data to read, no args meaning a success:
  206. ( (BufferIODevice*)m_iodev.data() )->inputComplete();
  207. shutdown();
  208. }
  209. }
  210. Connection*
  211. StreamConnection::clone()
  212. {
  213. Q_ASSERT( false );
  214. return 0;
  215. }
  216. void
  217. StreamConnection::sendSome()
  218. {
  219. Q_ASSERT( m_type == StreamConnection::SENDING );
  220. QByteArray ba = "data";
  221. ba.append( m_readdev->read( BufferIODevice::blockSize() ) );
  222. m_bsent += ba.length() - 4;
  223. if ( m_readdev->atEnd() )
  224. {
  225. sendMsg( Msg::factory( ba, Msg::RAW ) );
  226. return;
  227. }
  228. else
  229. {
  230. // more to come -> FRAGMENT
  231. sendMsg( Msg::factory( ba, Msg::RAW | Msg::FRAGMENT ) );
  232. }
  233. // HINT: change the 0 to 50 to transmit at 640Kbps, for example
  234. // (this is where upload throttling could be implemented)
  235. QTimer::singleShot( 0, this, SLOT( sendSome() ) );
  236. }
  237. void
  238. StreamConnection::onBlockRequest( int block )
  239. {
  240. qDebug() << Q_FUNC_INFO << block;
  241. if ( m_curBlock == block )
  242. return;
  243. QByteArray sm;
  244. sm.append( QString( "block%1" ).arg( block ) );
  245. sendMsg( Msg::factory( sm, Msg::RAW | Msg::FRAGMENT ) );
  246. }