1
0
mirror of https://github.com/tomahawk-player/tomahawk.git synced 2025-04-21 00:12:06 +02:00

Enable spotify subscriptions

This commit is contained in:
Hugo Lindström 2012-07-15 19:07:33 +02:00
parent 82ac16feb4
commit a1c8b326ab
6 changed files with 333 additions and 36 deletions

View File

@ -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<PlaylistUpdaterInterface*> 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<PlaylistUpdaterInterface*> 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 );
}

View File

@ -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 <here>( 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<SpotifyAccountConfig> m_configWidget;
QWeakPointer<QWidget> m_aboutWidget;
QWeakPointer<ScriptResolver> m_spotifyResolver;

View File

@ -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 )

View File

@ -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;

View File

@ -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;
}

View File

@ -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 <QObject>
#include <QSet>
#include <QtCore/QStringList>
@ -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;
};