PageRenderTime 195ms CodeModel.GetById 81ms app.highlight 48ms RepoModel.GetById 63ms app.codeStats 0ms

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