PageRenderTime 112ms CodeModel.GetById 2ms app.highlight 99ms RepoModel.GetById 1ms app.codeStats 1ms

/src/libtomahawk/Playlist.cpp

http://github.com/tomahawk-player/tomahawk
C++ | 959 lines | 717 code | 190 blank | 52 comment | 56 complexity | d3eda2a3ef93461dfd716af9840c0f1e MD5 | raw file
  1/* === This file is part of Tomahawk Player - <http://tomahawk-player.org> ===
  2 *
  3 *   Copyright 2010-2015, Christian Muehlhaeuser <muesli@tomahawk-player.org>
  4 *   Copyright 2010-2011, Leo Franchi <lfranchi@kde.org>
  5 *   Copyright 2010-2012, Jeff Mitchell <jeff@tomahawk-player.org>
  6 *
  7 *   Tomahawk is free software: you can redistribute it and/or modify
  8 *   it under the terms of the GNU General Public License as published by
  9 *   the Free Software Foundation, either version 3 of the License, or
 10 *   (at your option) any later version.
 11 *
 12 *   Tomahawk is distributed in the hope that it will be useful,
 13 *   but WITHOUT ANY WARRANTY; without even the implied warranty of
 14 *   MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
 15 *   GNU General Public License for more details.
 16 *
 17 *   You should have received a copy of the GNU General Public License
 18 *   along with Tomahawk. If not, see <http://www.gnu.org/licenses/>.
 19 */
 20
 21#include "Playlist_p.h"
 22
 23#include "collection/Collection.h"
 24#include "database/Database.h"
 25#include "database/DatabaseCommand_LoadPlaylistEntries.h"
 26#include "database/DatabaseCommand_SetPlaylistRevision.h"
 27#include "database/DatabaseCommand_CreatePlaylist.h"
 28#include "database/DatabaseCommand_DeletePlaylist.h"
 29#include "database/DatabaseCommand_RenamePlaylist.h"
 30#include "playlist/PlaylistUpdaterInterface.h"
 31#include "playlist/PlaylistModel.h"
 32#include "utils/Logger.h"
 33#include "utils/Closure.h"
 34
 35#include "Pipeline.h"
 36#include "PlaylistEntry.h"
 37#include "PlaylistPlaylistInterface.h"
 38#include "Source.h"
 39#include "SourceList.h"
 40
 41#include <QDomDocument>
 42#include <QDomElement>
 43
 44using namespace Tomahawk;
 45
 46static QSharedPointer<PlaylistRemovalHandler> s_removalHandler;
 47
 48Playlist::Playlist( const source_ptr& author )
 49    : d_ptr( new PlaylistPrivate( this, author ) )
 50{
 51}
 52
 53
 54// used when loading from DB:
 55Playlist::Playlist( const source_ptr& src,
 56                    const QString& currentrevision,
 57                    const QString& title,
 58                    const QString& info,
 59                    const QString& creator,
 60                    uint createdOn,
 61                    bool shared,
 62                    int lastmod,
 63                    const QString& guid )
 64    : QObject()
 65    , d_ptr( new PlaylistPrivate( this, src, currentrevision, title, info, creator, createdOn, shared, lastmod, guid ) )
 66{
 67    init();
 68}
 69
 70
 71Playlist::Playlist( const source_ptr& author,
 72                    const QString& guid,
 73                    const QString& title,
 74                    const QString& info,
 75                    const QString& creator,
 76                    bool shared,
 77                    const QList< Tomahawk::plentry_ptr >& entries )
 78    : QObject()
 79    , d_ptr( new PlaylistPrivate( this, author, guid, title, info, creator, shared, entries ) )
 80{
 81    init();
 82}
 83
 84
 85Playlist::Playlist( const source_ptr& author,
 86                    const QString& guid,
 87                    const QString& title,
 88                    const QString& info,
 89                    const QString& creator,
 90                    bool shared)
 91    : QObject()
 92    , d_ptr( new PlaylistPrivate( this, author, guid, title, info, creator, shared, QList< Tomahawk::plentry_ptr >() ) )
 93{
 94    init();
 95}
 96
 97
 98void
 99Playlist::init()
