/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
- /* === This file is part of Tomahawk Player - <http://tomahawk-player.org> ===
- *
- * Copyright 2010-2011, Christian Muehlhaeuser <muesli@tomahawk-player.org>
- * Copyright 2010-2011, Jeff Mitchell <jeff@tomahawk-player.org>
- *
- * Tomahawk is free software: you can redistribute it and/or modify
- * it under the terms of the GNU General Public License as published by
- * the Free Software Foundation, either version 3 of the License, or
- * (at your option) any later version.
- *
- * Tomahawk is distributed in the hope that it will be useful,
- * but WITHOUT ANY WARRANTY; without even the implied warranty of
- * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
- * GNU General Public License for more details.
- *
- * You should have received a copy of the GNU General Public License
- * along with Tomahawk. If not, see <http://www.gnu.org/licenses/>.
- */
- /*
- Database syncing using the oplog table.
- =======================================
- Load the last GUID we applied for the peer, tell them it.
- In return, they send us all new ops since that guid.
- We then apply those new ops to our cache of their data
- Synced.
- */
- #include "DbSyncConnection.h"
- #include "database/Database.h"
- #include "database/DatabaseCommand.h"
- #include "database/DatabaseCommand_CollectionStats.h"
- #include "database/DatabaseCommand_LoadOps.h"
- #include "utils/Logger.h"
- #include "Msg.h"
- #include "MsgProcessor.h"
- #include "RemoteCollection.h"
- #include "Source.h"
- #include "SourceList.h"
- using namespace Tomahawk;
- DBSyncConnection::DBSyncConnection( Servent* s, const source_ptr& src )
- : Connection( s )
- , m_fetchCount( 0 )
- , m_source( src )
- , m_state( UNKNOWN )
- {
- qDebug() << Q_FUNC_INFO << src->id() << thread();
- // Be aware of namespaces in these signals/slots!
- connect( this, SIGNAL( stateChanged( Tomahawk::DBSyncConnectionState, Tomahawk::DBSyncConnectionState, QString ) ),
- m_source.data(), SLOT( onStateChanged( Tomahawk::DBSyncConnectionState, Tomahawk::DBSyncConnectionState, QString ) ) );
- connect( m_source.data(), SIGNAL( commandsFinished() ),
- this, SLOT( lastOpApplied() ) );
- this->setMsgProcessorModeIn( MsgProcessor::PARSE_JSON | MsgProcessor::UNCOMPRESS_ALL );
- // msgs are stored compressed in the db, so not typically needed here, but doesnt hurt:
- this->setMsgProcessorModeOut( MsgProcessor::COMPRESS_IF_LARGE );
- }
- DBSyncConnection::~DBSyncConnection()
- {
- tDebug() << "DTOR" << Q_FUNC_INFO << m_source->id() << m_source->friendlyName();
- m_state = SHUTDOWN;
- }
- void
- DBSyncConnection::changeState( DBSyncConnectionState newstate )
- {
- if ( m_state == SHUTDOWN )
- return;
- DBSyncConnectionState s = m_state;
- m_state = newstate;
- qDebug() << "DBSYNC State changed from" << s << "to" << newstate << "- source:" << m_source->id();
- emit stateChanged( newstate, s, "" );
- }
- void
- DBSyncConnection::setup()
- {
- setId( QString( "DBSyncConnection/%1" ).arg( socket()->peerAddress().toString() ) );
- check();
- }
- void
- DBSyncConnection::trigger()
- {
- // if we're still setting up the connection, do nothing - we sync on first connect anyway:
- if ( !isRunning() )
- return;
- QMetaObject::invokeMethod( this, "sendMsg", Qt::QueuedConnection,
- Q_ARG( msg_ptr, Msg::factory( "{\"method\":\"trigger\"}", Msg::JSON ) ) );
- }
- void
- DBSyncConnection::check()
- {
- qDebug() << Q_FUNC_INFO << this << m_source->id();
- if ( m_state == SHUTDOWN )
- {
- qDebug() << "Aborting sync due to shutdown.";
- return;
- }
- if ( m_state != UNKNOWN && m_state != SYNCED )
- {
- qDebug() << "Syncing in progress already.";
- return;
- }
- m_uscache.clear();
- changeState( CHECKING );
- if ( m_source->lastCmdGuid().isEmpty() )
- {
- tDebug( LOGVERBOSE ) << "Fetching lastCmdGuid from database!";
- DatabaseCommand_CollectionStats* cmd_them = new DatabaseCommand_CollectionStats( m_source );
- connect( cmd_them, SIGNAL( done( QVariantMap ) ), SLOT( gotThem( QVariantMap ) ) );
- Database::instance()->enqueue( dbcmd_ptr(cmd_them) );
- }
- else
- {
- fetchOpsData( m_source->lastCmdGuid() );
- }
- }
- /// Called once we've loaded our cached data about their collection
- void
- DBSyncConnection::gotThem( const QVariantMap& m )
- {
- fetchOpsData( m.value( "lastop" ).toString() );
- }
- void
- DBSyncConnection::fetchOpsData( const QString& sinceguid )
- {
- changeState( FETCHING );
- tLog() << "Sending a FETCHOPS cmd since:" << sinceguid << "- source:" << m_source->id();
- QVariantMap msg;
- msg.insert( "method", "fetchops" );
- msg.insert( "lastop", sinceguid );
- sendMsg( msg );
- }
- void
- DBSyncConnection::handleMsg( msg_ptr msg )
- {
- Q_ASSERT( !msg->is( Msg::COMPRESSED ) );
- if ( m_state == FETCHING )
- changeState( PARSING );
- // "everything is synced" indicated by non-json msg containing "ok":
- if ( !msg->is( Msg::JSON ) &&
- msg->is( Msg::DBOP ) &&
- msg->payload() == "ok" )
- {
- changeState( SYNCED );
- // calc the collection stats, to updates the "X tracks" in the sidebar etc
- // this is done automatically if you run a dbcmd to add files.
- DatabaseCommand_CollectionStats* cmd = new DatabaseCommand_CollectionStats( m_source );
- connect( cmd, SIGNAL( done( const QVariantMap & ) ),
- m_source.data(), SLOT( setStats( const QVariantMap& ) ), Qt::QueuedConnection );
- Database::instance()->enqueue( Tomahawk::dbcmd_ptr(cmd) );
- return;
- }
- Q_ASSERT( msg->is( Msg::JSON ) );
- QVariantMap m = msg->json().toMap();
- if ( m.empty() )
- {
- tLog() << "Failed to parse msg in dbsync from:" << m_source->id() << m_source->friendlyName() << msg->payload();
- Q_ASSERT( false );
- return;
- }
- // a db sync op msg
- if ( msg->is( Msg::DBOP ) )
- {
- dbcmd_ptr cmd = Database::instance()->createCommandInstance( m, m_source );
- if ( !cmd.isNull() )
- {
- m_source->addCommand( cmd );
- }
- if ( !msg->is( Msg::FRAGMENT ) ) // last msg in this batch
- {
- changeState( SAVING ); // just DB work left to complete
- m_source->executeCommands();
- }
- return;
- }
- if ( m.value( "method" ).toString() == "fetchops" )
- {
- ++m_fetchCount;
- tDebug( LOGVERBOSE ) << "Fetching new dbops:" << m["lastop"].toString() << m_fetchCount;
- m_uscache = m;
- sendOps();
- return;
- }
- if ( m.value( "method" ).toString() == "trigger" )
- {
- tLog( LOGVERBOSE ) << "Got trigger msg on dbsyncconnection, checking for new stuff.";
- check();
- return;
- }
- tLog() << Q_FUNC_INFO << "Unhandled msg:" << msg->payload();
- Q_ASSERT( false );
- }
- void
- DBSyncConnection::lastOpApplied()
- {
- changeState( SYNCED );
- // check again, until peer responds we have no new ops to process
- check();
- }
- /// request new copies of anything we've cached that is stale
- void
- DBSyncConnection::sendOps()
- {
- tLog() << "Will send peer" << m_source->id() << "all ops since" << m_uscache.value( "lastop" ).toString();
- source_ptr src = SourceList::instance()->getLocal();
- DatabaseCommand_loadOps* cmd = new DatabaseCommand_loadOps( src, m_uscache.value( "lastop" ).toString() );
- connect( cmd, SIGNAL( done( QString, QString, QList< dbop_ptr > ) ),
- SLOT( sendOpsData( QString, QString, QList< dbop_ptr > ) ) );
- m_uscache.clear();
- Database::instance()->enqueue( Tomahawk::dbcmd_ptr( cmd ) );
- }
- void
- DBSyncConnection::sendOpsData( QString sinceguid, QString lastguid, QList< dbop_ptr > ops )
- {
- if ( m_lastSentOp == lastguid )
- ops.clear();
- m_lastSentOp = lastguid;
- if ( ops.isEmpty() )
- {
- tLog( LOGVERBOSE ) << "Sending ok" << m_source->id() << m_source->friendlyName();
- sendMsg( Msg::factory( "ok", Msg::DBOP ) );
- return;
- }
- tLog( LOGVERBOSE ) << Q_FUNC_INFO << sinceguid << lastguid << "Num ops to send:" << ops.length();
- int i;
- for( i = 0; i < ops.length(); ++i )
- {
- quint8 flags = Msg::JSON | Msg::DBOP;
- if ( ops.at( i )->compressed )
- flags |= Msg::COMPRESSED;
- if ( i != ops.length() - 1 )
- flags |= Msg::FRAGMENT;
- sendMsg( Msg::factory( ops.at( i )->payload, flags ) );
- }
- }
- Connection*
- DBSyncConnection::clone()
- {
- Q_ASSERT( false );
- return 0;
- }