PageRenderTime 96ms CodeModel.GetById 11ms app.highlight 41ms RepoModel.GetById 40ms app.codeStats 0ms

/src/libtomahawk/network/DbSyncConnection.cpp

http://github.com/tomahawk-player/tomahawk
C++ | 300 lines | 201 code | 60 blank | 39 comment | 29 complexity | 909feb99c9d87889ea5097f132eb57a2 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 *
  6 *   Tomahawk is free software: you can redistribute it and/or modify
  7 *   it under the terms of the GNU General Public License as published by
  8 *   the Free Software Foundation, either version 3 of the License, or
  9 *   (at your option) any later version.
 10 *
 11 *   Tomahawk is distributed in the hope that it will be useful,
 12 *   but WITHOUT ANY WARRANTY; without even the implied warranty of
 13 *   MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
 14 *   GNU General Public License for more details.
 15 *
 16 *   You should have received a copy of the GNU General Public License
 17 *   along with Tomahawk. If not, see <http://www.gnu.org/licenses/>.
 18 */
 19
 20/*
 21    Database syncing using the oplog table.
 22    =======================================
 23    Load the last GUID we applied for the peer, tell them it.
 24    In return, they send us all new ops since that guid.
 25
 26    We then apply those new ops to our cache of their data
 27
 28    Synced.
 29
 30*/
 31
 32#include "DbSyncConnection.h"
 33
 34#include "database/Database.h"
 35#include "database/DatabaseCommand.h"
 36#include "database/DatabaseCommand_CollectionStats.h"
 37#include "database/DatabaseCommand_LoadOps.h"
 38#include "utils/Logger.h"
 39
 40#include "Msg.h"
 41#include "MsgProcessor.h"
 42#include "RemoteCollection.h"
 43#include "Source.h"
 44#include "SourceList.h"
 45
 46using namespace Tomahawk;
 47
 48
 49DBSyncConnection::DBSyncConnection( Servent* s, const source_ptr& src )
 50    : Connection( s )
 51    , m_fetchCount( 0 )
 52    , m_source( src )
 53    , m_state( UNKNOWN )
 54{
 55    qDebug() << Q_FUNC_INFO << src->id() << thread();
 56
 57    // Be aware of namespaces in these signals/slots!
 58    connect( this,            SIGNAL( stateChanged( Tomahawk::DBSyncConnectionState, Tomahawk::DBSyncConnectionState, QString ) ),
 59             m_source.data(),   SLOT( onStateChanged( Tomahawk::DBSyncConnectionState, Tomahawk::DBSyncConnectionState, QString ) ) );
 60    connect( m_source.data(), SIGNAL( commandsFinished() ),
 61             this,              SLOT( lastOpApplied() ) );
 62
 63    this->setMsgProcessorModeIn( MsgProcessor::PARSE_JSON | MsgProcessor::UNCOMPRESS_ALL );
 64
 65    // msgs are stored compressed in the db, so not typically needed here, but doesnt hurt:
 66    this->setMsgProcessorModeOut( MsgProcessor::COMPRESS_IF_LARGE );
 67}
 68
 69
 70DBSyncConnection::~DBSyncConnection()
 71{
 72    tDebug() << "DTOR" << Q_FUNC_INFO << m_source->id() << m_source->friendlyName();
 73    m_state = SHUTDOWN;
 74}
 75
 76
 77void
 78DBSyncConnection::changeState( DBSyncConnectionState newstate )
 79{
 80    if ( m_state == SHUTDOWN )
 81        return;
 82
 83    DBSyncConnectionState s = m_state;
 84    m_state = newstate;
 85    qDebug() << "DBSYNC State changed from" << s << "to" << newstate << "- source:" << m_source->id();
 86    emit stateChanged( newstate, s, "" );
 87}
 88
 89
 90void
 91DBSyncConnection::setup()
 92{
 93    setId( QString( "DBSyncConnection/%1" ).arg( socket()->peerAddress().toString() ) );
 94    check();
 95}
 96
 97
 98void
 99DBSyncConnection::trigger()