100{
101    Q_D( Playlist );
102
103    d->busy = false;
104    d->deleted = false;
105    d->locallyChanged = false;
106    d->loaded = false;
107
108    connect( Pipeline::instance(), SIGNAL( idle() ), SLOT( onResolvingFinished() ) );
109}
110
111
112Playlist::~Playlist()
113{
114    delete d_ptr;
115}
116
117
118QSharedPointer<PlaylistRemovalHandler> Playlist::removalHandler()
119{
120    if ( s_removalHandler.isNull() )
121    {
122        s_removalHandler = QSharedPointer<PlaylistRemovalHandler>( new PlaylistRemovalHandler() );
123    }
124
125    return s_removalHandler;
126}
127
128
129playlist_ptr
130Playlist::create( const source_ptr& author,
131                  const QString& guid,
132                  const QString& title,
133                  const QString& info,
134                  const QString& creator,
135                  bool shared,
136                  const QList<Tomahawk::query_ptr>& queries )
137{
138    QList< plentry_ptr > entries;
139    foreach( const Tomahawk::query_ptr& query, queries )
140    {
141        plentry_ptr p( new PlaylistEntry );
142        p->setGuid( uuid() );
143        p->setDuration( query->track()->duration() );
144        p->setLastmodified( 0 );
145        p->setAnnotation( query->property( "annotation" ).toString() );
146        p->setQuery( query );
147
148        entries << p;
149    }
150
151    playlist_ptr playlist( new Playlist( author, guid, title, info, creator, shared, entries ), &QObject::deleteLater );
152    playlist->setWeakSelf( playlist.toWeakRef() );
153
154    // save to DB in the background
155    // Watch for the created() signal if you need to be sure it's written.
156    //
157    // When a playlist is created it will reportCreated(), adding it to the
158    // collection it belongs to and emitting the appropriate signal.
159    // When we create a new playlist for the local Source.here we call reportCreated()
160    // immediately, so the GUI can reflect the new playlist without waiting for the DB sync
161    //
162    // When createplaylist DBOPs come from peers, the postCommitHook will call
163    // reportCreated for us automatically, which should cause new playlists to be added to the GUI.
164
165    DatabaseCommand_CreatePlaylist* cmd = new DatabaseCommand_CreatePlaylist( author, playlist );
166    connect( cmd, SIGNAL( finished() ), playlist.data(), SIGNAL( created() ) );
167    Database::instance()->enqueue( dbcmd_ptr(cmd) );
168    playlist->reportCreated( playlist );
169
170    return playlist;
171}
172
173
174playlist_ptr
175Playlist::get( const QString& guid )
176{
177    playlist_ptr p;
178
179    foreach( const Tomahawk::source_ptr& source, SourceList::instance()->sources() )
180    {
181        p = source->dbCollection()->playlist( guid );
182        if ( !p )
183            p = source->dbCollection()->autoPlaylist( guid );
184        if ( !p )
185            p = source->dbCollection()->station( guid );
186
187        if ( p )
188            return p;
189    }
190
191    return p;
192}
193
194
195void
196Playlist::rename( const QString& title )
197{
198    DatabaseCommand_RenamePlaylist* cmd = new DatabaseCommand_RenamePlaylist( author(), guid(), title );
199    Database::instance()->enqueue( Tomahawk::dbcmd_ptr(cmd) );
200}
201
202
203void
204Playlist::setTitle( const QString& title )
205{
206    Q_D( Playlist );
207
208    if ( title == d->title )
209        return;
210
211    const QString oldTitle = d->title;
212    d->title = title;
213
214    emit changed();
215    emit renamed( d->title, oldTitle );
216}
217
218
219void
220Playlist::reportCreated( const playlist_ptr& self )
221{
222    Q_D( Playlist );
223
224    Q_ASSERT( self.data() == this );
225    d->source->dbCollection()->addPlaylist( self );
226}
227
228
229void
230Playlist::reportDeleted( const Tomahawk::playlist_ptr& self )
231{
232    Q_D( Playlist );
233
234    Q_ASSERT( self.data() == this );
235    if ( !d->updaters.isEmpty() )
236    {
237        foreach( PlaylistUpdaterInterface* updater, d->updaters )
238        {
239            updater->remove();
240        }
241    }
242
243    d->deleted = true;
244    d->source->dbCollection()->deletePlaylist( self );
245
246    emit deleted( self );
247}
248
249
250void
251Playlist::addUpdater( PlaylistUpdaterInterface* updater )
252{
253    Q_D( Playlist );
254    d->updaters << updater;
255
256    connect( updater, SIGNAL( changed() ), this, SIGNAL( changed() ), Qt::UniqueConnection );
257    connect( updater, SIGNAL( destroyed( QObject* ) ), this, SIGNAL( changed() ), Qt::QueuedConnection );
258
259    emit changed();
260}
261
262
263void
264Playlist::removeUpdater( PlaylistUpdaterInterface* updater )
265{
266    Q_D( Playlist );
267    d->updaters.removeAll( updater );
268
269    disconnect( updater, SIGNAL( changed() ), this, SIGNAL( changed() ) );
270    disconnect( updater, SIGNAL( destroyed( QObject* ) ), this, SIGNAL( changed() ) );
271
272    emit changed();
273}
274
275
276bool
277Playlist::hasCustomDeleter() const
278{
279    Q_D( const Playlist );
280    foreach ( PlaylistUpdaterInterface* updater, d->updaters )
281    {
282        if ( updater->hasCustomDeleter() )
283            return true;
284    }
285
286    return false;
287}
288
289
290void
291Playlist::loadRevision( const QString& rev )
292{
293//    qDebug() << Q_FUNC_INFO << currentrevision() << rev << m_title;
294
295    setBusy( true );
296    DatabaseCommand_LoadPlaylistEntries* cmd =
297            new DatabaseCommand_LoadPlaylistEntries( rev.isEmpty() ? currentrevision() : rev );
298
299    connect( cmd, SIGNAL( done( const QString&,
300                                const QList<QString>&,
301                                const QList<QString>&,
302                                bool,
303                                const QMap< QString, Tomahawk::plentry_ptr >&,
304                                bool ) ),
305                    SLOT( setRevision( const QString&,
306                                       const QList<QString>&,
307                                       const QList<QString>&,
308                                       bool,
309                                       const QMap< QString, Tomahawk::plentry_ptr >&,
310                                       bool ) ) );
311
312    Database::instance()->enqueue( Tomahawk::dbcmd_ptr( cmd ) );
313}
314
315
316//public, model can call this if user changes a playlist:
317void
318Playlist::createNewRevision( const QString& newrev, const QString& oldrev, const QList< plentry_ptr >& entries )
319{
320    Q_D( Playlist );
321    tDebug() << Q_FUNC_INFO << newrev << oldrev << entries.count();
322    Q_ASSERT( d->source->isLocal() || newrev == oldrev );
323
324    if ( busy() )
325    {
326        d->revisionQueue.enqueue( RevisionQueueItem( newrev, oldrev, entries, oldrev == currentrevision() ) );
327        return;
328    }
329
330    if ( newrev != oldrev )
331        setBusy( true );
332
333    // calc list of newly added entries:
334    QList<plentry_ptr> added = newEntries( entries );
335    QStringList orderedguids;
336    qDebug() << "Inserting ordered GUIDs:";
337    foreach( const plentry_ptr& p, entries )
338    {
339        qDebug() << p->guid() << p->query()->track()->toString();
340        orderedguids << p->guid();
341    }
342
343    foreach( const plentry_ptr& p, added )
344        qDebug() << p->guid();
345
346    // source making the change (local user in this case)
347    source_ptr author = SourceList::instance()->getLocal();
348    // command writes new rev to DB and calls setRevision, which emits our signal
349    DatabaseCommand_SetPlaylistRevision* cmd =
350            new DatabaseCommand_SetPlaylistRevision( author,
351                                                     guid(),
352                                                     newrev,
353                                                     oldrev,
354                                                     orderedguids,
355                                                     added,
356                                                     entries );
357
358    connect( cmd, SIGNAL( finished() ),
359             this, SLOT( setPlaylistRevisionFinished() ) );
360    if ( d->queuedSetPlaylistRevision )
361    {
362        d->queuedSetPlaylistRevisionCmds.enqueue( cmd );
363    }
364    else
365    {
366        d->queuedSetPlaylistRevision = true;
367        Database::instance()->enqueue( Tomahawk::dbcmd_ptr( cmd ) );
368    }
369}
370
371
372void
373Playlist::updateEntries( const QString& newrev, const QString& oldrev, const QList< plentry_ptr >& entries )
374{
375    Q_D( Playlist );
376    tDebug() << Q_FUNC_INFO << newrev << oldrev << entries.count();
377    Q_ASSERT( d->source->isLocal() || newrev == oldrev );
378
379    if ( busy() )
380    {
381        d->updateQueue.enqueue( RevisionQueueItem( newrev, oldrev, entries, oldrev == currentrevision() ) );
382        return;
383    }
384
385    if ( newrev != oldrev )
386        setBusy( true );
387
388    QStringList orderedguids;
389    foreach( const plentry_ptr& p, d->entries )
390    {
391        orderedguids << p->guid();
392    }
393
394    qDebug() << "Updating playlist metadata:" << entries;
395    DatabaseCommand_SetPlaylistRevision* cmd =
396    new DatabaseCommand_SetPlaylistRevision( SourceList::instance()->getLocal(),
397                                             guid(),
398                                             newrev,
399                                             oldrev,
400                                             orderedguids,
401                                             entries );
402
403    connect( cmd, SIGNAL( finished() ),
404             this, SLOT( setPlaylistRevisionFinished() ) );
405    if ( d->queuedSetPlaylistRevision )
406    {
407        d->queuedSetPlaylistRevisionCmds.enqueue( cmd );
408    }
409    else
410    {
411        d->queuedSetPlaylistRevision = true;
412        Database::instance()->enqueue( Tomahawk::dbcmd_ptr( cmd ) );
413    }
414}
415
416
417// private, called when we loadRevision, or by our friend class DatabaseCommand_SetPlaylistRevision
418// used to save new revision data (either originating locally, or via network msg for syncing)
419void
420Playlist::setRevision( const QString& rev,
421                       const QList<QString>& neworderedguids,
422                       const QList<QString>& oldorderedguids,
423                       bool is_newest_rev,
424                       const QMap< QString, Tomahawk::plentry_ptr >& addedmap,
425                       bool applied )
426{
427    Q_D( Playlist );
428    if ( QThread::currentThread() != thread() )
429    {
430        QMetaObject::invokeMethod( this,
431                                   "setRevision",
432                                   Qt::BlockingQueuedConnection,
433                                   Q_ARG( QString, rev ),
434                                   Q_ARG( QList<QString>, neworderedguids ),
435                                   Q_ARG( QList<QString>, oldorderedguids ),
436                                   Q_ARG( bool, is_newest_rev ),
437                                   QGenericArgument( "QMap< QString,Tomahawk::plentry_ptr >", (const void*)&addedmap ),
438                                   Q_ARG( bool, applied )
439                                 );
440        return;
441    }
442
443    PlaylistRevision pr = setNewRevision( rev, neworderedguids, oldorderedguids, is_newest_rev, addedmap );
444
445    Q_ASSERT( applied );
446    if ( applied )
447        d->currentrevision = rev;
448    pr.applied = applied;
449
450    foreach( const plentry_ptr& entry, d->entries )
451    {
452        connect( entry.data(), SIGNAL( resultChanged() ), SLOT( onResultsChanged() ), Qt::UniqueConnection );
453    }
454
455    setBusy( false );
456    setLoaded( true );
457
458    if ( !d->initEntries.isEmpty() && currentrevision().isEmpty() )
459    {
460        // add initial tracks
461        createNewRevision( uuid(), currentrevision(), d->initEntries );
462        d->initEntries.clear();
463    }
464    else
465        emit revisionLoaded( pr );
466
467    checkRevisionQueue();
468}
469
470
471PlaylistRevision
472Playlist::setNewRevision( const QString& rev,
473                          const QList<QString>& neworderedguids,
474                          const QList<QString>& oldorderedguids,
475                          bool is_newest_rev,
476                          const QMap< QString, Tomahawk::plentry_ptr >& addedmap )
477{
478    Q_D( Playlist );
479    Q_UNUSED( oldorderedguids );
480    Q_UNUSED( is_newest_rev );
481
482    // build up correctly ordered new list of plentry_ptrs from
483    // existing ones, and the ones that have been added
484    QMap<QString, plentry_ptr> entriesmap;
485    foreach ( const plentry_ptr& p, d->entries )
486    {
487        entriesmap.insert( p->guid(), p );
488    }
489
490    // re-build m_entries from neworderedguids. plentries come either from the old m_entries OR addedmap.
491    d->entries.clear();
492
493    foreach ( const QString& id, neworderedguids )
494    {
495        if ( entriesmap.contains( id ) )
496        {
497            d->entries.append( entriesmap.value( id ) );
498        }
499        else if ( addedmap.contains( id ) )
500        {
501            d->entries.append( addedmap.value( id ) );
502        }
503        else
504        {
505            tDebug() << "id:" << id;
506            tDebug() << "newordered:" << neworderedguids.count() << neworderedguids;
507            tDebug() << "entriesmap:" << entriesmap.count() << entriesmap;
508            tDebug() << "addedmap:" << addedmap.count() << addedmap;
509            tDebug() << "m_entries" << d->entries;
510
511            tLog() << "Playlist error for playlist with guid" << guid() << "from source" << author()->friendlyName();
512//             Q_ASSERT( false ); // XXX
513        }
514    }
515
516    PlaylistRevision pr;
517    pr.oldrevisionguid = d->currentrevision;
518    pr.revisionguid = rev;
519    pr.added = addedmap.values();
520    pr.newlist = d->entries;
521
522    return pr;
523}
524
525void
526Playlist::removeFromDatabase()
527{
528    Q_D( Playlist );
529
530    emit aboutToBeDeleted( d->weakSelf.toStrongRef() );
531    DatabaseCommand_DeletePlaylist* cmd = new DatabaseCommand_DeletePlaylist( d->source, d->guid );
532    Database::instance()->enqueue( Tomahawk::dbcmd_ptr( cmd ) );
533}
534
535
536Playlist::Playlist( PlaylistPrivate *d )
537    : d_ptr( d )
538{
539    init();
540}
541
542
543source_ptr
544Playlist::author() const
545{
546    Q_D( const Playlist );
547    return d->source;
548}
549
550
551void
552Playlist::resolve()
553{
554    Q_D( Playlist );
555    QList< query_ptr > qlist;
556    foreach( const plentry_ptr& p, d->entries )
557    {
558        qlist << p->query();
559    }
560
561    Pipeline::instance()->resolve( qlist );
562}
563
564
565void
566Playlist::onResultsChanged()
567{
568    Q_D( Playlist );
569    d->locallyChanged = true;
570}
571
572
573void
574Playlist::onResolvingFinished()
575{
576    Q_D( Playlist );
577    if ( d->locallyChanged && !d->deleted )
578    {
579        d->locallyChanged = false;
580        createNewRevision( currentrevision(), currentrevision(), d->entries );
581    }
582}
583
584
585void
586Playlist::setPlaylistRevisionFinished()
587{
588    Q_D( Playlist );
589    if ( !d->queuedSetPlaylistRevisionCmds.isEmpty() )
590    {
591        DatabaseCommand_SetPlaylistRevision* cmd = d->queuedSetPlaylistRevisionCmds.dequeue();
592        // Update oldrev to currentRevision so that optimistic locking works correctly.
593        cmd->setOldrev( currentrevision() );
594        Database::instance()->enqueue( Tomahawk::dbcmd_ptr( cmd ) );
595    }
596    else
597    {
598        d->queuedSetPlaylistRevision = false;
599    }
600}
601
602
603void
604Playlist::addEntry( const query_ptr& query )
605{
606    QList<query_ptr> queries;
607    queries << query;
608
609    addEntries( queries );
610}
611
612
613void
614Playlist::addEntries( const QList<query_ptr>& queries )
615{
616    Q_D( Playlist );
617    if ( !d->loaded )
618    {
619        tDebug() << Q_FUNC_INFO << "Queueing addEntries call!";
620        loadRevision();
621        d->queuedOps << NewClosure( 0, "", this, SLOT( addEntries( QList<Tomahawk::query_ptr> ) ), queries );
622        return;
623    }
624
625    const QList<plentry_ptr> el = entriesFromQueries( queries );
626    const int prevSize = d->entries.size();
627
628    QString newrev = uuid();
629    createNewRevision( newrev, d->currentrevision, el );
630
631    // We are appending at end, so notify listeners.
632    // PlaylistModel also emits during appends, but since we call
633    // createNewRevision, it reloads instead of appends.
634    const QList<plentry_ptr> added = el.mid( prevSize );
635    tDebug( LOGVERBOSE ) << "Playlist got" << queries.size() << "tracks added, emitting tracksInserted with:" << added.size() << "at pos:" << prevSize - 1;
636    emit tracksInserted( added, prevSize );
637}
638
639
640void
641Playlist::insertEntries( const QList< query_ptr >& queries, const int position )
642{
643    Q_D( Playlist );
644    if ( !d->loaded )
645    {
646        tDebug() << Q_FUNC_INFO << "Queueing insertEntries call!";
647        loadRevision();
648        d->queuedOps << NewClosure( 0, "", this, SLOT( insertEntries( QList<Tomahawk::query_ptr>, int ) ), queries, position );
649        return;
650    }
651
652    QList<plentry_ptr> toInsert = entriesFromQueries( queries, true );
653    QList<plentry_ptr> entries = d->entries;
654
655    Q_ASSERT( position <= d->entries.size() );
656    if ( position > d->entries.size() )
657    {
658        tDebug() << "ERROR trying to insert tracks past end of playlist! Appending!";
659        addEntries( queries );
660        return;
661    }
662
663    for ( int i = toInsert.size()-1; i >= 0; --i )
664        entries.insert( position, toInsert.at(i) );
665
666    createNewRevision( uuid(), d->currentrevision, entries );
667
668    // We are appending at end, so notify listeners.
669    // PlaylistModel also emits during appends, but since we call
670    // createNewRevision, it reloads instead of appends.
671    qDebug() << "Playlist got" << toInsert.size() << "tracks added, emitting tracksInserted at pos:" << position;
672    emit tracksInserted( toInsert, position );
673}
674
675
676QList<plentry_ptr>
677Playlist::entriesFromQueries( const QList<Tomahawk::query_ptr>& queries, bool clearFirst )
678{
679    QList<plentry_ptr> el;
680    if ( !clearFirst )
681        el = entries();
682
683    foreach( const query_ptr& query, queries )
684    {
685        plentry_ptr e( new PlaylistEntry() );
686        e->setGuid( uuid() );
687
688        e->setDuration( query->track()->duration() );
689        e->setLastmodified( 0 );
690        QString annotation = "";
691        if ( !query->property( "annotation" ).toString().isEmpty() )
692            annotation = query->property( "annotation" ).toString();
693        e->setAnnotation( annotation ); // FIXME
694        e->setQuery( query );
695
696        el << e;
697    }
698    return el;
699}
700
701
702QList< plentry_ptr >
703Playlist::newEntries( const QList< plentry_ptr >& entries )
704{
705    Q_D( Playlist );
706    QSet<QString> currentguids;
707    foreach( const plentry_ptr& p, d->entries )
708        currentguids.insert( p->guid() ); // could be cached as member?
709
710    // calc list of newly added entries:
711    QList<plentry_ptr> added;
712    foreach( const plentry_ptr& p, entries )
713    {
714        if ( !currentguids.contains( p->guid() ) )
715            added << p;
716    }
717    return added;
718}
719
720
721void
722Playlist::setBusy( bool b )
723{
724    Q_D( Playlist );
725    d->busy = b;
726    emit changed();
727}
728
729
730void
731Playlist::setLoaded( bool b )
732{
733    Q_D( Playlist );
734    d->loaded = b;
735}
736
737
738void
739Playlist::checkRevisionQueue()
740{
741    Q_D( Playlist );
742    if ( !d->revisionQueue.isEmpty() )
743    {
744        RevisionQueueItem item = d->revisionQueue.dequeue();
745
746        if ( item.oldRev != currentrevision() && item.applyToTip )
747        {
748            // this was applied to the then-latest, but the already-running operation changed it so it's out of date now. fix it
749            if ( item.oldRev == item.newRev )
750            {
751                checkRevisionQueue();
752                return;
753            }
754
755            item.oldRev = currentrevision();
756        }
757        createNewRevision( item.newRev, item.oldRev, item.entries );
758    }
759    if ( !d->updateQueue.isEmpty() )
760    {
761        RevisionQueueItem item = d->updateQueue.dequeue();
762
763        if ( item.oldRev != currentrevision() && item.applyToTip )
764        {
765            // this was applied to the then-latest, but the already-running operation changed it so it's out of date now. fix it
766            if ( item.oldRev == item.newRev )
767            {
768                checkRevisionQueue();
769                return;
770            }
771
772            item.oldRev = currentrevision();
773        }
774        updateEntries( item.newRev, item.oldRev, item.entries );
775    }
776
777    if ( !d->queuedOps.isEmpty() )
778    {
779        _detail::Closure* next = d->queuedOps.dequeue();
780        next->forceInvoke();
781    }
782}
783
784
785void
786Playlist::setWeakSelf( QWeakPointer< Playlist > self )
787{
788    Q_D( Playlist );
789    d->weakSelf = self;
790}
791
792
793Tomahawk::playlistinterface_ptr
794Playlist::playlistInterface()
795{
796    Q_D( Playlist );
797    if ( d->playlistInterface.isNull() )
798    {
799        d->playlistInterface = Tomahawk::playlistinterface_ptr( new Tomahawk::PlaylistPlaylistInterface( this ) );
800    }
801
802    return d->playlistInterface;
803}
804
805
806QString
807Playlist::currentrevision() const
808{
809    Q_D( const Playlist );
810    return d->currentrevision;
811}
812
813
814QString
815Playlist::title() const
816{
817    Q_D( const Playlist );
818    return d->title;
819}
820
821
822QString
823Playlist::info() const
824{
825    Q_D( const Playlist );
826    return d->info;
827}
828
829
830QString
831Playlist::creator() const
832{
833    Q_D( const Playlist );
834    return d->creator;
835}
836
837
838QString
839Playlist::guid() const
840{
841    Q_D( const Playlist );
842    return d->guid;
843}
844
845
846bool
847Playlist::shared() const
848{
849    Q_D( const Playlist );
850    return d->shared;
851}
852
853
854unsigned int
855Playlist::lastmodified() const
856{
857    Q_D( const Playlist );
858    return d->lastmodified;
859}
860
861
862uint
863Playlist::createdOn() const
864{
865    Q_D( const Playlist );
866    return d->createdOn;
867}
868
869
870bool
871Playlist::busy() const
872{
873    Q_D( const Playlist );
874    return d->busy;
875}
876
877
878bool
879Playlist::loaded() const
880{
881    Q_D( const Playlist );
882    return d->loaded;
883}
884
885
886const QList<plentry_ptr>&
887Playlist::entries()
888{
889    Q_D( const Playlist );
890    return d->entries;
891}
892
893
894void
895Playlist::setCurrentrevision( const QString& s )
896{
897    Q_D( Playlist );
898    d->currentrevision = s;
899}
900
901
902void
903Playlist::setInfo( const QString& s )
904{
905    Q_D( Playlist );
906    d->info = s;
907}
908
909
910void
911Playlist::setCreator( const QString& s )
912{
913    Q_D( Playlist );
914    d->creator = s;
915}
916
917
918void
919Playlist::setGuid( const QString& s )
920{
921    Q_D( Playlist );
922    d->guid = s;
923}
924
925
926void
927Playlist::setShared( bool b )
928{
929    Q_D( Playlist );
930    d->shared = b;
931}
932
933
934void
935Playlist::setCreatedOn( uint createdOn )
936{
937    Q_D( Playlist );
938    d->createdOn = createdOn;
939}
940
941
942QList<PlaylistUpdaterInterface *>
943Playlist::updaters() const
944{
945    Q_D( const Playlist );
946    return d->updaters;
947}
948
949
950void
951PlaylistRemovalHandler::remove( const playlist_ptr& playlist )
952{
953    playlist->removeFromDatabase();
954}
955
956
957PlaylistRemovalHandler::PlaylistRemovalHandler()
958{
959}