/src/libtomahawk/database/Database.cpp

http://github.com/tomahawk-player/tomahawk · C++ · 386 lines · 277 code · 75 blank · 34 comment · 28 complexity · c04736ce4bcad35b81ff94dfd65d794a 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 "Database.h"
  20. #include "utils/Json.h"
  21. #include "utils/Logger.h"
  22. #include "DatabaseCommand.h"
  23. #include "DatabaseImpl.h"
  24. #include "DatabaseWorker.h"
  25. #include "IdThreadWorker.h"
  26. #include "PlaylistEntry.h"
  27. #include "DatabaseCommand_AddFiles.h"
  28. #include "DatabaseCommand_CreatePlaylist.h"
  29. #include "DatabaseCommand_DeleteFiles.h"
  30. #include "DatabaseCommand_DeletePlaylist.h"
  31. #include "DatabaseCommand_LogPlayback.h"
  32. #include "DatabaseCommand_RenamePlaylist.h"
  33. #include "DatabaseCommand_SetPlaylistRevision.h"
  34. #include "DatabaseCommand_CreateDynamicPlaylist.h"
  35. #include "DatabaseCommand_DeleteDynamicPlaylist.h"
  36. #include "DatabaseCommand_SetDynamicPlaylistRevision.h"
  37. #include "DatabaseCommand_SocialAction.h"
  38. #include "DatabaseCommand_ShareTrack.h"
  39. #include "DatabaseCommand_SetCollectionAttributes.h"
  40. #include "DatabaseCommand_SetTrackAttributes.h"
  41. #define DEFAULT_WORKER_THREADS 4
  42. #define MAX_WORKER_THREADS 16
  43. namespace Tomahawk
  44. {
  45. dbcmd_ptr
  46. DatabaseCommandFactory::newInstance()
  47. {
  48. dbcmd_ptr command = dbcmd_ptr( create() );
  49. notifyCreated( command );
  50. return command;
  51. }
  52. void
  53. DatabaseCommandFactory::notifyCreated( const dbcmd_ptr& command )
  54. {
  55. command->setWeakRef( command.toWeakRef() );
  56. emit created( command );
  57. }
  58. Database* Database::s_instance = 0;
  59. Database*
  60. Database::instance()
  61. {
  62. return s_instance;
  63. }
  64. Database::Database( const QString& dbname, QObject* parent )
  65. : QObject( parent )
  66. , m_ready( false )
  67. , m_impl( new DatabaseImpl( dbname ) )
  68. , m_workerRW( new DatabaseWorkerThread( this, true ) )
  69. , m_idWorker( new IdThreadWorker( this ) )
  70. {
  71. s_instance = this;
  72. // register commands
  73. registerCommand<DatabaseCommand_AddFiles>();
  74. registerCommand<DatabaseCommand_DeleteFiles>();
  75. registerCommand<DatabaseCommand_CreatePlaylist>();
  76. registerCommand<DatabaseCommand_DeletePlaylist>();
  77. registerCommand<DatabaseCommand_LogPlayback>();
  78. registerCommand<DatabaseCommand_RenamePlaylist>();
  79. registerCommand<DatabaseCommand_SetPlaylistRevision>();
  80. registerCommand<DatabaseCommand_CreateDynamicPlaylist>();
  81. registerCommand<DatabaseCommand_DeleteDynamicPlaylist>();
  82. registerCommand<DatabaseCommand_SetDynamicPlaylistRevision>();
  83. registerCommand<DatabaseCommand_SocialAction>();
  84. registerCommand<DatabaseCommand_SetCollectionAttributes>();
  85. registerCommand<DatabaseCommand_SetTrackAttributes>();
  86. registerCommand<DatabaseCommand_ShareTrack>();
  87. if ( MAX_WORKER_THREADS < DEFAULT_WORKER_THREADS )
  88. m_maxConcurrentThreads = MAX_WORKER_THREADS;
  89. else
  90. m_maxConcurrentThreads = qBound( DEFAULT_WORKER_THREADS, QThread::idealThreadCount(), MAX_WORKER_THREADS );
  91. tDebug() << Q_FUNC_INFO << "Using" << m_maxConcurrentThreads << "database worker threads";
  92. connect( m_impl, SIGNAL( indexReady() ), SLOT( markAsReady() ) );
  93. connect( m_impl, SIGNAL( indexStarted() ), SIGNAL( indexStarted() ) );
  94. connect( m_impl, SIGNAL( indexReady() ), SIGNAL( indexReady() ) );
  95. Q_ASSERT( m_workerRW );
  96. m_workerRW.data()->start();
  97. while ( m_workerThreads.count() < m_maxConcurrentThreads )
  98. {
  99. QPointer< DatabaseWorkerThread > workerThread( new DatabaseWorkerThread( this, false ) );
  100. Q_ASSERT( workerThread );
  101. workerThread.data()->start();
  102. m_workerThreads << workerThread;
  103. }
  104. m_idWorker->start();
  105. }
  106. Database::~Database()
  107. {
  108. tDebug( LOGVERBOSE ) << Q_FUNC_INFO;
  109. m_idWorker->stop();
  110. delete m_idWorker;
  111. if ( m_workerRW )
  112. {
  113. // Ensure event loop was started so quit() has any effect.
  114. m_workerRW->waitForEventLoopStart();
  115. m_workerRW.data()->quit();
  116. }
  117. foreach ( QPointer< DatabaseWorkerThread > workerThread, m_workerThreads )
  118. {
  119. // Ensure event loop was started so quit() has any effect.
  120. workerThread->waitForEventLoopStart();
  121. // If event loop already was killed, the following is just a no-op.
  122. workerThread->quit();
  123. }
  124. emit waitingForWorkers();
  125. if ( m_workerRW )
  126. {
  127. m_workerRW.data()->wait();
  128. delete m_workerRW.data();
  129. }
  130. foreach ( QPointer< DatabaseWorkerThread > workerThread, m_workerThreads )
  131. {
  132. if ( workerThread )
  133. {
  134. workerThread.data()->wait();
  135. delete workerThread.data();
  136. }
  137. }
  138. m_workerThreads.clear();
  139. qDeleteAll( m_implHash.values() );
  140. qDeleteAll( m_commandFactories.values() );
  141. delete m_impl;
  142. emit workersFinished();
  143. }
  144. void
  145. Database::loadIndex()
  146. {
  147. m_impl->loadIndex();
  148. }
  149. void
  150. Database::enqueue( const QList< Tomahawk::dbcmd_ptr >& lc )
  151. {
  152. Q_ASSERT( m_ready );
  153. if ( !m_ready )
  154. {
  155. tDebug() << "Can't enqueue DatabaseCommand, Database is not ready yet!";
  156. return;
  157. }
  158. foreach ( const Tomahawk::dbcmd_ptr& cmd, lc )
  159. {
  160. DatabaseCommandFactory* factory = commandFactoryByCommandName( cmd->commandname() );
  161. if ( factory )
  162. {
  163. factory->notifyCreated( cmd );
  164. }
  165. }
  166. tDebug( LOGVERBOSE ) << "Enqueueing" << lc.count() << "commands to rw thread";
  167. if ( m_workerRW && m_workerRW.data()->worker() )
  168. m_workerRW.data()->worker().data()->enqueue( lc );
  169. }
  170. void
  171. Database::enqueue( const Tomahawk::dbcmd_ptr& lc )
  172. {
  173. Q_ASSERT( m_ready );
  174. if ( !m_ready )
  175. {
  176. tDebug() << "Can't enqueue DatabaseCommand, Database is not ready yet!";
  177. return;
  178. }
  179. DatabaseCommandFactory* factory = commandFactoryByCommandName( lc->commandname() );
  180. if ( factory )
  181. {
  182. factory->notifyCreated( lc );
  183. }
  184. if ( lc->doesMutates() )
  185. {
  186. tDebug( LOGVERBOSE ) << "Enqueueing command to rw thread:" << lc->commandname();
  187. if ( m_workerRW && m_workerRW.data()->worker() )
  188. m_workerRW.data()->worker().data()->enqueue( lc );
  189. }
  190. else
  191. {
  192. // find thread for commandname with lowest amount of outstanding jobs and enqueue job
  193. int busyThreads = 0;
  194. QPointer< DatabaseWorkerThread > workerThread;
  195. QPointer< DatabaseWorker > happyWorker;
  196. for ( int i = 0; i < m_workerThreads.count(); i++ )
  197. {
  198. workerThread = m_workerThreads.at( i );
  199. if ( !workerThread || !workerThread->worker() )
  200. {
  201. // We have no valid worker for the current thread so skip it.
  202. continue;
  203. }
  204. if ( !workerThread->worker()->busy() )
  205. {
  206. // Case 1: We have a workerThread with no outstanding jobs.
  207. // As this is the optimal situation we do not need to look at
  208. // the other workers.
  209. happyWorker = workerThread->worker();
  210. break;
  211. }
  212. else
  213. {
  214. busyThreads++;
  215. if ( !happyWorker )
  216. {
  217. // Case 2: We have not yet got a happyWorker but the current
  218. // workerThread has a worker so use it as a fallback.
  219. happyWorker = workerThread->worker();
  220. }
  221. else if ( workerThread->worker()->outstandingJobs() < happyWorker->outstandingJobs() )
  222. {
  223. // Case 3: We have a worker and the current worker is less busy
  224. // than the previous minimum.
  225. happyWorker = workerThread->worker();
  226. }
  227. }
  228. }
  229. tDebug( LOGVERBOSE ) << "Enqueueing command to thread:" << happyWorker << busyThreads << lc->commandname();
  230. Q_ASSERT( happyWorker );
  231. happyWorker->enqueue( lc );
  232. }
  233. }
  234. DatabaseImpl*
  235. Database::impl()
  236. {
  237. QMutexLocker lock( &m_mutex );
  238. QThread* thread = QThread::currentThread();
  239. if ( !m_implHash.contains( thread ) )
  240. {
  241. tDebug( LOGVERBOSE ) << Q_FUNC_INFO << "Creating database impl for thread" << QThread::currentThread();
  242. DatabaseImpl* impl = m_impl->clone();
  243. m_implHash.insert( thread, impl );
  244. }
  245. return m_implHash.value( thread );
  246. }
  247. void
  248. Database::markAsReady()
  249. {
  250. if ( m_ready )
  251. return;
  252. tLog() << Q_FUNC_INFO << "Database is ready now!";
  253. // In addition to a ready index, we also need at leat one workerThread to
  254. // be ready so that we can queue DatabaseCommands.
  255. if ( m_workerThreads.size() > 0 && m_workerThreads.first() )
  256. {
  257. m_workerThreads.first()->waitForEventLoopStart();
  258. }
  259. m_ready = true;
  260. emit ready();
  261. }
  262. void
  263. Database::registerCommand( DatabaseCommandFactory* commandFactory )
  264. {
  265. // this is ugly, but we don't have virtual static methods in C++ :(
  266. dbcmd_ptr command = commandFactory->newInstance();
  267. const QString commandName = command->commandname();
  268. const QString className = command->metaObject()->className();
  269. tDebug( LOGVERBOSE ) << "Registering command" << commandName << "from class" << className;
  270. if( m_commandFactories.keys().contains( commandName ) )
  271. {
  272. tLog() << commandName << "is already in " << m_commandFactories.keys();
  273. }
  274. Q_ASSERT( !m_commandFactories.keys().contains( commandName ) );
  275. m_commandNameClassNameMapping.insert( commandName, className );
  276. m_commandFactories.insert( commandName, commandFactory );
  277. }
  278. DatabaseCommandFactory*
  279. Database::commandFactoryByClassName(const QString& className)
  280. {
  281. const QString commandName = m_commandNameClassNameMapping.key( className );
  282. return commandFactoryByCommandName( commandName );
  283. }
  284. DatabaseCommandFactory*
  285. Database::commandFactoryByCommandName(const QString& commandName )
  286. {
  287. return m_commandFactories.value( commandName );
  288. }
  289. dbcmd_ptr
  290. Database::createCommandInstance( const QString& commandName )
  291. {
  292. DatabaseCommandFactory* factory = commandFactoryByCommandName( commandName );
  293. if( !factory )
  294. {
  295. tLog() << "Unknown database command" << commandName;
  296. return dbcmd_ptr();
  297. }
  298. return factory->newInstance();
  299. }
  300. dbcmd_ptr
  301. Database::createCommandInstance(const QVariant& op, const source_ptr& source)
  302. {
  303. const QString commandName = op.toMap().value( "command" ).toString();
  304. dbcmd_ptr command = createCommandInstance( commandName );
  305. if ( command.isNull() )
  306. return command;
  307. command->setSource( source );
  308. TomahawkUtils::qvariant2qobject( op.toMap(), command.data() );
  309. return command;
  310. }
  311. }