/src/libtomahawk/network/DbSyncConnection.cpp
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}