/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. Database syncing using the oplog table.
  21. =======================================
  22. Load the last GUID we applied for the peer, tell them it.
  23. In return, they send us all new ops since that guid.
  24. We then apply those new ops to our cache of their data
  25. Synced.
  26. */
  27. #include "DbSyncConnection.h"
  28. #include "database/Database.h"
  29. #include "database/DatabaseCommand.h"
  30. #include "database/DatabaseCommand_CollectionStats.h"
  31. #include "database/DatabaseCommand_LoadOps.h"
  32. #include "utils/Logger.h"
  33. #include "Msg.h"
  34. #include "MsgProcessor.h"
  35. #include "RemoteCollection.h"
  36. #include "Source.h"
  37. #include "SourceList.h"
  38. using namespace Tomahawk;
  39. DBSyncConnection::DBSyncConnection( Servent* s, const source_ptr& src )
  40. : Connection( s )
  41. , m_fetchCount( 0 )
  42. , m_source( src )
  43. , m_state( UNKNOWN )
  44. {
  45. qDebug() << Q_FUNC_INFO << src->id() << thread();
  46. // Be aware of namespaces in these signals/slots!
  47. connect( this, SIGNAL( stateChanged( Tomahawk::DBSyncConnectionState, Tomahawk::DBSyncConnectionState, QString ) ),
  48. m_source.data(), SLOT( onStateChanged( Tomahawk::DBSyncConnectionState, Tomahawk::DBSyncConnectionState, QString ) ) );
  49. connect( m_source.data(), SIGNAL( commandsFinished() ),
  50. this, SLOT( lastOpApplied() ) );
  51. this->setMsgProcessorModeIn( MsgProcessor::PARSE_JSON | MsgProcessor::UNCOMPRESS_ALL );
  52. // msgs are stored compressed in the db, so not typically needed here, but doesnt hurt:
  53. this->setMsgProcessorModeOut( MsgProcessor::COMPRESS_IF_LARGE );
  54. }
  55. DBSyncConnection::~DBSyncConnection()
  56. {
  57. tDebug() << "DTOR" << Q_FUNC_INFO << m_source->id() << m_source->friendlyName();
  58. m_state = SHUTDOWN;
  59. }
  60. void
  61. DBSyncConnection::changeState( DBSyncConnectionState newstate )
  62. {
  63. if ( m_state == SHUTDOWN )
  64. return;
  65. DBSyncConnectionState s = m_state;
  66. m_state = newstate;
  67. qDebug() << "DBSYNC State changed from" << s << "to" << newstate << "- source:" << m_source->id();
  68. emit stateChanged( newstate, s, "" );
  69. }
  70. void
  71. DBSyncConnection::setup()
  72. {
  73. setId( QString( "DBSyncConnection/%1" ).arg( socket()->peerAddress().toString() ) );
  74. check();
  75. }
  76. void
  77. DBSyncConnection::trigger()
  78. {
  79. // if we're still setting up the connection, do nothing - we sync on first connect anyway:
  80. if ( !isRunning() )
  81. return;
  82. QMetaObject::invokeMethod( this, "sendMsg", Qt::QueuedConnection,
  83. Q_ARG( msg_ptr, Msg::factory( "{\"method\":\"trigger\"}", Msg::JSON ) ) );
  84. }
  85. void
  86. DBSyncConnection::check()
  87. {
  88. qDebug() << Q_FUNC_INFO << this << m_source->id();
  89. if ( m_state == SHUTDOWN )
  90. {
  91. qDebug() << "Aborting sync due to shutdown.";
  92. return;
  93. }
  94. if ( m_state != UNKNOWN && m_state != SYNCED )
  95. {
  96. qDebug() << "Syncing in progress already.";
  97. return;
  98. }
  99. m_uscache.clear();
  100. changeState( CHECKING );
  101. if ( m_source->lastCmdGuid().isEmpty() )
  102. {
  103. tDebug( LOGVERBOSE ) << "Fetching lastCmdGuid from database!";
  104. DatabaseCommand_CollectionStats* cmd_them = new DatabaseCommand_CollectionStats( m_source );
  105. connect( cmd_them, SIGNAL( done( QVariantMap ) ), SLOT( gotThem( QVariantMap ) ) );
  106. Database::instance()->enqueue( dbcmd_ptr(cmd_them) );
  107. }
  108. else
  109. {
  110. fetchOpsData( m_source->lastCmdGuid() );
  111. }
  112. }
  113. /// Called once we've loaded our cached data about their collection
  114. void
  115. DBSyncConnection::gotThem( const QVariantMap& m )
  116. {
  117. fetchOpsData( m.value( "lastop" ).toString() );
  118. }
  119. void
  120. DBSyncConnection::fetchOpsData( const QString& sinceguid )
  121. {
  122. changeState( FETCHING );
  123. tLog() << "Sending a FETCHOPS cmd since:" << sinceguid << "- source:" << m_source->id();
  124. QVariantMap msg;
  125. msg.insert( "method", "fetchops" );
  126. msg.insert( "lastop", sinceguid );
  127. sendMsg( msg );
  128. }
  129. void
  130. DBSyncConnection::handleMsg( msg_ptr msg )
  131. {
  132. Q_ASSERT( !msg->is( Msg::COMPRESSED ) );
  133. if ( m_state == FETCHING )
  134. changeState( PARSING );
  135. // "everything is synced" indicated by non-json msg containing "ok":
  136. if ( !msg->is( Msg::JSON ) &&
  137. msg->is( Msg::DBOP ) &&
  138. msg->payload() == "ok" )
  139. {
  140. changeState( SYNCED );
  141. // calc the collection stats, to updates the "X tracks" in the sidebar etc
  142. // this is done automatically if you run a dbcmd to add files.
  143. DatabaseCommand_CollectionStats* cmd = new DatabaseCommand_CollectionStats( m_source );
  144. connect( cmd, SIGNAL( done( const QVariantMap & ) ),
  145. m_source.data(), SLOT( setStats( const QVariantMap& ) ), Qt::QueuedConnection );
  146. Database::instance()->enqueue( Tomahawk::dbcmd_ptr(cmd) );
  147. return;
  148. }
  149. Q_ASSERT( msg->is( Msg::JSON ) );
  150. QVariantMap m = msg->json().toMap();
  151. if ( m.empty() )
  152. {
  153. tLog() << "Failed to parse msg in dbsync from:" << m_source->id() << m_source->friendlyName() << msg->payload();
  154. Q_ASSERT( false );
  155. return;
  156. }
  157. // a db sync op msg
  158. if ( msg->is( Msg::DBOP ) )
  159. {
  160. dbcmd_ptr cmd = Database::instance()->createCommandInstance( m, m_source );
  161. if ( !cmd.isNull() )
  162. {
  163. m_source->addCommand( cmd );
  164. }
  165. if ( !msg->is( Msg::FRAGMENT ) ) // last msg in this batch
  166. {
  167. changeState( SAVING ); // just DB work left to complete
  168. m_source->executeCommands();
  169. }
  170. return;
  171. }
  172. if ( m.value( "method" ).toString() == "fetchops" )
  173. {
  174. ++m_fetchCount;
  175. tDebug( LOGVERBOSE ) << "Fetching new dbops:" << m["lastop"].toString() << m_fetchCount;
  176. m_uscache = m;
  177. sendOps();
  178. return;
  179. }
  180. if ( m.value( "method" ).toString() == "trigger" )
  181. {
  182. tLog( LOGVERBOSE ) << "Got trigger msg on dbsyncconnection, checking for new stuff.";
  183. check();
  184. return;
  185. }
  186. tLog() << Q_FUNC_INFO << "Unhandled msg:" << msg->payload();
  187. Q_ASSERT( false );
  188. }
  189. void
  190. DBSyncConnection::lastOpApplied()
  191. {
  192. changeState( SYNCED );
  193. // check again, until peer responds we have no new ops to process
  194. check();
  195. }
  196. /// request new copies of anything we've cached that is stale
  197. void
  198. DBSyncConnection::sendOps()
  199. {
  200. tLog() << "Will send peer" << m_source->id() << "all ops since" << m_uscache.value( "lastop" ).toString();
  201. source_ptr src = SourceList::instance()->getLocal();
  202. DatabaseCommand_loadOps* cmd = new DatabaseCommand_loadOps( src, m_uscache.value( "lastop" ).toString() );
  203. connect( cmd, SIGNAL( done( QString, QString, QList< dbop_ptr > ) ),
  204. SLOT( sendOpsData( QString, QString, QList< dbop_ptr > ) ) );
  205. m_uscache.clear();
  206. Database::instance()->enqueue( Tomahawk::dbcmd_ptr( cmd ) );
  207. }
  208. void
  209. DBSyncConnection::sendOpsData( QString sinceguid, QString lastguid, QList< dbop_ptr > ops )
  210. {
  211. if ( m_lastSentOp == lastguid )
  212. ops.clear();
  213. m_lastSentOp = lastguid;
  214. if ( ops.isEmpty() )
  215. {
  216. tLog( LOGVERBOSE ) << "Sending ok" << m_source->id() << m_source->friendlyName();
  217. sendMsg( Msg::factory( "ok", Msg::DBOP ) );
  218. return;
  219. }
  220. tLog( LOGVERBOSE ) << Q_FUNC_INFO << sinceguid << lastguid << "Num ops to send:" << ops.length();
  221. int i;
  222. for( i = 0; i < ops.length(); ++i )
  223. {
  224. quint8 flags = Msg::JSON | Msg::DBOP;
  225. if ( ops.at( i )->compressed )
  226. flags |= Msg::COMPRESSED;
  227. if ( i != ops.length() - 1 )
  228. flags |= Msg::FRAGMENT;
  229. sendMsg( Msg::factory( ops.at( i )->payload, flags ) );
  230. }
  231. }
  232. Connection*
  233. DBSyncConnection::clone()
  234. {
  235. Q_ASSERT( false );
  236. return 0;
  237. }