100{
101    // if we're still setting up the connection, do nothing - we sync on first connect anyway:
102    if ( !isRunning() )
103        return;
104
105    QMetaObject::invokeMethod( this, "sendMsg", Qt::QueuedConnection,
106                               Q_ARG( msg_ptr, Msg::factory( "{\"method\":\"trigger\"}", Msg::JSON ) ) );
107}
108
109
110void
111DBSyncConnection::check()
112{
113    qDebug() << Q_FUNC_INFO << this << m_source->id();
114
115    if ( m_state == SHUTDOWN )
116    {
117        qDebug() << "Aborting sync due to shutdown.";
118        return;
119    }
120    if ( m_state != UNKNOWN && m_state != SYNCED )
121    {
122        qDebug() << "Syncing in progress already.";
123        return;
124    }
125
126    m_uscache.clear();
127    changeState( CHECKING );
128
129    if ( m_source->lastCmdGuid().isEmpty() )
130    {
131        tDebug( LOGVERBOSE ) << "Fetching lastCmdGuid from database!";
132        DatabaseCommand_CollectionStats* cmd_them = new DatabaseCommand_CollectionStats( m_source );
133        connect( cmd_them, SIGNAL( done( QVariantMap ) ), SLOT( gotThem( QVariantMap ) ) );
134        Database::instance()->enqueue( dbcmd_ptr(cmd_them) );
135    }
136    else
137    {
138        fetchOpsData( m_source->lastCmdGuid() );
139    }
140}
141
142
143/// Called once we've loaded our cached data about their collection
144void
145DBSyncConnection::gotThem( const QVariantMap& m )
146{
147    fetchOpsData( m.value( "lastop" ).toString() );
148}
149
150
151void
152DBSyncConnection::fetchOpsData( const QString& sinceguid )
153{
154    changeState( FETCHING );
155
156    tLog() << "Sending a FETCHOPS cmd since:" << sinceguid << "- source:" << m_source->id();
157
158    QVariantMap msg;
159    msg.insert( "method", "fetchops" );
160    msg.insert( "lastop", sinceguid );
161    sendMsg( msg );
162}
163
164
165void
166DBSyncConnection::handleMsg( msg_ptr msg )
167{
168    Q_ASSERT( !msg->is( Msg::COMPRESSED ) );
169
170    if ( m_state == FETCHING )
171        changeState( PARSING );
172
173    // "everything is synced" indicated by non-json msg containing "ok":
174    if ( !msg->is( Msg::JSON ) &&
175         msg->is( Msg::DBOP ) &&
176         msg->payload() == "ok" )
177    {
178        changeState( SYNCED );
179
180        // calc the collection stats, to updates the "X tracks" in the sidebar etc
181        // this is done automatically if you run a dbcmd to add files.
182        DatabaseCommand_CollectionStats* cmd = new DatabaseCommand_CollectionStats( m_source );
183        connect( cmd,           SIGNAL( done( const QVariantMap & ) ),
184                 m_source.data(), SLOT( setStats( const QVariantMap& ) ), Qt::QueuedConnection );
185        Database::instance()->enqueue( Tomahawk::dbcmd_ptr(cmd) );
186        return;
187    }
188
189    Q_ASSERT( msg->is( Msg::JSON ) );
190
191    QVariantMap m = msg->json().toMap();
192    if ( m.empty() )
193    {
194        tLog() << "Failed to parse msg in dbsync from:" << m_source->id() << m_source->friendlyName() << msg->payload();
195        Q_ASSERT( false );
196        return;
197    }
198
199    // a db sync op msg
200    if ( msg->is( Msg::DBOP ) )
201    {
202        dbcmd_ptr cmd = Database::instance()->createCommandInstance( m, m_source );
203        if ( !cmd.isNull() )
204        {
205            m_source->addCommand( cmd );
206        }
207
208        if ( !msg->is( Msg::FRAGMENT ) ) // last msg in this batch
209        {
210            changeState( SAVING ); // just DB work left to complete
211            m_source->executeCommands();
212        }
213        return;
214    }
215
216    if ( m.value( "method" ).toString() == "fetchops" )
217    {
218        ++m_fetchCount;
219        tDebug( LOGVERBOSE ) << "Fetching new dbops:" << m["lastop"].toString() << m_fetchCount;
220        m_uscache = m;
221        sendOps();
222        return;
223    }
224
225    if ( m.value( "method" ).toString() == "trigger" )
226    {
227        tLog( LOGVERBOSE ) << "Got trigger msg on dbsyncconnection, checking for new stuff.";
228        check();
229        return;
230    }
231
232    tLog() << Q_FUNC_INFO << "Unhandled msg:" << msg->payload();
233    Q_ASSERT( false );
234}
235
236
237void
238DBSyncConnection::lastOpApplied()
239{
240    changeState( SYNCED );
241    // check again, until peer responds we have no new ops to process
242    check();
243}
244
245
246/// request new copies of anything we've cached that is stale
247void
248DBSyncConnection::sendOps()
249{
250    tLog() << "Will send peer" << m_source->id() << "all ops since" << m_uscache.value( "lastop" ).toString();
251
252    source_ptr src = SourceList::instance()->getLocal();
253
254    DatabaseCommand_loadOps* cmd = new DatabaseCommand_loadOps( src, m_uscache.value( "lastop" ).toString() );
255    connect( cmd, SIGNAL( done( QString, QString, QList< dbop_ptr > ) ),
256                    SLOT( sendOpsData( QString, QString, QList< dbop_ptr > ) ) );
257
258    m_uscache.clear();
259
260    Database::instance()->enqueue( Tomahawk::dbcmd_ptr( cmd ) );
261}
262
263
264void
265DBSyncConnection::sendOpsData( QString sinceguid, QString lastguid, QList< dbop_ptr > ops )
266{
267    if ( m_lastSentOp == lastguid )
268        ops.clear();
269
270    m_lastSentOp = lastguid;
271    if ( ops.isEmpty() )
272    {
273        tLog( LOGVERBOSE ) << "Sending ok" << m_source->id() << m_source->friendlyName();
274        sendMsg( Msg::factory( "ok", Msg::DBOP ) );
275        return;
276    }
277
278    tLog( LOGVERBOSE ) << Q_FUNC_INFO << sinceguid << lastguid << "Num ops to send:" << ops.length();
279
280    int i;
281    for( i = 0; i < ops.length(); ++i )
282    {
283        quint8 flags = Msg::JSON | Msg::DBOP;
284
285        if ( ops.at( i )->compressed )
286            flags |= Msg::COMPRESSED;
287        if ( i != ops.length() - 1 )
288            flags |= Msg::FRAGMENT;
289
290        sendMsg( Msg::factory( ops.at( i )->payload, flags ) );
291    }
292}
293
294
295Connection*
296DBSyncConnection::clone()
297{
298    Q_ASSERT( false );
299    return 0;
300}