/src/libtomahawk/database/DatabaseWorker.cpp

http://github.com/tomahawk-player/tomahawk · C++ · 340 lines · 244 code · 55 blank · 41 comment · 24 complexity · 641d1b23762621d71eb0386baf5e0298 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. #include "DatabaseWorker.h"
  20. #include "utils/Json.h"
  21. #include "utils/Logger.h"
  22. #include "Database.h"
  23. #include "DatabaseImpl.h"
  24. #include "DatabaseCommandLoggable.h"
  25. #include "PlaylistEntry.h"
  26. #include "Source.h"
  27. #include "TomahawkSqlQuery.h"
  28. #include <QTimer>
  29. #include <QTime>
  30. #include <QSqlQuery>
  31. #ifndef QT_NO_DEBUG
  32. //#define DEBUG_TIMING TRUE
  33. #endif
  34. namespace Tomahawk
  35. {
  36. DatabaseWorkerThread::DatabaseWorkerThread( Database* db, bool mutates )
  37. : QThread()
  38. , m_db( db )
  39. , m_mutates( mutates )
  40. {
  41. m_startupMutex.lock();
  42. }
  43. void
  44. DatabaseWorkerThread::run()
  45. {
  46. tDebug() << Q_FUNC_INFO << "DatabaseWorkerThread starting...";
  47. m_worker = QPointer< DatabaseWorker >( new DatabaseWorker( m_db, m_mutates ) );
  48. m_startupMutex.unlock();
  49. exec();
  50. tDebug() << Q_FUNC_INFO << "DatabaseWorkerThread finishing...";
  51. if ( m_worker )
  52. delete m_worker.data();
  53. }
  54. DatabaseWorkerThread::~DatabaseWorkerThread()
  55. {
  56. }
  57. QPointer< DatabaseWorker >
  58. DatabaseWorkerThread::worker() const
  59. {
  60. return m_worker;
  61. }
  62. void
  63. DatabaseWorkerThread::waitForEventLoopStart()
  64. {
  65. m_startupMutex.lock();
  66. // no-op just to block on locking.
  67. m_startupMutex.unlock();
  68. }
  69. DatabaseWorker::DatabaseWorker( Database* db, bool mutates )
  70. : QObject()
  71. , m_db( db )
  72. , m_outstanding( 0 )
  73. {
  74. Q_UNUSED( mutates );
  75. tDebug() << Q_FUNC_INFO << "New db connection with name:" << Database::instance()->impl()->database().connectionName() << "on thread" << this->thread();
  76. }
  77. DatabaseWorker::~DatabaseWorker()
  78. {
  79. tDebug() << Q_FUNC_INFO << m_outstanding;
  80. if ( m_outstanding )
  81. {
  82. foreach ( const Tomahawk::dbcmd_ptr& cmd, m_commands )
  83. {
  84. tDebug() << "Outstanding db command to finish:" << cmd->guid() << cmd->commandname();
  85. }
  86. }
  87. }
  88. void
  89. DatabaseWorker::enqueue( const QList< Tomahawk::dbcmd_ptr >& cmds )
  90. {
  91. QMutexLocker lock( &m_mut );
  92. m_outstanding += cmds.count();
  93. m_commands << cmds;
  94. if ( m_outstanding == cmds.count() )
  95. QTimer::singleShot( 0, this, SLOT( doWork() ) );
  96. }
  97. void
  98. DatabaseWorker::enqueue( const Tomahawk::dbcmd_ptr& cmd )
  99. {
  100. QMutexLocker lock( &m_mut );
  101. m_outstanding++;
  102. m_commands << cmd;
  103. if ( m_outstanding == 1 )
  104. QTimer::singleShot( 0, this, SLOT( doWork() ) );
  105. }
  106. void
  107. DatabaseWorker::doWork()
  108. {
  109. /*
  110. Run the dbcmd. Only inside a transaction if the cmd does mutates.
  111. If the cmd is modifying local content (ie source->isLocal()) then
  112. log to the database oplog for replication to peers.
  113. */
  114. #ifdef DEBUG_TIMING
  115. QTime timer;
  116. timer.start();
  117. #endif
  118. QList< Tomahawk::dbcmd_ptr > cmdGroup;
  119. Tomahawk::dbcmd_ptr cmd;
  120. {
  121. QMutexLocker lock( &m_mut );
  122. cmd = m_commands.takeFirst();
  123. }
  124. DatabaseImpl* impl = Database::instance()->impl();
  125. if ( cmd->doesMutates() )
  126. {
  127. bool transok = impl->database().transaction();
  128. Q_ASSERT( transok );
  129. Q_UNUSED( transok );
  130. }
  131. unsigned int completed = 0;
  132. try
  133. {
  134. bool finished = false;
  135. {
  136. while ( !finished )
  137. {
  138. completed++;
  139. cmd->_exec( impl ); // runs actual SQL stuff
  140. if ( cmd->loggable() )
  141. {
  142. // We only save our own ops to the oplog, since incoming ops from peers
  143. // are applied immediately.
  144. //
  145. // Crazy idea: if peers had keypairs and could sign ops/msgs, in theory it
  146. // would be safe to sync ops for friend A from friend B's cache, if he saved them,
  147. // which would mean you could get updates even if a peer was offline.
  148. if ( cmd->source()->isLocal() && !cmd->localOnly() )
  149. {
  150. // save to op-log
  151. DatabaseCommandLoggable* command = (DatabaseCommandLoggable*)cmd.data();
  152. logOp( command );
  153. }
  154. else
  155. {
  156. // Make a note of the last guid we applied for this source
  157. // so we can always request just the newer ops in future.
  158. //
  159. if ( !cmd->singletonCmd() )
  160. {
  161. TomahawkSqlQuery query = impl->newquery();
  162. query.prepare( "UPDATE source SET lastop = ? WHERE id = ?" );
  163. query.addBindValue( cmd->guid() );
  164. query.addBindValue( cmd->source()->id() );
  165. if ( !query.exec() )
  166. {
  167. throw "Failed to set lastop";
  168. }
  169. }
  170. }
  171. }
  172. cmdGroup << cmd;
  173. if ( cmd->groupable() && !m_commands.isEmpty() )
  174. {
  175. QMutexLocker lock( &m_mut );
  176. if ( m_commands.first()->groupable() )
  177. {
  178. cmd = m_commands.takeFirst();
  179. }
  180. else
  181. {
  182. finished = true;
  183. }
  184. }
  185. else
  186. finished = true;
  187. }
  188. if ( cmd->doesMutates() )
  189. {
  190. qDebug() << "Committing" << cmd->commandname() << cmd->guid();
  191. if ( !impl->newquery().commitTransaction() )
  192. {
  193. tDebug() << "FAILED TO COMMIT TRANSACTION*";
  194. throw "commit failed";
  195. }
  196. }
  197. #ifdef DEBUG_TIMING
  198. uint duration = timer.elapsed();
  199. tDebug() << "DBCmd Duration:" << duration << "ms, now running postcommit for" << cmd->commandname();
  200. #endif
  201. foreach ( Tomahawk::dbcmd_ptr c, cmdGroup )
  202. c->postCommit();
  203. #ifdef DEBUG_TIMING
  204. tDebug() << "Post commit finished in" << timer.elapsed() - duration << "ms for" << cmd->commandname();
  205. #endif
  206. }
  207. }
  208. catch ( const char * msg )
  209. {
  210. tLog() << endl
  211. << "*ERROR* processing databasecommand:"
  212. << cmd->commandname()
  213. << msg
  214. << impl->database().lastError().databaseText()
  215. << impl->database().lastError().driverText()
  216. << endl;
  217. if ( cmd->doesMutates() )
  218. impl->database().rollback();
  219. Q_ASSERT( false );
  220. }
  221. catch (...)
  222. {
  223. qDebug() << "Uncaught exception processing dbcmd";
  224. if ( cmd->doesMutates() )
  225. impl->database().rollback();
  226. Q_ASSERT( false );
  227. throw;
  228. }
  229. foreach ( Tomahawk::dbcmd_ptr c, cmdGroup )
  230. c->emitFinished();
  231. QMutexLocker lock( &m_mut );
  232. m_outstanding -= completed;
  233. if ( m_outstanding > 0 )
  234. QTimer::singleShot( 0, this, SLOT( doWork() ) );
  235. }
  236. // this should take a const command, need to check/make json stuff mutable for some objs tho maybe.
  237. void
  238. DatabaseWorker::logOp( DatabaseCommandLoggable* command )
  239. {
  240. TomahawkSqlQuery oplogquery = Database::instance()->impl()->newquery();
  241. tLog( LOGVERBOSE ) << "INSERTING INTO OPLOG:" << command->source()->id() << command->guid() << command->commandname();
  242. oplogquery.prepare( "INSERT INTO oplog(source, guid, command, singleton, compressed, json) "
  243. "VALUES(?, ?, ?, ?, ?, ?)" );
  244. QVariantMap variant = TomahawkUtils::qobject2qvariant( command );
  245. QByteArray ba = TomahawkUtils::toJson( variant );
  246. bool compressed = false;
  247. if ( ba.length() >= 512 )
  248. {
  249. // We need to compress this in this thread, since inserting into the log
  250. // has to happen as part of the same transaction as the dbcmd.
  251. // (we are in a worker thread for RW dbcmds anyway, so it's ok)
  252. ba = qCompress( ba, 9 );
  253. compressed = true;
  254. }
  255. if ( command->singletonCmd() )
  256. {
  257. tLog( LOGVERBOSE ) << "Singleton command, deleting previous oplog commands";
  258. TomahawkSqlQuery oplogdelquery = Database::instance()->impl()->newquery();
  259. oplogdelquery.prepare( QString( "DELETE FROM oplog WHERE "
  260. "source %1 "
  261. "AND (singleton = 'true' or singleton = 1) "
  262. "AND command = ?" )
  263. .arg( command->source()->isLocal() ? "IS NULL" : QString( "= %1" ).arg( command->source()->id() ) ) );
  264. oplogdelquery.bindValue( 0, command->commandname() );
  265. oplogdelquery.exec();
  266. }
  267. tLog( LOGVERBOSE ) << "Saving to oplog:" << command->commandname()
  268. << "bytes:" << ba.length()
  269. << "guid:" << command->guid();
  270. oplogquery.bindValue( 0, command->source()->isLocal() ?
  271. QVariant(QVariant::Int) : command->source()->id() );
  272. oplogquery.bindValue( 1, command->guid() );
  273. oplogquery.bindValue( 2, command->commandname() );
  274. oplogquery.bindValue( 3, command->singletonCmd() ? "true" : "false" );
  275. oplogquery.bindValue( 4, compressed ? "true" : "false" );
  276. oplogquery.bindValue( 5, ba );
  277. if ( !oplogquery.exec() )
  278. {
  279. tLog() << "Error saving to oplog";
  280. throw "Failed to save to oplog";
  281. }
  282. }
  283. }