PageRenderTime 165ms CodeModel.GetById 40ms app.highlight 53ms RepoModel.GetById 68ms app.codeStats 0ms

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