PageRenderTime 134ms CodeModel.GetById 40ms app.highlight 52ms RepoModel.GetById 38ms app.codeStats 0ms

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