diff --git a/src/libtomahawk/accounts/spotify/SpotifyAccount.cpp b/src/libtomahawk/accounts/spotify/SpotifyAccount.cpp index f37c22645..2d9c36219 100644 --- a/src/libtomahawk/accounts/spotify/SpotifyAccount.cpp +++ b/src/libtomahawk/accounts/spotify/SpotifyAccount.cpp @@ -62,6 +62,7 @@ static QString s_resolverId = "spotify-linux-x86"; static QString s_resolverId = "spotify-unknown"; #endif + Account* SpotifyAccountFactory::createAccount( const QString& accountId ) { @@ -79,6 +80,14 @@ SpotifyAccountFactory::icon() const } +SpotifyAccount* SpotifyAccount::s_instance = 0; + +SpotifyAccount* +SpotifyAccount::instance() +{ + return s_instance; +} + SpotifyAccount::SpotifyAccount( const QString& accountId ) : CustomAtticaAccount( accountId ) , m_preventEnabling( false ) @@ -424,34 +433,171 @@ SpotifyAccount::aboutToShow( QAction* action, const playlist_ptr& playlist ) // If it's not being synced, allow the option to sync bool found = false; + bool canSubscribe = false; + bool isSubscribed = false; bool manuallyDisabled = false; + bool sync = false; + action->setVisible( true ); + QList updaters = playlist->updaters(); foreach ( PlaylistUpdaterInterface* updater, updaters ) { if ( SpotifyPlaylistUpdater* spotifyUpdater = qobject_cast< SpotifyPlaylistUpdater* >( updater ) ) { found = true; - if ( !spotifyUpdater->sync() ) + + if( spotifyUpdater->canSubscribe() ) + canSubscribe = true; + + isSubscribed = spotifyUpdater->subscribed(); + + if ( !spotifyUpdater->canSubscribe() && !spotifyUpdater->sync() ) manuallyDisabled = true; + if( spotifyUpdater->sync() ) + sync = true; + } } - if ( !found ) + if( action->data().toString() == "sync" ) { - action->setText( tr( "Sync with Spotify" ) ); + + if ( !found ) + { + action->setText( tr( "Sync with Spotify" ) ); + } + else if ( manuallyDisabled ) + { + action->setText( tr( "Re-enable syncing with Spotify" ) ); + } + else + { + // We dont want to sync a subscribeable playlist but if a playlist isnt + // collaborative, he will loose his changes on next update, thus, + // we create a new copy of it + if( canSubscribe ) + action->setText( tr( "Create subscribed copy and sync with Spotify") ); + else if( sync ) + action->setText( tr( "Stop syncing with Spotify" ) ); + else + action->setVisible( false ); + } } - else if ( manuallyDisabled ) + + // User can sync or subscribe on playlist. + // Sync means creating a new copy of it, subscribe is listening on changes from owner + if( action->data().toString() == "subscribe" ) { - action->setText( tr( "Re-enable syncing with Spotify" ) ); - } - else - { - action->setText( tr( "Stop syncing with Spotify" ) ); + if( found && canSubscribe ) + { + if ( canSubscribe && !isSubscribed ) + { + action->setText( tr( "Subscribe with Spotify" ) ); + } + else if ( manuallyDisabled ) + { + action->setText( tr( "Re-enable subscribing with Spotify" ) ); + } + else if( isSubscribed ) + { + action->setText( tr( "Stop subscribing with Spotify" ) ); + } + else + { + // Hide the action, we dont have this option on the playlist + action->setVisible(false); + } + }else + action->setVisible( false ); } } +void +SpotifyAccount::subscribeActionTriggered( bool checked ) +{ + Q_UNUSED( checked ); + QAction* action = qobject_cast< QAction* >( sender() ); + if ( !action || !m_customActions.contains( action ) ) + return; + + const playlist_ptr playlist = action->property( "payload" ).value< playlist_ptr >(); + if ( playlist.isNull() ) + { + qWarning() << "Got context menu spotify sync action triggered, but invalid playlist payload!"; + Q_ASSERT( false ); + return; + } + + SpotifyPlaylistUpdater* updater = 0; + QList updaters = playlist->updaters(); + foreach ( PlaylistUpdaterInterface* u, updaters ) + { + if ( SpotifyPlaylistUpdater* spotifyUpdater = qobject_cast< SpotifyPlaylistUpdater* >( u ) ) + { + updater = spotifyUpdater; + } + } + + if ( !updater ) + { + qDebug() << "Errr GOT NO UPDATER! uho What to do??"; + JobStatusView::instance()->model()->addJob( new ErrorStatusMessage( QString( "Spotify: Failed to get SpotifyID from playlist" ) ) ); + } + else + { + + SpotifyPlaylistInfo* info = 0; + foreach ( SpotifyPlaylistInfo* ifo, m_allSpotifyPlaylists ) + { + if ( ifo->plid == updater->spotifyId() ) + { + info = ifo; + break; + } + } + + // When we unsubscribe, all playlists is resent + // and we will could loose the SpotifyPlaylistInfo, but all we really need is the id + if ( !updater->spotifyId().isEmpty() ) + { + if( !info ) + { + info = new SpotifyPlaylistInfo( playlist->title() + , updater->spotifyId() + , updater->spotifyId() + , false + , false + ); + + registerPlaylistInfo( info ); + } + + info->subscribed = !updater->subscribed(); + + QVariantMap msg; + msg[ "_msgtype" ] = "setSubscription"; + msg[ "subscribe" ] = info->subscribed; + msg[ "playlistid" ] = info->plid; + + + sendMessage( msg, this ); + + updater->setSync( !updater->sync() ); + updater->setSubscribed( !updater->subscribed() ); + info->sync = !updater->sync(); + info->subscribed = !updater->subscribed(); + + + } + else + { + qDebug() << "oh no! DONT HAVE ANY INFO NOR UPDATER!!"; + } + + } +} void SpotifyAccount::syncActionTriggered( bool checked ) { @@ -495,6 +641,22 @@ SpotifyAccount::syncActionTriggered( bool checked ) const QString qid = sendMessage( msg, this, "playlistCreated" ); m_waitingForCreateReply[ qid ] = playlist; } + else if ( updater->canSubscribe() ) + { + QVariantMap msg; + msg[ "_msgtype" ] = "createPlaylist"; + msg[ "sync" ] = true; + msg[ "title" ] = "Copy of " + playlist->title(); + + QList< query_ptr > queries; + foreach ( const plentry_ptr& ple, playlist->entries() ) + queries << ple->query(); + QVariantList tracks = SpotifyPlaylistUpdater::queriesToVariant( queries ); + msg[ "tracks" ] = tracks; + + const QString qid = sendMessage( msg, this, "playlistCopyCreated" ); + m_waitingForCreateReply[ qid ] = playlist; + } else { SpotifyPlaylistInfo* info = 0; @@ -584,13 +746,15 @@ SpotifyAccount::resolverMessage( const QString &msgType, const QVariantMap &msg const QString plid = plMap.value( "id" ).toString(); const QString revid = plMap.value( "revid" ).toString(); const bool sync = plMap.value( "sync" ).toBool(); + const bool subscribed = plMap.value( "subscribed" ).toBool(); if ( name.isNull() || plid.isNull() || revid.isNull() ) { qDebug() << "Did not get name and plid and revid for spotify playlist:" << name << plid << revid << plMap; continue; } - m_allSpotifyPlaylists << new SpotifyPlaylistInfo( name, plid, revid, sync ); + + m_allSpotifyPlaylists << new SpotifyPlaylistInfo( name, plid, revid, sync, subscribed ); } if ( !m_configWidget.isNull() ) @@ -714,8 +878,10 @@ SpotifyAccount::resolverMessage( const QString &msgType, const QVariantMap &msg m_loggedIn = success; if ( success ) + { createActions(); - + s_instance = this; + } configurationWidget(); // ensure it's created so we can set the login button if ( m_configWidget.data() ) { @@ -741,7 +907,10 @@ SpotifyAccount::resolverMessage( const QString &msgType, const QVariantMap &msg qDebug() << "Got status message with login info:" << loggedIn << username; if ( !loggedIn || username.isEmpty() || credentials().value( "username").toString() != username ) + { m_loggedIn = false; + s_instance = 0; + } QVariantMap msg; msg[ "_msgtype" ] = "status"; @@ -896,6 +1065,7 @@ SpotifyAccount::logout() QVariantMap msg; msg[ "_msgtype" ] = "logout"; m_spotifyResolver.data()->sendMessage( msg ); + s_instance = 0; } @@ -961,7 +1131,35 @@ SpotifyAccount::startPlaylistSyncWithPlaylist( const QString& msgType, const QVa } } +void +SpotifyAccount::playlistCopyCreated( const QString& msgType, const QVariantMap& msg, const QVariant& ) +{ + Q_UNUSED( msgType ); + qDebug() << Q_FUNC_INFO << "Got response from our createCopyPlaylist command, now creating updater and attaching"; + const bool success = msg.value( "success" ).toBool(); + + if ( !success ) + { + qWarning() << "Got FAILED return code from spotify resolver createPlaylist command, aborting sync"; + return; + } + + const QString id = msg.value( "playlistid" ).toString(); + const QString revid = msg.value( "playlistid" ).toString(); + const QString qid = msg.value( "qid" ).toString(); + const QString title = msg.value( "playlistname" ).toString(); + + qDebug() << msg; + if ( !m_waitingForCreateReply.contains( qid ) ) + { + qWarning() << "Got a createPlaylist reply for a playlist/qid we were not waiting for :-/ " << qid << m_waitingForCreateReply; + return; + } + + SpotifyPlaylistInfo *info = new SpotifyPlaylistInfo( title, id, revid, true, false ); + startPlaylistSync( info ); +} void SpotifyAccount::playlistCreated( const QString& msgType, const QVariantMap& msg, const QVariant& ) { @@ -1020,7 +1218,17 @@ SpotifyAccount::registerUpdaterForPlaylist( const QString& plId, SpotifyPlaylist m_updaters[ plId ] = updater; } +void +SpotifyAccount::registerPlaylistInfo( const QString& name, const QString& plid, const QString &revid, const bool sync, const bool subscribed ) +{ + m_allSpotifyPlaylists << new SpotifyPlaylistInfo( name, plid, revid, sync, subscribed ); +} +void +SpotifyAccount::registerPlaylistInfo( SpotifyPlaylistInfo* info ) +{ + m_allSpotifyPlaylists << info; +} void SpotifyAccount::unregisterUpdater( const QString& plid ) { @@ -1055,6 +1263,7 @@ SpotifyAccount::stopPlaylistSync( SpotifyPlaylistInfo* playlist, bool forceDontD if ( m_updaters.contains( playlist->plid ) ) { + SpotifyPlaylistUpdater* updater = m_updaters[ playlist->plid ]; updater->setSync( false ); @@ -1105,11 +1314,19 @@ SpotifyAccount::createActions() if ( !m_customActions.isEmpty() ) return; - QAction* action = new QAction( 0 ); - action->setIcon( QIcon( RESPATH "images/spotify-logo.png" ) ); - connect( action, SIGNAL( triggered( bool ) ), this, SLOT( syncActionTriggered( bool ) ) ); - ActionCollection::instance()->addAction( ActionCollection::LocalPlaylists, action, this ); - m_customActions.append( action ); + QAction* syncAction = new QAction( 0 ); + syncAction->setIcon( QIcon( RESPATH "images/spotify-logo.png" ) ); + connect( syncAction, SIGNAL( triggered( bool ) ), this, SLOT( syncActionTriggered( bool ) ) ); + ActionCollection::instance()->addAction( ActionCollection::LocalPlaylists, syncAction, this ); + syncAction->setData( QString( "sync" ) ); + m_customActions.append( syncAction ); + + QAction* subscribeAction = new QAction( 0 ); + subscribeAction->setIcon( QIcon( RESPATH "images/spotify-logo.png" ) ); + connect( subscribeAction, SIGNAL( triggered( bool ) ), this, SLOT( subscribeActionTriggered( bool ) ) ); + ActionCollection::instance()->addAction( ActionCollection::LocalPlaylists, subscribeAction, this ); + subscribeAction->setData( "subscribe" ); + m_customActions.append( subscribeAction ); } diff --git a/src/libtomahawk/accounts/spotify/SpotifyAccount.h b/src/libtomahawk/accounts/spotify/SpotifyAccount.h index 4347d7bb5..14cb239b3 100644 --- a/src/libtomahawk/accounts/spotify/SpotifyAccount.h +++ b/src/libtomahawk/accounts/spotify/SpotifyAccount.h @@ -48,11 +48,11 @@ class SpotifyAccountConfig; // metadata for a playlist struct SpotifyPlaylistInfo { QString name, plid, revid; - bool sync, changed; + bool sync, subscribed, changed; - SpotifyPlaylistInfo( const QString& nname, const QString& pid, const QString& rrevid, bool ssync ) - : name( nname ), plid( pid ), revid( rrevid ), sync( ssync ), changed( false ) {} + SpotifyPlaylistInfo( const QString& nname, const QString& pid, const QString& rrevid, bool ssync, bool ssubscribed ) + : name( nname ), plid( pid ), revid( rrevid ), sync( ssync ), subscribed( ssubscribed ), changed( false ) {} SpotifyPlaylistInfo() : sync( false ), changed( false ) {} }; @@ -83,7 +83,7 @@ public: SpotifyAccount( const QString& accountId ); SpotifyAccount( const QString& accountId, const QString& path ); virtual ~SpotifyAccount(); - + static SpotifyAccount* instance(); virtual QPixmap icon() const; virtual QWidget* configurationWidget(); virtual QWidget* aboutWidget(); @@ -101,6 +101,8 @@ public: void registerUpdaterForPlaylist( const QString& plId, SpotifyPlaylistUpdater* updater ); + void registerPlaylistInfo( const QString& name, const QString& plid, const QString &revid, const bool sync, const bool subscribed ); + void registerPlaylistInfo( SpotifyPlaylistInfo* info ); void unregisterUpdater( const QString& plid ); bool deleteOnUnsync() const; @@ -114,6 +116,7 @@ public slots: void aboutToShow( QAction* action, const Tomahawk::playlist_ptr& playlist ); void syncActionTriggered( bool ); + void subscribeActionTriggered( bool ); void atticaLoaded(Attica::Content::List); private slots: @@ -129,7 +132,7 @@ private slots: // void ( const QString& msgType, const QVariantMap& msg, const QVariant& extraData ); void startPlaylistSyncWithPlaylist( const QString& msgType, const QVariantMap& msg, const QVariant& extraData ); void playlistCreated( const QString& msgType, const QVariantMap& msg, const QVariant& extraData ); - + void playlistCopyCreated( const QString& msgType, const QVariantMap& msg, const QVariant& extraData ); void delayedInit(); void hookupAfterDeletion( bool autoEnable ); @@ -151,6 +154,8 @@ private: void createActions(); void removeActions(); + static SpotifyAccount* s_instance; + QWeakPointer m_configWidget; QWeakPointer m_aboutWidget; QWeakPointer m_spotifyResolver; diff --git a/src/libtomahawk/accounts/spotify/SpotifyPlaylistUpdater.cpp b/src/libtomahawk/accounts/spotify/SpotifyPlaylistUpdater.cpp index 7e5c2d236..a13a9eeb8 100644 --- a/src/libtomahawk/accounts/spotify/SpotifyPlaylistUpdater.cpp +++ b/src/libtomahawk/accounts/spotify/SpotifyPlaylistUpdater.cpp @@ -57,10 +57,14 @@ SpotifyUpdaterFactory::create( const Tomahawk::playlist_ptr& pl, const QVariantH const QString spotifyId = settings.value( "spotifyId" ).toString(); const QString latestRev = settings.value( "latestrev" ).toString(); const bool sync = settings.value( "sync" ).toBool(); + const bool canSubscribe = settings.value( "canSubscribe" ).toBool(); + const bool isSubscribed = settings.value( "subscribed" ).toBool(); Q_ASSERT( !spotifyId.isEmpty() ); SpotifyPlaylistUpdater* updater = new SpotifyPlaylistUpdater( m_account.data(), latestRev, spotifyId, pl ); updater->setSync( sync ); + updater->setCanSubscribe( canSubscribe ); + updater->setSubscribed( isSubscribed ); m_account.data()->registerUpdaterForPlaylist( spotifyId, updater ); return updater; @@ -74,6 +78,8 @@ SpotifyPlaylistUpdater::SpotifyPlaylistUpdater( SpotifyAccount* acct, const QStr , m_spotifyId( spotifyId ) , m_blockUpdatesForNextRevision( false ) , m_sync( false ) + , m_canSubscribe( false ) + , m_subscribed( false ) { init(); } @@ -177,6 +183,8 @@ SpotifyPlaylistUpdater::saveToSettings() s[ "latestrev" ] = m_latestRev; s[ "sync" ] = m_sync; + s[ "canSubscribe" ] = m_canSubscribe; + s[ "subscribed" ] = m_subscribed; s[ "spotifyId" ] = m_spotifyId; saveSettings( s ); @@ -227,6 +235,46 @@ SpotifyPlaylistUpdater::sync() const return m_sync; } +void +SpotifyPlaylistUpdater::setSubscribed( bool subscribed ) +{ + if ( m_subscribed == subscribed ) + return; + + m_subscribed = subscribed; + setSync( subscribed ); + saveToSettings(); + emit changed(); +} + + +bool +SpotifyPlaylistUpdater::subscribed() const +{ + return m_subscribed; +} + + +void +SpotifyPlaylistUpdater::setCanSubscribe( bool canSubscribe ) +{ + + if ( m_canSubscribe == canSubscribe ) + return; + + m_canSubscribe = canSubscribe; + + saveToSettings(); + emit changed(); +} + + +bool +SpotifyPlaylistUpdater::canSubscribe() const +{ + return m_canSubscribe; +} + void SpotifyPlaylistUpdater::spotifyTracksAdded( const QVariantList& tracks, const QString& startPosId, const QString& newRev, const QString& oldRev ) diff --git a/src/libtomahawk/accounts/spotify/SpotifyPlaylistUpdater.h b/src/libtomahawk/accounts/spotify/SpotifyPlaylistUpdater.h index 3ac899a1d..c2ae2f5fa 100644 --- a/src/libtomahawk/accounts/spotify/SpotifyPlaylistUpdater.h +++ b/src/libtomahawk/accounts/spotify/SpotifyPlaylistUpdater.h @@ -53,7 +53,10 @@ public: bool sync() const; void setSync( bool sync ); - + bool subscribed() const; + void setSubscribed( bool subscribed ); + bool canSubscribe() const; + void setCanSubscribe( bool canSub ); QString spotifyId() const { return m_spotifyId; } void remove( bool askToDeletePlaylist = true ); @@ -99,7 +102,8 @@ private: bool m_blockUpdatesForNextRevision; bool m_sync; - + bool m_subscribed; + bool m_canSubscribe; QQueue<_detail::Closure*> m_queuedOps; #ifndef ENABLE_HEADLESS static QPixmap* s_typePixmap; diff --git a/src/libtomahawk/utils/SpotifyParser.cpp b/src/libtomahawk/utils/SpotifyParser.cpp index 7c37e1ed2..33a3efdf7 100644 --- a/src/libtomahawk/utils/SpotifyParser.cpp +++ b/src/libtomahawk/utils/SpotifyParser.cpp @@ -97,31 +97,31 @@ void SpotifyParser::lookupSpotifyBrowse( const QString& linkRaw ) { tLog() << "Parsing Spotify Browse URI:" << linkRaw; - QString browseUri = linkRaw; - if ( browseUri.contains( "open.spotify.com/" ) ) // convert to a URI + m_browseUri = linkRaw; + if ( m_browseUri.contains( "open.spotify.com/" ) ) // convert to a URI { - browseUri.replace( "http://open.spotify.com/", "" ); - browseUri.replace( "/", ":" ); - browseUri = "spotify:" + browseUri; + m_browseUri.replace( "http://open.spotify.com/", "" ); + m_browseUri.replace( "/", ":" ); + m_browseUri = "spotify:" + m_browseUri; } DropJob::DropType type; - if ( browseUri.contains( "spotify:user" ) ) + if ( m_browseUri.contains( "spotify:user" ) ) type = DropJob::Playlist; - if ( browseUri.contains( "spotify:artist" ) ) + if ( m_browseUri.contains( "spotify:artist" ) ) type = DropJob::Artist; - if ( browseUri.contains( "spotify:album" ) ) + if ( m_browseUri.contains( "spotify:album" ) ) type = DropJob::Album; - if ( browseUri.contains( "spotify:track" ) ) + if ( m_browseUri.contains( "spotify:track" ) ) type = DropJob::Track; QUrl url; if( type != DropJob::Artist ) - url = QUrl( QString( SPOTIFY_PLAYLIST_API_URL "/browse/%1" ).arg( browseUri ) ); + url = QUrl( QString( SPOTIFY_PLAYLIST_API_URL "/browse/%1" ).arg( m_browseUri ) ); else - url = QUrl( QString( SPOTIFY_PLAYLIST_API_URL "/browse/%1/%2" ).arg( browseUri ) + url = QUrl( QString( SPOTIFY_PLAYLIST_API_URL "/browse/%1/%2" ).arg( m_browseUri ) .arg ( m_limit ) ); tDebug() << "Looking up URL..." << url.toString(); @@ -304,6 +304,7 @@ SpotifyParser::checkBrowseFinished() if ( m_createNewPlaylist && !m_tracks.isEmpty() ) { + m_playlist = Playlist::create( SourceList::instance()->getLocal(), uuid(), m_title, @@ -312,6 +313,25 @@ SpotifyParser::checkBrowseFinished() false, m_tracks ); connect( m_playlist.data(), SIGNAL( revisionLoaded( Tomahawk::PlaylistRevision ) ), this, SLOT( playlistCreated() ) ); + + if( Accounts::SpotifyAccount::instance() != 0 ) + { + SpotifyPlaylistUpdater* updater = new SpotifyPlaylistUpdater( + Accounts::SpotifyAccount::instance(), m_playlist->currentrevision(), m_browseUri, m_playlist ); + + QVariantHash creds = Accounts::SpotifyAccount::instance()->credentials(); + + // If the user isnt dropping a playlist the he owns, its subscribeable + if( !m_browseUri.contains( creds.value( "username" ).toString() ) ) + updater->setCanSubscribe( true ); + + updater->setSubscribed( false ); + updater->setSync( false ); + + // Just register the infos + Accounts::SpotifyAccount::instance()->registerPlaylistInfo( m_title, m_browseUri, m_browseUri, false, false ); + Accounts::SpotifyAccount::instance()->registerUpdaterForPlaylist( m_browseUri, updater ); + } return; } diff --git a/src/libtomahawk/utils/SpotifyParser.h b/src/libtomahawk/utils/SpotifyParser.h index 7f5c65b61..1bde874e3 100644 --- a/src/libtomahawk/utils/SpotifyParser.h +++ b/src/libtomahawk/utils/SpotifyParser.h @@ -24,7 +24,8 @@ #include "Typedefs.h" #include "Query.h" #include "jobview/JobStatusItem.h" - +#include "accounts/spotify/SpotifyPlaylistUpdater.h" +#include "accounts/spotify/SpotifyAccount.h" #include #include #include @@ -38,6 +39,8 @@ */ class QNetworkReply; +class SpotifyAccount; +class SpotifyPlaylistUpdater; namespace Tomahawk { @@ -84,7 +87,7 @@ private: QString m_title, m_info, m_creator; Tomahawk::playlist_ptr m_playlist; DropJobNotifier* m_browseJob; - + QString m_browseUri; static QPixmap* s_pixmap; };