mirror of
https://github.com/tomahawk-player/tomahawk.git
synced 2025-01-19 15:37:01 +01:00
Merge branch 'spotifyplaylists'
This commit is contained in:
commit
701ef5a69d
@ -1,6 +1,6 @@
|
||||
#!/usr/bin/python
|
||||
|
||||
# This file is part of Tomahawk.
|
||||
<<<<<<< HEAD
|
||||
# This file is part of Tomahawk.
|
||||
# It was inspired in large part by the macdeploy script in Clementine.
|
||||
#
|
||||
# Clementine is free software: you can redistribute it and/or modify
|
||||
|
BIN
data/images/spotifycore-logo.png
Normal file
BIN
data/images/spotifycore-logo.png
Normal file
Binary file not shown.
After Width: | Height: | Size: 6.9 KiB |
@ -136,6 +136,7 @@
|
||||
<file>data/images/rdio.png</file>
|
||||
<file>data/images/grooveshark.png</file>
|
||||
<file>data/images/lastfm-icon.png</file>
|
||||
<file>data/images/spotifycore-logo.png</file>
|
||||
<file>data/images/playlist-header-tiled.png</file>
|
||||
<file>data/images/share.png</file>
|
||||
<file>data/sql/dbmigrate-27_to_28.sql</file>
|
||||
|
@ -75,6 +75,8 @@ SET( tomahawkSourcesGui ${tomahawkSourcesGui}
|
||||
accounts/lastfm/lastfmplugin.cpp
|
||||
|
||||
accounts/spotify/SpotifyAccount.cpp
|
||||
accounts/spotify/SpotifyAccountConfig.cpp
|
||||
accounts/spotify/SpotifyPlaylistUpdater.cpp
|
||||
|
||||
tomahawktrayicon.cpp
|
||||
audiocontrols.cpp
|
||||
@ -101,6 +103,7 @@ SET( tomahawkUI ${tomahawkUI}
|
||||
proxydialog.ui
|
||||
|
||||
accounts/lastfm/LastFmConfig.ui
|
||||
accounts/spotify/SpotifyAccountConfig.ui
|
||||
|
||||
audiocontrols.ui
|
||||
LoadXSPFDialog.ui
|
||||
|
@ -17,6 +17,7 @@
|
||||
*/
|
||||
|
||||
#include "LoadXSPFDialog.h"
|
||||
#include "tomahawksettings.h"
|
||||
|
||||
#include "ui_LoadXSPFDialog.h"
|
||||
#include <QFileDialog>
|
||||
@ -44,7 +45,11 @@ LoadXSPFDialog::~LoadXSPFDialog()
|
||||
void
|
||||
LoadXSPFDialog::getLocalFile()
|
||||
{
|
||||
QString url = QFileDialog::getOpenFileName( this, tr( "Load XSPF File" ), QDir::homePath(), tr( "XSPF Files (*.xspf)" ) );
|
||||
const QString path = TomahawkSettings::instance()->importXspfPath();
|
||||
QString url = QFileDialog::getOpenFileName( this, tr( "Load XSPF File" ), path, tr( "XSPF Files (*.xspf)" ) );
|
||||
if ( !url.isEmpty() )
|
||||
TomahawkSettings::instance()->setImportXspfPath( QFileInfo( url ).absoluteDir().absolutePath() );
|
||||
|
||||
m_ui->lineEdit->setText( url );
|
||||
}
|
||||
|
||||
|
@ -95,4 +95,3 @@ private:
|
||||
}
|
||||
|
||||
#endif // LASTFMPLUGIN_H
|
||||
|
||||
|
@ -19,15 +19,27 @@
|
||||
|
||||
#include "SpotifyAccount.h"
|
||||
#include "playlist.h"
|
||||
#include "utils/tomahawkutils.h"
|
||||
#include "playlist/PlaylistUpdaterInterface.h"
|
||||
#include "sourcelist.h"
|
||||
#include "SpotifyAccountConfig.h"
|
||||
#include "SpotifyPlaylistUpdater.h"
|
||||
#include "resolvers/scriptresolver.h"
|
||||
#include "utils/tomahawkutils.h"
|
||||
#include "actioncollection.h"
|
||||
|
||||
#ifndef ENABLE_HEADLESS
|
||||
#include "jobview/JobStatusView.h"
|
||||
#include "jobview/JobStatusModel.h"
|
||||
#include "jobview/ErrorStatusMessage.h"
|
||||
#endif
|
||||
|
||||
#include <QPixmap>
|
||||
#include <QAction>
|
||||
|
||||
using namespace Tomahawk;
|
||||
using namespace Accounts;
|
||||
|
||||
|
||||
static QPixmap* s_icon = 0;
|
||||
|
||||
Account*
|
||||
@ -65,14 +77,330 @@ SpotifyAccountFactory::icon() const
|
||||
SpotifyAccount::SpotifyAccount( const QString& accountId )
|
||||
: ResolverAccount( accountId )
|
||||
{
|
||||
|
||||
init();
|
||||
}
|
||||
|
||||
|
||||
SpotifyAccount::SpotifyAccount( const QString& accountId, const QString& path )
|
||||
: ResolverAccount( accountId, path )
|
||||
{
|
||||
init();
|
||||
}
|
||||
|
||||
|
||||
SpotifyAccount::~SpotifyAccount()
|
||||
{
|
||||
clearUser();
|
||||
}
|
||||
|
||||
|
||||
void
|
||||
SpotifyAccount::init()
|
||||
{
|
||||
qRegisterMetaType< Tomahawk::Accounts::SpotifyPlaylistInfo* >( "Tomahawk::Accounts::SpotifyPlaylist*" );
|
||||
|
||||
m_spotifyResolver = dynamic_cast< ScriptResolver* >( m_resolver.data() );
|
||||
|
||||
connect( m_spotifyResolver.data(), SIGNAL( customMessage( QString,QVariantMap ) ), this, SLOT( resolverMessage( QString, QVariantMap ) ) );
|
||||
|
||||
const bool hasMigrated = configuration().value( "hasMigrated" ).toBool();
|
||||
if ( !hasMigrated )
|
||||
{
|
||||
qDebug() << "Getting credentials from spotify resolver to migrate to in-app config";
|
||||
QVariantMap msg;
|
||||
msg[ "_msgtype" ] = "getCredentials";
|
||||
m_spotifyResolver.data()->sendMessage( msg );
|
||||
}
|
||||
|
||||
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 );
|
||||
}
|
||||
|
||||
|
||||
void
|
||||
SpotifyAccount::aboutToShow( QAction* action, const playlist_ptr& playlist )
|
||||
{
|
||||
if ( !m_customActions.contains( action ) )
|
||||
return;
|
||||
|
||||
// If it's not being synced, allow the option to sync
|
||||
SpotifyPlaylistUpdater* updater = qobject_cast< SpotifyPlaylistUpdater* >( playlist->updater() );
|
||||
if ( !updater || !updater->sync() )
|
||||
{
|
||||
action->setText( tr( "Sync with Spotify" ) );
|
||||
}
|
||||
else
|
||||
{
|
||||
action->setText( tr( "Stop syncing with Spotify" ) );
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
void
|
||||
SpotifyAccount::syncActionTriggered( 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 = qobject_cast< SpotifyPlaylistUpdater* >( playlist->updater() );
|
||||
|
||||
if ( !updater )
|
||||
{
|
||||
QVariantMap msg;
|
||||
msg[ "_msgtype" ] = "createPlaylist";
|
||||
msg[ "sync" ] = true;
|
||||
msg[ "title" ] = 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, "playlistCreated" );
|
||||
m_waitingForCreateReply[ qid ] = playlist;
|
||||
}
|
||||
else
|
||||
{
|
||||
SpotifyPlaylistInfo* info = 0;
|
||||
foreach ( SpotifyPlaylistInfo* ifo, m_allSpotifyPlaylists )
|
||||
{
|
||||
if ( ifo->plid == updater->spotifyId() )
|
||||
{
|
||||
info = ifo;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
Q_ASSERT( info );
|
||||
if ( info )
|
||||
info->sync = !updater->sync();
|
||||
|
||||
if ( m_configWidget.data() )
|
||||
m_configWidget.data()->setPlaylists( m_allSpotifyPlaylists );
|
||||
|
||||
if ( !updater->sync() )
|
||||
{
|
||||
startPlaylistSync( info );
|
||||
}
|
||||
else
|
||||
{
|
||||
stopPlaylistSync( info, true );
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
void
|
||||
SpotifyAccount::resolverMessage( const QString &msgType, const QVariantMap &msg )
|
||||
{
|
||||
if ( msgType == "credentials" )
|
||||
{
|
||||
QVariantHash creds = credentials();
|
||||
creds[ "username" ] = msg.value( "username" );
|
||||
creds[ "password" ] = msg.value( "password" );
|
||||
creds[ "highQuality" ] = msg.value( "highQuality" );
|
||||
setCredentials( creds );
|
||||
|
||||
qDebug() << "Set creds:" << creds.value( "username" ) << creds.value( "password" ) << msg.value( "username" ) << msg.value( "password" );
|
||||
|
||||
QVariantHash config = configuration();
|
||||
config[ "hasMigrated" ] = true;
|
||||
setConfiguration( config );
|
||||
sync();
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
|
||||
const QString qid = msg.value( "qid" ).toString();
|
||||
if ( m_qidToSlotMap.contains( qid ) )
|
||||
{
|
||||
QObject* receiver = m_qidToSlotMap[ qid ].first;
|
||||
QString slot = m_qidToSlotMap[ qid ].second;
|
||||
m_qidToSlotMap.remove( qid );
|
||||
|
||||
QMetaObject::invokeMethod( receiver, slot.toLatin1(), Q_ARG( QString, msgType ), Q_ARG( QVariantMap, msg ) );
|
||||
}
|
||||
else if ( msgType == "allPlaylists" )
|
||||
{
|
||||
const QVariantList playlists = msg.value( "playlists" ).toList();
|
||||
qDeleteAll( m_allSpotifyPlaylists );
|
||||
m_allSpotifyPlaylists.clear();
|
||||
|
||||
foreach ( const QVariant& playlist, playlists )
|
||||
{
|
||||
const QVariantMap plMap = playlist.toMap();
|
||||
const QString name = plMap.value( "name" ).toString();
|
||||
const QString plid = plMap.value( "id" ).toString();
|
||||
const QString revid = plMap.value( "revid" ).toString();
|
||||
const bool sync = plMap.value( "sync" ).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 );
|
||||
}
|
||||
|
||||
if ( !m_configWidget.isNull() )
|
||||
{
|
||||
m_configWidget.data()->setPlaylists( m_allSpotifyPlaylists );
|
||||
}
|
||||
}
|
||||
else if ( msgType == "tracksAdded" )
|
||||
{
|
||||
const QString plid = msg.value( "playlistid" ).toString();
|
||||
// We should already be syncing this playlist if we get updates for it
|
||||
// Q_ASSERT( m_updaters.contains( plid ) );
|
||||
|
||||
if ( !m_updaters.contains( plid ) )
|
||||
return;
|
||||
|
||||
SpotifyPlaylistUpdater* updater = m_updaters[ plid ];
|
||||
Q_ASSERT( updater->sync() );
|
||||
|
||||
const QString startPos = msg.value( "startPosition" ).toString();
|
||||
const QVariantList tracksList = msg.value( "tracks" ).toList();
|
||||
const QString newRev = msg.value( "revid" ).toString();
|
||||
const QString oldRev = msg.value( "oldRev" ).toString();
|
||||
|
||||
updater->spotifyTracksAdded( tracksList, startPos, newRev, oldRev );
|
||||
}
|
||||
else if ( msgType == "tracksRemoved" )
|
||||
{
|
||||
const QString plid = msg.value( "playlistid" ).toString();
|
||||
// We should already be syncing this playlist if we get updates for it
|
||||
// Q_ASSERT( m_updaters.contains( plid ) );
|
||||
|
||||
if ( !m_updaters.contains( plid ) )
|
||||
return;
|
||||
|
||||
SpotifyPlaylistUpdater* updater = m_updaters[ plid ];
|
||||
|
||||
// If we're not syncing with this, the resolver is quite misinformed.
|
||||
// Q_ASSERT( updater && updater->sync() );
|
||||
if ( !updater || !updater->sync() )
|
||||
return;
|
||||
|
||||
const QVariantList tracksList = msg.value( "trackPositions" ).toList();
|
||||
const QString newRev = msg.value( "revid" ).toString();
|
||||
const QString oldRev = msg.value( "oldRev" ).toString();
|
||||
|
||||
|
||||
updater->spotifyTracksRemoved( tracksList, newRev, oldRev );
|
||||
}
|
||||
else if ( msgType == "tracksMoved" )
|
||||
{
|
||||
const QString plid = msg.value( "playlistid" ).toString();
|
||||
// We should already be syncing this playlist if we get updates for it
|
||||
Q_ASSERT( m_updaters.contains( plid ) );
|
||||
|
||||
if ( !m_updaters.contains( plid ) )
|
||||
return;
|
||||
|
||||
SpotifyPlaylistUpdater* updater = m_updaters[ plid ];
|
||||
Q_ASSERT( updater->sync() );
|
||||
|
||||
const QString newStartPos = msg.value( "newStartPosition" ).toString();
|
||||
const QVariantList tracksList = msg.value( "tracks" ).toList();
|
||||
const QString newRev = msg.value( "revid" ).toString();
|
||||
const QString oldRev = msg.value( "oldRev" ).toString();
|
||||
|
||||
updater->spotifyTracksMoved( tracksList, newStartPos, newRev, oldRev );
|
||||
}
|
||||
else if( msgType == "playlistRenamed" )
|
||||
{
|
||||
const QString plid = msg.value( "id" ).toString();
|
||||
// We should already be syncing this playlist if we get updates for it
|
||||
//Q_ASSERT( m_updaters.contains( plid ) );
|
||||
|
||||
qDebug() << Q_FUNC_INFO;
|
||||
if ( !m_updaters.contains( plid ) )
|
||||
return;
|
||||
|
||||
SpotifyPlaylistUpdater* updater = m_updaters[ plid ];
|
||||
Q_ASSERT( updater->sync() );
|
||||
|
||||
qDebug() << "Playlist renamed fetched in tomahawk";
|
||||
const QString title = msg.value( "name" ).toString();
|
||||
const QString newRev = msg.value( "revid" ).toString();
|
||||
const QString oldRev = msg.value( "oldRev" ).toString();
|
||||
|
||||
updater->spotifyPlaylistRenamed( title, newRev, oldRev );
|
||||
}
|
||||
else if( msgType == "spotifyError" )
|
||||
{
|
||||
const QString error = msg.value( "msg" ).toString();
|
||||
if( error.isEmpty() )
|
||||
return;
|
||||
|
||||
if( msg.value( "isDebugMsg" ).toBool() )
|
||||
tDebug( LOGVERBOSE ) << "SpotifyResolverError: " << error;
|
||||
else
|
||||
JobStatusView::instance()->model()->addJob( new ErrorStatusMessage( QString( "Spotify: %1" ).arg( error ) ) );
|
||||
}
|
||||
else if( msgType == "userChanged" )
|
||||
{
|
||||
const QString rmsg = msg.value( "msg" ).toString();
|
||||
clearUser();
|
||||
|
||||
if ( m_configWidget.data() )
|
||||
m_configWidget.data()->setPlaylists( QList< SpotifyPlaylistInfo* >() );
|
||||
|
||||
qDebug() << "User changed message from spotify:" << rmsg;
|
||||
}
|
||||
else if ( msgType == "loginResponse" )
|
||||
{
|
||||
QVariantHash creds = credentials();
|
||||
creds[ "username" ] = msg.value( "username" ).toString();
|
||||
creds[ "password" ] = msg.value( "password" ).toString();
|
||||
creds[ "highQuality" ] = msg.value( "highQuality" ).toString();
|
||||
setCredentials( creds );
|
||||
sync();
|
||||
|
||||
configurationWidget(); // ensure it's created so we can set the login button
|
||||
if ( m_configWidget.data() )
|
||||
{
|
||||
const bool success = msg.value( "success" ).toBool();
|
||||
const QString message = msg.value( "message" ).toString();
|
||||
m_configWidget.data()->loginResponse( success, message );
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
void
|
||||
SpotifyAccount::clearUser()
|
||||
{
|
||||
foreach( SpotifyPlaylistUpdater* updater, m_updaters.values() )
|
||||
updater->remove();
|
||||
|
||||
m_updaters.clear();
|
||||
|
||||
qDeleteAll( m_allSpotifyPlaylists );
|
||||
m_allSpotifyPlaylists.clear();
|
||||
|
||||
m_qidToSlotMap.clear();
|
||||
m_waitingForCreateReply.clear();
|
||||
|
||||
foreach( QAction* action, m_customActions )
|
||||
ActionCollection::instance()->removeAction( action );
|
||||
}
|
||||
|
||||
|
||||
@ -86,63 +414,273 @@ SpotifyAccount::icon() const
|
||||
}
|
||||
|
||||
|
||||
void
|
||||
SpotifyAccount::addPlaylist( const QString &qid, const QString& title, QList< Tomahawk::query_ptr > tracks )
|
||||
QWidget*
|
||||
SpotifyAccount::configurationWidget()
|
||||
{
|
||||
/* Sync sync;
|
||||
sync.id_ = qid;
|
||||
int index = m_syncPlaylists.indexOf( sync );
|
||||
|
||||
if( !m_syncPlaylists.contains( sync ) )
|
||||
if ( m_configWidget.isNull() )
|
||||
{
|
||||
qDebug() << Q_FUNC_INFO << "Adding playlist to sync" << qid;
|
||||
playlist_ptr pl;
|
||||
pl = Tomahawk::Playlist::create( SourceList::instance()->getLocal(),
|
||||
uuid(),
|
||||
title,
|
||||
QString(),
|
||||
QString(),
|
||||
false,
|
||||
tracks );
|
||||
sync.playlist = pl;
|
||||
sync.uuid = pl->guid();
|
||||
m_syncPlaylists.append( sync );
|
||||
m_configWidget = QWeakPointer< SpotifyAccountConfig >( new SpotifyAccountConfig( this ) );
|
||||
connect( m_configWidget.data(), SIGNAL( login( QString,QString ) ), this, SLOT( login( QString,QString ) ) );
|
||||
m_configWidget.data()->setPlaylists( m_allSpotifyPlaylists );
|
||||
}
|
||||
|
||||
return static_cast< QWidget* >( m_configWidget.data() );
|
||||
}
|
||||
|
||||
|
||||
void
|
||||
SpotifyAccount::saveConfig()
|
||||
{
|
||||
Q_ASSERT( !m_configWidget.isNull() );
|
||||
if ( m_configWidget.isNull() )
|
||||
return;
|
||||
|
||||
QVariantHash creds = credentials();
|
||||
if ( creds.value( "username" ).toString() != m_configWidget.data()->username() ||
|
||||
creds.value( "password" ).toString() != m_configWidget.data()->password() ||
|
||||
creds.value( "highQuality" ).toBool() != m_configWidget.data()->highQuality() )
|
||||
{
|
||||
creds[ "username" ] = m_configWidget.data()->username();
|
||||
creds[ "password" ] = m_configWidget.data()->password();
|
||||
creds[ "highQuality" ] = m_configWidget.data()->highQuality();
|
||||
setCredentials( creds );
|
||||
|
||||
}
|
||||
|
||||
QVariantHash config = configuration();
|
||||
config[ "deleteOnUnsync" ] = m_configWidget.data()->deleteOnUnsync();
|
||||
setConfiguration( config );
|
||||
|
||||
m_configWidget.data()->saveSettings();
|
||||
foreach ( SpotifyPlaylistInfo* pl, m_allSpotifyPlaylists )
|
||||
{
|
||||
// qDebug() << "Checking changed state:" << pl->changed << pl->name << pl->sync;
|
||||
if ( pl->changed )
|
||||
{
|
||||
pl->changed = false;
|
||||
if ( pl->sync )
|
||||
{
|
||||
// Fetch full playlist contents, then begin the sync
|
||||
startPlaylistSync( pl );
|
||||
}
|
||||
else
|
||||
stopPlaylistSync( pl );
|
||||
}
|
||||
}
|
||||
sync();
|
||||
}
|
||||
|
||||
|
||||
void
|
||||
SpotifyAccount::login( const QString& username, const QString& password )
|
||||
{
|
||||
// Send the result to the resolver
|
||||
QVariantMap msg;
|
||||
msg[ "_msgtype" ] = "login";
|
||||
msg[ "username" ] = username;
|
||||
msg[ "password" ] = password;
|
||||
|
||||
msg[ "highQuality" ] = m_configWidget.data()->highQuality();
|
||||
|
||||
m_spotifyResolver.data()->sendMessage( msg );
|
||||
}
|
||||
|
||||
|
||||
void
|
||||
SpotifyAccount::startPlaylistSync( SpotifyPlaylistInfo* playlist )
|
||||
{
|
||||
if ( !playlist )
|
||||
return;
|
||||
|
||||
QVariantMap msg;
|
||||
msg[ "_msgtype" ] = "getPlaylist";
|
||||
msg[ "playlistid" ] = playlist->plid;
|
||||
msg[ "sync" ] = playlist->sync;
|
||||
|
||||
sendMessage( msg, this, "startPlaylistSyncWithPlaylist" );
|
||||
}
|
||||
|
||||
|
||||
void
|
||||
SpotifyAccount::startPlaylistSyncWithPlaylist( const QString& msgType, const QVariantMap& msg )
|
||||
{
|
||||
Q_UNUSED( msgType );
|
||||
qDebug() << Q_FUNC_INFO << "Got full spotify playlist body, creating a tomahawk playlist and enabling sync!!";
|
||||
const QString id = msg.value( "id" ).toString();
|
||||
const QString name = msg.value( "name" ).toString();
|
||||
const QString revid = msg.value( "revid" ).toString();
|
||||
|
||||
qDebug() << "Starting sync with pl:" << id << name;
|
||||
QVariantList tracks = msg.value( "tracks" ).toList();
|
||||
|
||||
// create a list of query/plentries directly
|
||||
QList< query_ptr > queries = SpotifyPlaylistUpdater::variantToQueries( tracks );
|
||||
|
||||
/**
|
||||
* Begin syncing a playlist. Two options:
|
||||
* 1) This is a playlist that has never been synced to tomahawk. Create a new one
|
||||
* and attach a new SpotifyPlaylistUpdater to it
|
||||
* 2) This was previously synced, and has since been unsynced. THe playlist is still around
|
||||
* with an inactive SpotifyPlaylistUpdater, so just enable it and bring it up to date by merging current with new
|
||||
* TODO: show a warning( "Do you want to overwrite with spotify's version?" )
|
||||
*/
|
||||
if ( m_updaters.contains( id ) )
|
||||
{
|
||||
Q_ASSERT( m_updaters[ id ]->sync() == false ); /// Should have been unchecked/off before
|
||||
m_updaters[ id ]->setSync( true );
|
||||
// m_updaters[ id ]->
|
||||
// TODO
|
||||
}
|
||||
else
|
||||
{
|
||||
playlist_ptr plPtr = Tomahawk::Playlist::create( SourceList::instance()->getLocal(),
|
||||
uuid(),
|
||||
name,
|
||||
QString(),
|
||||
QString(),
|
||||
false,
|
||||
queries );
|
||||
|
||||
qDebug() << Q_FUNC_INFO << "Found playlist";
|
||||
SpotifyPlaylistUpdater* updater = new SpotifyPlaylistUpdater( this, revid, id, plPtr );
|
||||
updater->setSync( true );
|
||||
m_updaters[ id ] = updater;
|
||||
}
|
||||
}
|
||||
|
||||
if ( index != -1 && !tracks.isEmpty())
|
||||
{
|
||||
|
||||
qDebug() << Q_FUNC_INFO << "Got pl" << m_syncPlaylists[ index ].playlist->guid();
|
||||
void
|
||||
SpotifyAccount::playlistCreated( const QString& msgType, const QVariantMap& msg )
|
||||
{
|
||||
Q_UNUSED( msgType );
|
||||
|
||||
QList< query_ptr > currTracks;
|
||||
foreach ( const plentry_ptr ple, m_syncPlaylists[ index ].playlist->entries() )
|
||||
currTracks << ple->query();
|
||||
qDebug() << Q_FUNC_INFO << "Got response from our createPlaylist command, now creating updater and attaching";
|
||||
const bool success = msg.value( "success" ).toBool();
|
||||
|
||||
qDebug() << Q_FUNC_INFO << "tracks" << currTracks;
|
||||
|
||||
bool changed = false;
|
||||
QList< query_ptr > mergedTracks = TomahawkUtils::mergePlaylistChanges( currTracks, tracks, changed );
|
||||
|
||||
if ( changed )
|
||||
{
|
||||
QList<Tomahawk::plentry_ptr> el = m_syncPlaylists[ index ].playlist->entriesFromQueries( mergedTracks, true );
|
||||
m_syncPlaylists[ index ].playlist->createNewRevision( uuid(), m_syncPlaylists[ index ].playlist->currentrevision(), el );
|
||||
}
|
||||
}
|
||||
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();
|
||||
|
||||
if ( !m_waitingForCreateReply.contains( qid ) )
|
||||
{
|
||||
qWarning() << "Got a createPlaylist reply for a playlist/qid we were not waiting for :-/ " << qid << m_waitingForCreateReply;
|
||||
return;
|
||||
}
|
||||
|
||||
playlist_ptr playlist = m_waitingForCreateReply.take( qid );
|
||||
SpotifyPlaylistUpdater* updater = new SpotifyPlaylistUpdater( this, revid, id, playlist );
|
||||
updater->setSync( true );
|
||||
m_updaters[ id ] = updater;
|
||||
}
|
||||
|
||||
|
||||
QString
|
||||
SpotifyAccount::sendMessage( const QVariantMap &m, QObject* obj, const QString& slot )
|
||||
{
|
||||
QVariantMap msg = m;
|
||||
QString qid;
|
||||
|
||||
if ( obj )
|
||||
{
|
||||
qid = QUuid::createUuid().toString().replace( "{", "" ).replace( "}", "" );
|
||||
|
||||
m_qidToSlotMap[ qid ] = qMakePair( obj, slot );
|
||||
msg[ "qid" ] = qid;
|
||||
|
||||
}
|
||||
|
||||
m_spotifyResolver.data()->sendMessage( msg );
|
||||
|
||||
return qid;
|
||||
}
|
||||
|
||||
|
||||
void
|
||||
SpotifyAccount::registerUpdaterForPlaylist( const QString& plId, SpotifyPlaylistUpdater* updater )
|
||||
{
|
||||
m_updaters[ plId ] = updater;
|
||||
}
|
||||
|
||||
|
||||
void
|
||||
SpotifyAccount::unregisterUpdater( const QString& plid )
|
||||
{
|
||||
m_updaters.remove( plid );
|
||||
}
|
||||
|
||||
|
||||
void
|
||||
SpotifyAccount::fetchFullPlaylist( SpotifyPlaylistInfo* playlist )
|
||||
{
|
||||
|
||||
}
|
||||
|
||||
|
||||
bool
|
||||
operator==( Tomahawk::Accounts::SpotifyAccount::Sync one, Tomahawk::Accounts::SpotifyAccount::Sync two )
|
||||
SpotifyAccount::deleteOnUnsync() const
|
||||
{
|
||||
if( one.id_ == two.id_ )
|
||||
return true;
|
||||
return false;
|
||||
return configuration().value( "deleteOnUnsync", false ).toBool();
|
||||
}
|
||||
|
||||
void
|
||||
SpotifyAccount::stopPlaylistSync( SpotifyPlaylistInfo* playlist, bool forceDontDelete )
|
||||
{
|
||||
if ( !playlist )
|
||||
return;
|
||||
|
||||
QVariantMap msg;
|
||||
msg[ "_msgtype" ] = "removeFromSyncList";
|
||||
msg[ "playlistid" ] = playlist->plid;
|
||||
|
||||
m_spotifyResolver.data()->sendMessage( msg );
|
||||
|
||||
if ( m_updaters.contains( playlist->plid ) )
|
||||
{
|
||||
SpotifyPlaylistUpdater* updater = m_updaters[ playlist->plid ];
|
||||
updater->setSync( false );
|
||||
|
||||
if ( deleteOnUnsync() && !forceDontDelete )
|
||||
{
|
||||
playlist_ptr tomahawkPl = updater->playlist();
|
||||
|
||||
if ( !tomahawkPl.isNull() )
|
||||
Playlist::remove( tomahawkPl );
|
||||
|
||||
updater->deleteLater();
|
||||
|
||||
}
|
||||
|
||||
updater->save();
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
|
||||
void
|
||||
SpotifyAccount::loadPlaylists()
|
||||
{
|
||||
// TODO cache this and only get changed?
|
||||
QVariantMap msg;
|
||||
msg[ "_msgtype" ] = "getAllPlaylists";
|
||||
sendMessage( msg, this, "allPlaylistsLoaded" );
|
||||
}
|
||||
|
||||
|
||||
void
|
||||
SpotifyAccount::setSyncForPlaylist( const QString& spotifyPlaylistId, bool sync )
|
||||
{
|
||||
foreach ( SpotifyPlaylistInfo* info, m_allSpotifyPlaylists )
|
||||
{
|
||||
if( info->plid == spotifyPlaylistId )
|
||||
info->sync = sync;
|
||||
}
|
||||
|
||||
if ( !m_configWidget.isNull() )
|
||||
m_configWidget.data()->setPlaylists( m_allSpotifyPlaylists );
|
||||
}
|
||||
|
||||
|
@ -20,19 +20,35 @@
|
||||
#ifndef SpotifyAccount_H
|
||||
#define SpotifyAccount_H
|
||||
|
||||
#include "accounts/ResolverAccount.h"
|
||||
#include "sourcelist.h"
|
||||
#include "playlist.h"
|
||||
#include "utils/tomahawkutils.h"
|
||||
#include "sourcelist.h"
|
||||
#include "accounts/ResolverAccount.h"
|
||||
#include "utils/SmartPointerList.h"
|
||||
|
||||
class QAction;
|
||||
class SpotifyPlaylistUpdater;
|
||||
class QTimer;
|
||||
|
||||
class ScriptResolver;
|
||||
|
||||
namespace Tomahawk {
|
||||
|
||||
class ExternalResolverGui;
|
||||
|
||||
namespace Accounts {
|
||||
|
||||
class SpotifyAccountConfig;
|
||||
|
||||
// metadata for a playlist
|
||||
struct SpotifyPlaylistInfo {
|
||||
QString name, plid, revid;
|
||||
bool sync, changed;
|
||||
|
||||
|
||||
SpotifyPlaylistInfo( const QString& nname, const QString& pid, const QString& rrevid, bool ssync )
|
||||
: name( nname ), plid( pid ), revid( rrevid ), sync( ssync ), changed( false ) {}
|
||||
|
||||
SpotifyPlaylistInfo() : sync( false ), changed( false ) {}
|
||||
};
|
||||
|
||||
|
||||
class SpotifyAccountFactory : public AccountFactory
|
||||
{
|
||||
@ -61,27 +77,65 @@ class SpotifyAccount : public ResolverAccount
|
||||
public:
|
||||
SpotifyAccount( const QString& accountId );
|
||||
SpotifyAccount( const QString& accountId, const QString& path );
|
||||
virtual ~SpotifyAccount() {}
|
||||
virtual ~SpotifyAccount();
|
||||
|
||||
virtual QPixmap icon() const;
|
||||
virtual QWidget* configurationWidget();
|
||||
virtual void saveConfig();
|
||||
|
||||
virtual QWidget* aclWidget() { return 0; }
|
||||
virtual Tomahawk::InfoSystem::InfoPluginPtr infoPlugin() { return Tomahawk::InfoSystem::InfoPluginPtr(); }
|
||||
virtual SipPlugin* sipPlugin() { return 0; }
|
||||
|
||||
void addPlaylist( const QString &qid, const QString& title, QList< Tomahawk::query_ptr > tracks );
|
||||
QString sendMessage( const QVariantMap& msg, QObject* receiver = 0, const QString& slot = QString() );
|
||||
|
||||
struct Sync {
|
||||
QString id_;
|
||||
QString uuid;
|
||||
Tomahawk::playlist_ptr playlist;
|
||||
};
|
||||
void registerUpdaterForPlaylist( const QString& plId, SpotifyPlaylistUpdater* updater );
|
||||
void unregisterUpdater( const QString& plid );
|
||||
|
||||
bool deleteOnUnsync() const;
|
||||
|
||||
public slots:
|
||||
void aboutToShow( QAction* action, const Tomahawk::playlist_ptr& playlist );
|
||||
void syncActionTriggered( bool );
|
||||
|
||||
private slots:
|
||||
void resolverMessage( const QString& msgType, const QVariantMap& msg );
|
||||
|
||||
void login( const QString& username, const QString& password );
|
||||
// SpotifyResolver message handlers, all take msgtype, msg as argument
|
||||
// void <here>( const QString& msgType, const QVariantMap& msg );
|
||||
void startPlaylistSyncWithPlaylist( const QString& msgType, const QVariantMap& msg );
|
||||
void playlistCreated( const QString& msgType, const QVariantMap& msg );
|
||||
|
||||
private:
|
||||
QList<Sync> m_syncPlaylists;
|
||||
void init();
|
||||
void loadPlaylists();
|
||||
void clearUser();
|
||||
|
||||
void startPlaylistSync( SpotifyPlaylistInfo* playlist );
|
||||
void stopPlaylistSync( SpotifyPlaylistInfo* playlist, bool forceDontDelete = false );
|
||||
void fetchFullPlaylist( SpotifyPlaylistInfo* playlist );
|
||||
|
||||
void setSyncForPlaylist( const QString& spotifyPlaylistId, bool sync );
|
||||
|
||||
QWeakPointer<SpotifyAccountConfig> m_configWidget;
|
||||
QWeakPointer<ScriptResolver> m_spotifyResolver;
|
||||
|
||||
QMap<QString, QPair<QObject*, QString> > m_qidToSlotMap;
|
||||
|
||||
// List of synced spotify playlists in config UI
|
||||
QList< SpotifyPlaylistInfo* > m_allSpotifyPlaylists;
|
||||
QHash< QString, SpotifyPlaylistUpdater* > m_updaters;
|
||||
|
||||
QHash< QString, playlist_ptr > m_waitingForCreateReply;
|
||||
|
||||
SmartPointerList< QAction > m_customActions;
|
||||
friend class ::SpotifyPlaylistUpdater;
|
||||
};
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
Q_DECLARE_METATYPE( Tomahawk::Accounts::SpotifyPlaylistInfo* );
|
||||
|
||||
#endif // SpotifyAccount_H
|
||||
|
165
src/accounts/spotify/SpotifyAccountConfig.cpp
Normal file
165
src/accounts/spotify/SpotifyAccountConfig.cpp
Normal file
@ -0,0 +1,165 @@
|
||||
/* === This file is part of Tomahawk Player - <http://tomahawk-player.org> ===
|
||||
*
|
||||
* Copyright 2012, Leo Franchi <lfranchi@kde.org>
|
||||
*
|
||||
* Tomahawk is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
* the Free Software Foundation, either version 3 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* Tomahawk is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with Tomahawk. If not, see <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
#include "SpotifyAccountConfig.h"
|
||||
|
||||
#include "SpotifyAccount.h"
|
||||
#include <playlist/dynamic/widgets/LoadingSpinner.h>
|
||||
#include "ui_SpotifyAccountConfig.h"
|
||||
|
||||
#include <QListWidget>
|
||||
#include <QListWidgetItem>
|
||||
#include <QShowEvent>
|
||||
|
||||
using namespace Tomahawk;
|
||||
using namespace Accounts;
|
||||
|
||||
SpotifyAccountConfig::SpotifyAccountConfig( SpotifyAccount *account )
|
||||
: QWidget( 0 )
|
||||
, m_ui( new Ui::SpotifyConfig )
|
||||
, m_account( account )
|
||||
, m_playlistsLoading( 0 )
|
||||
{
|
||||
m_ui->setupUi( this );
|
||||
|
||||
connect( m_ui->loginButton, SIGNAL( clicked( bool ) ), this, SLOT( doLogin() ) );
|
||||
|
||||
connect( m_ui->usernameEdit, SIGNAL( textChanged( QString ) ), this, SLOT( resetLoginButton() ) );
|
||||
connect( m_ui->passwordEdit, SIGNAL( textChanged( QString ) ), this, SLOT( resetLoginButton() ) );
|
||||
loadFromConfig();
|
||||
|
||||
m_playlistsLoading = new LoadingSpinner( m_ui->playlistList );
|
||||
}
|
||||
|
||||
|
||||
void
|
||||
SpotifyAccountConfig::showEvent( QShowEvent *event )
|
||||
{
|
||||
loadFromConfig();
|
||||
}
|
||||
|
||||
|
||||
void
|
||||
SpotifyAccountConfig::loadFromConfig()
|
||||
{
|
||||
m_ui->usernameEdit->setText( m_account->credentials().value( "username" ).toString() );
|
||||
m_ui->passwordEdit->setText( m_account->credentials().value( "password" ).toString() );
|
||||
m_ui->streamingCheckbox->setChecked( m_account->credentials().value( "highQuality" ).toBool() );
|
||||
m_ui->deleteOnUnsync->setChecked( m_account->deleteOnUnsync() );
|
||||
}
|
||||
|
||||
void
|
||||
SpotifyAccountConfig::saveSettings()
|
||||
{
|
||||
for( int i = 0; i < m_ui->playlistList->count(); i++ )
|
||||
{
|
||||
const QListWidgetItem* item = m_ui->playlistList->item( i );
|
||||
|
||||
SpotifyPlaylistInfo* pl = item->data( Qt::UserRole ).value< SpotifyPlaylistInfo* >();
|
||||
const bool toSync = ( item->checkState() == Qt::Checked );
|
||||
if ( pl->sync != toSync )
|
||||
{
|
||||
pl->changed = true;
|
||||
pl->sync = toSync;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
QString
|
||||
SpotifyAccountConfig::username() const
|
||||
{
|
||||
return m_ui->usernameEdit->text().trimmed();
|
||||
}
|
||||
|
||||
QString
|
||||
SpotifyAccountConfig::password() const
|
||||
{
|
||||
return m_ui->passwordEdit->text().trimmed();
|
||||
}
|
||||
|
||||
bool
|
||||
SpotifyAccountConfig::highQuality() const
|
||||
{
|
||||
return m_ui->streamingCheckbox->isChecked();
|
||||
}
|
||||
|
||||
|
||||
bool
|
||||
SpotifyAccountConfig::deleteOnUnsync() const
|
||||
{
|
||||
return m_ui->deleteOnUnsync->isChecked();
|
||||
}
|
||||
|
||||
|
||||
void
|
||||
SpotifyAccountConfig::setPlaylists( const QList<SpotifyPlaylistInfo *>& playlists )
|
||||
{
|
||||
// User always has at least 1 playlist (starred tracks)
|
||||
if ( !playlists.isEmpty() )
|
||||
m_playlistsLoading->fadeOut();
|
||||
|
||||
m_ui->playlistList->clear();
|
||||
foreach ( SpotifyPlaylistInfo* pl, playlists )
|
||||
{
|
||||
QListWidgetItem* item = new QListWidgetItem( pl->name, m_ui->playlistList );
|
||||
item->setData( Qt::UserRole, QVariant::fromValue< SpotifyPlaylistInfo* >( pl ) );
|
||||
item->setFlags( Qt::ItemIsUserCheckable | Qt::ItemIsSelectable | Qt::ItemIsEnabled );
|
||||
item->setCheckState( pl->sync ? Qt::Checked : Qt::Unchecked );
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
void
|
||||
SpotifyAccountConfig::doLogin()
|
||||
{
|
||||
m_ui->loginButton->setText( tr( "Logging in..." ) );
|
||||
m_ui->loginButton->setEnabled( false );
|
||||
|
||||
m_playlistsLoading->fadeIn();
|
||||
|
||||
emit login( username(), password() );
|
||||
}
|
||||
|
||||
|
||||
void
|
||||
SpotifyAccountConfig::loginResponse( bool success, const QString& msg )
|
||||
{
|
||||
if ( success )
|
||||
{
|
||||
m_ui->loginButton->setText( tr( "Logged in!" ) );
|
||||
m_ui->loginButton->setEnabled( false );
|
||||
}
|
||||
else
|
||||
{
|
||||
setPlaylists( QList< SpotifyPlaylistInfo* >() );
|
||||
m_playlistsLoading->fadeOut();
|
||||
m_ui->loginButton->setText( tr( "Failed: %1" ).arg( msg ) );
|
||||
m_ui->loginButton->setEnabled( true );
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
|
||||
void
|
||||
SpotifyAccountConfig::resetLoginButton()
|
||||
{
|
||||
m_ui->loginButton->setText( tr( "Log In" ) );
|
||||
m_ui->loginButton->setEnabled( true );
|
||||
}
|
||||
|
81
src/accounts/spotify/SpotifyAccountConfig.h
Normal file
81
src/accounts/spotify/SpotifyAccountConfig.h
Normal file
@ -0,0 +1,81 @@
|
||||
/* === This file is part of Tomahawk Player - <http://tomahawk-player.org> ===
|
||||
*
|
||||
* Copyright 2012, Leo Franchi <lfranchi@kde.org>
|
||||
*
|
||||
* Tomahawk is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
* the Free Software Foundation, either version 3 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* Tomahawk is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with Tomahawk. If not, see <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
#ifndef SPOTIFYACCOUNTCONFIG_H
|
||||
#define SPOTIFYACCOUNTCONFIG_H
|
||||
|
||||
#include <QWidget>
|
||||
#include <QVariantMap>
|
||||
#include <QTimer>
|
||||
|
||||
class LoadingSpinner;
|
||||
class QShowEvent;
|
||||
|
||||
namespace Ui
|
||||
{
|
||||
class SpotifyConfig;
|
||||
}
|
||||
|
||||
namespace Tomahawk
|
||||
{
|
||||
namespace Accounts
|
||||
{
|
||||
|
||||
class SpotifyAccount;
|
||||
struct SpotifyPlaylistInfo;
|
||||
|
||||
class SpotifyAccountConfig : public QWidget
|
||||
{
|
||||
Q_OBJECT
|
||||
public:
|
||||
explicit SpotifyAccountConfig( SpotifyAccount* account );
|
||||
|
||||
QString username() const;
|
||||
QString password() const;
|
||||
bool highQuality() const;
|
||||
bool deleteOnUnsync() const;
|
||||
|
||||
void setPlaylists( const QList< SpotifyPlaylistInfo* >& playlists );
|
||||
|
||||
void loadFromConfig();
|
||||
void saveSettings();
|
||||
|
||||
void loginResponse( bool success, const QString& msg );
|
||||
signals:
|
||||
void login( const QString& username, const QString& pw );
|
||||
|
||||
public slots:
|
||||
// void verifyResult( const QString& msgType, const QVariantMap& msg );
|
||||
|
||||
protected:
|
||||
void showEvent( QShowEvent* event );
|
||||
|
||||
private slots:
|
||||
void doLogin();
|
||||
void resetLoginButton();
|
||||
|
||||
private:
|
||||
Ui::SpotifyConfig* m_ui;
|
||||
SpotifyAccount* m_account;
|
||||
LoadingSpinner* m_playlistsLoading;
|
||||
};
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
#endif // SPOTIFYACCOUNTCONFIG_H
|
@ -6,8 +6,8 @@
|
||||
<rect>
|
||||
<x>0</x>
|
||||
<y>0</y>
|
||||
<width>397</width>
|
||||
<height>487</height>
|
||||
<width>419</width>
|
||||
<height>454</height>
|
||||
</rect>
|
||||
</property>
|
||||
<property name="sizePolicy">
|
||||
@ -20,21 +20,33 @@
|
||||
<string>Form</string>
|
||||
</property>
|
||||
<layout class="QVBoxLayout" name="verticalLayout">
|
||||
<item>
|
||||
<widget class="QLabel" name="icon">
|
||||
<property name="text">
|
||||
<string/>
|
||||
</property>
|
||||
<property name="pixmap">
|
||||
<pixmap>spotify-logo.png</pixmap>
|
||||
</property>
|
||||
<property name="alignment">
|
||||
<set>Qt::AlignCenter</set>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<property name="margin">
|
||||
<number>4</number>
|
||||
</property>
|
||||
<item>
|
||||
<layout class="QHBoxLayout" name="horizontalLayout_2">
|
||||
<item>
|
||||
<widget class="QLabel" name="icon">
|
||||
<property name="maximumSize">
|
||||
<size>
|
||||
<width>64</width>
|
||||
<height>64</height>
|
||||
</size>
|
||||
</property>
|
||||
<property name="text">
|
||||
<string/>
|
||||
</property>
|
||||
<property name="pixmap">
|
||||
<pixmap resource="../../../resources.qrc">:/data/images/spotify-logo.png</pixmap>
|
||||
</property>
|
||||
<property name="scaledContents">
|
||||
<bool>true</bool>
|
||||
</property>
|
||||
<property name="alignment">
|
||||
<set>Qt::AlignCenter</set>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item>
|
||||
<widget class="QLabel" name="titleText">
|
||||
<property name="font">
|
||||
@ -45,7 +57,7 @@
|
||||
</font>
|
||||
</property>
|
||||
<property name="text">
|
||||
<string>Configure your Spotify credentials</string>
|
||||
<string>Configure your Spotify account</string>
|
||||
</property>
|
||||
<property name="textFormat">
|
||||
<enum>Qt::PlainText</enum>
|
||||
@ -58,44 +70,61 @@
|
||||
</layout>
|
||||
</item>
|
||||
<item>
|
||||
<layout class="QFormLayout" name="formLayout">
|
||||
<item row="0" column="0">
|
||||
<widget class="QLabel" name="usernameLabel">
|
||||
<property name="text">
|
||||
<string>Username:</string>
|
||||
<layout class="QHBoxLayout" name="horizontalLayout_4">
|
||||
<item>
|
||||
<layout class="QFormLayout" name="formLayout">
|
||||
<property name="fieldGrowthPolicy">
|
||||
<enum>QFormLayout::ExpandingFieldsGrow</enum>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item row="0" column="1">
|
||||
<widget class="QLineEdit" name="usernameEdit">
|
||||
<property name="text">
|
||||
<string>placeholderUsername</string>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item row="1" column="0">
|
||||
<widget class="QLabel" name="passwordLabel">
|
||||
<property name="text">
|
||||
<string>Password:</string>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item row="1" column="1">
|
||||
<widget class="QLineEdit" name="passwordEdit">
|
||||
<property name="text">
|
||||
<string>placeholderPw</string>
|
||||
</property>
|
||||
<property name="echoMode">
|
||||
<enum>QLineEdit::Password</enum>
|
||||
</property>
|
||||
</widget>
|
||||
<item row="0" column="0">
|
||||
<widget class="QLabel" name="usernameLabel">
|
||||
<property name="text">
|
||||
<string>Username:</string>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item row="0" column="1">
|
||||
<widget class="QLineEdit" name="usernameEdit">
|
||||
<property name="text">
|
||||
<string/>
|
||||
</property>
|
||||
<property name="placeholderText">
|
||||
<string>Username or Facebook Email</string>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item row="1" column="0">
|
||||
<widget class="QLabel" name="passwordLabel">
|
||||
<property name="text">
|
||||
<string>Password:</string>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item row="1" column="1">
|
||||
<widget class="QLineEdit" name="passwordEdit">
|
||||
<property name="text">
|
||||
<string/>
|
||||
</property>
|
||||
<property name="echoMode">
|
||||
<enum>QLineEdit::Password</enum>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
</layout>
|
||||
</item>
|
||||
</layout>
|
||||
</item>
|
||||
<item>
|
||||
<widget class="QPushButton" name="loginButton">
|
||||
<property name="text">
|
||||
<string>Log In</string>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item>
|
||||
<layout class="QHBoxLayout" name="horizontalLayout_3">
|
||||
<item>
|
||||
<spacer name="horizontalSpacer_5">
|
||||
<spacer name="horizontalSpacer_3">
|
||||
<property name="orientation">
|
||||
<enum>Qt::Horizontal</enum>
|
||||
</property>
|
||||
@ -113,15 +142,15 @@
|
||||
<enum>Qt::RightToLeft</enum>
|
||||
</property>
|
||||
<property name="text">
|
||||
<string>High Quality Streaming</string>
|
||||
<string>High Quality Streams</string>
|
||||
</property>
|
||||
<property name="checked">
|
||||
<bool>STREAMING_DEFAULT</bool>
|
||||
<bool>false</bool>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item>
|
||||
<spacer name="horizontalSpacer_6">
|
||||
<spacer name="horizontalSpacer_4">
|
||||
<property name="orientation">
|
||||
<enum>Qt::Horizontal</enum>
|
||||
</property>
|
||||
@ -135,6 +164,23 @@
|
||||
</item>
|
||||
</layout>
|
||||
</item>
|
||||
<item>
|
||||
<widget class="QLabel" name="label_3">
|
||||
<property name="text">
|
||||
<string>Spotify playlists to keep in sync:</string>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item>
|
||||
<widget class="QListWidget" name="playlistList"/>
|
||||
</item>
|
||||
<item>
|
||||
<widget class="QCheckBox" name="deleteOnUnsync">
|
||||
<property name="text">
|
||||
<string>Delete Tomahawk playlist when removing synchronization</string>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item>
|
||||
<layout class="QHBoxLayout" name="horizontalLayout">
|
||||
<item>
|
||||
@ -152,11 +198,35 @@
|
||||
</item>
|
||||
<item>
|
||||
<widget class="QLabel" name="spotifyLogo">
|
||||
<property name="maximumSize">
|
||||
<size>
|
||||
<width>64</width>
|
||||
<height>64</height>
|
||||
</size>
|
||||
</property>
|
||||
<property name="text">
|
||||
<string/>
|
||||
</property>
|
||||
<property name="pixmap">
|
||||
<pixmap>spotifycore-logo.png</pixmap>
|
||||
<pixmap resource="../../../resources.qrc">:/data/images/spotifycore-logo.png</pixmap>
|
||||
</property>
|
||||
<property name="scaledContents">
|
||||
<bool>true</bool>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item>
|
||||
<widget class="QLabel" name="label">
|
||||
<property name="font">
|
||||
<font>
|
||||
<pointsize>9</pointsize>
|
||||
</font>
|
||||
</property>
|
||||
<property name="text">
|
||||
<string>This product uses SPOTIFY(R) CORE but is not endorsed, certified or otherwise approved in any way by Spotify. Spotify is the registered trade mark of the Spotify Group.</string>
|
||||
</property>
|
||||
<property name="wordWrap">
|
||||
<bool>true</bool>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
@ -175,25 +245,10 @@
|
||||
</item>
|
||||
</layout>
|
||||
</item>
|
||||
<item>
|
||||
<widget class="QLabel" name="label">
|
||||
<property name="font">
|
||||
<font>
|
||||
<pointsize>9</pointsize>
|
||||
</font>
|
||||
</property>
|
||||
<property name="text">
|
||||
<string>This product uses SPOTIFY(R) CORE but is not endorsed, certified or otherwise approved in any way by Spotify. Spotify is the registered trade mark of the Spotify Group.</string>
|
||||
</property>
|
||||
<property name="wordWrap">
|
||||
<bool>true</bool>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
</layout>
|
||||
</widget>
|
||||
<resources>
|
||||
<include location="resources.qrc"/>
|
||||
<include location="../../../resources.qrc"/>
|
||||
</resources>
|
||||
<connections/>
|
||||
</ui>
|
||||
|
670
src/accounts/spotify/SpotifyPlaylistUpdater.cpp
Normal file
670
src/accounts/spotify/SpotifyPlaylistUpdater.cpp
Normal file
@ -0,0 +1,670 @@
|
||||
/* === This file is part of Tomahawk Player - <http://tomahawk-player.org> ===
|
||||
*
|
||||
* Copyright 2010-2012, Leo Franchi <lfranchi@kde.org>
|
||||
*
|
||||
* Tomahawk is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
* the Free Software Foundation, either version 3 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* Tomahawk is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with Tomahawk. If not, see <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
#include "SpotifyPlaylistUpdater.h"
|
||||
|
||||
#include "accounts/AccountManager.h"
|
||||
#include "SpotifyAccount.h"
|
||||
#include "utils/tomahawkutils.h"
|
||||
|
||||
#include <QMessageBox>
|
||||
|
||||
using namespace Tomahawk;
|
||||
using namespace Accounts;
|
||||
|
||||
#ifndef ENABLE_HEADLESS
|
||||
QPixmap* SpotifyPlaylistUpdater::s_typePixmap = 0;
|
||||
#endif
|
||||
|
||||
Tomahawk::PlaylistUpdaterInterface*
|
||||
SpotifyUpdaterFactory::create( const Tomahawk::playlist_ptr& pl, const QString &key )
|
||||
{
|
||||
if ( !m_account )
|
||||
{
|
||||
// Find the spotify account
|
||||
foreach ( Account* account, AccountManager::instance()->accounts() )
|
||||
{
|
||||
if ( SpotifyAccount* spotify = qobject_cast< SpotifyAccount* >( account ) )
|
||||
{
|
||||
m_account = spotify;
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if ( m_account.isNull() )
|
||||
{
|
||||
qWarning() << "Found a spotify updater with no spotify account... ignoreing for now!!";
|
||||
return 0;
|
||||
}
|
||||
|
||||
// Register the updater with the account
|
||||
const QString spotifyId = TomahawkSettings::instance()->value( QString( "%1/spotifyId" ).arg( key ) ).toString();
|
||||
const QString latestRev = TomahawkSettings::instance()->value( QString( "%1/latestrev" ).arg( key ) ).toString();
|
||||
const bool sync = TomahawkSettings::instance()->value( QString( "%1/sync" ).arg( key ) ).toBool();
|
||||
|
||||
Q_ASSERT( !spotifyId.isEmpty() );
|
||||
SpotifyPlaylistUpdater* updater = new SpotifyPlaylistUpdater( m_account.data(), latestRev, spotifyId, pl );
|
||||
updater->setSync( sync );
|
||||
m_account.data()->registerUpdaterForPlaylist( spotifyId, updater );
|
||||
|
||||
return updater;
|
||||
}
|
||||
|
||||
|
||||
SpotifyPlaylistUpdater::SpotifyPlaylistUpdater( SpotifyAccount* acct, const QString& revid, const QString& spotifyId, const playlist_ptr& pl )
|
||||
: PlaylistUpdaterInterface( pl )
|
||||
, m_spotify( acct )
|
||||
, m_latestRev( revid )
|
||||
, m_spotifyId( spotifyId )
|
||||
, m_blockUpdatesForNextRevision( false )
|
||||
, m_sync( false )
|
||||
{
|
||||
init();
|
||||
}
|
||||
|
||||
|
||||
void
|
||||
SpotifyPlaylistUpdater::init()
|
||||
{
|
||||
|
||||
connect( playlist().data(), SIGNAL( tracksInserted( QList<Tomahawk::plentry_ptr>, int ) ), this, SLOT( tomahawkTracksInserted( QList<Tomahawk::plentry_ptr>, int ) ) );
|
||||
connect( playlist().data(), SIGNAL( tracksRemoved( QList<Tomahawk::query_ptr> ) ), this, SLOT( tomahawkTracksRemoved( QList<Tomahawk::query_ptr> ) ) );
|
||||
connect( playlist().data(), SIGNAL( tracksMoved( QList<Tomahawk::plentry_ptr>, int ) ), this, SLOT( tomahawkTracksMoved( QList<Tomahawk::plentry_ptr>, int ) ) );
|
||||
connect( playlist().data(), SIGNAL( renamed( QString, QString ) ), this, SLOT( tomahawkPlaylistRenamed( QString, QString ) ) );
|
||||
connect( playlist().data(), SIGNAL( revisionLoaded( Tomahawk::PlaylistRevision ) ), this, SLOT( playlistRevisionLoaded() ), Qt::QueuedConnection ); // Queued so that in playlist.cpp:443 we let the playlist clear its own queue first
|
||||
// TODO reorders in a playlist
|
||||
}
|
||||
|
||||
|
||||
SpotifyPlaylistUpdater::~SpotifyPlaylistUpdater()
|
||||
{
|
||||
if ( !m_spotify.isNull() )
|
||||
{
|
||||
if ( m_sync )
|
||||
{
|
||||
QVariantMap msg;
|
||||
msg[ "_msgtype" ] = "removeFromSyncList";
|
||||
msg[ "playlistid" ] = m_spotifyId;
|
||||
|
||||
m_spotify.data()->sendMessage( msg );
|
||||
|
||||
m_spotify.data()->setSyncForPlaylist( m_spotifyId, false );
|
||||
}
|
||||
|
||||
m_spotify.data()->unregisterUpdater( m_spotifyId );
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
void
|
||||
SpotifyPlaylistUpdater::removeFromSettings( const QString& group ) const
|
||||
{
|
||||
TomahawkSettings::instance()->remove( QString( "%1/latestrev" ).arg( group ) );
|
||||
TomahawkSettings::instance()->remove( QString( "%1/sync" ).arg( group ) );
|
||||
TomahawkSettings::instance()->remove( QString( "%1/spotifyId" ).arg( group ) );
|
||||
|
||||
if ( m_sync )
|
||||
{
|
||||
if ( QThread::currentThread() != QApplication::instance()->thread() )
|
||||
QMetaObject::invokeMethod( const_cast<SpotifyPlaylistUpdater*>(this), "checkDeleteDialog", Qt::BlockingQueuedConnection );
|
||||
else
|
||||
checkDeleteDialog();
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
void
|
||||
SpotifyPlaylistUpdater::checkDeleteDialog() const
|
||||
{
|
||||
// Ask if we should delete the playlist on the spotify side as well
|
||||
QMessageBox askDelete( QMessageBox::Question, tr( "Delete in Spotify?" ), tr( "Would you like to delete the corresponding Spotify playlist as well?" ), QMessageBox::Yes | QMessageBox::No, 0 );
|
||||
int ret = askDelete.exec();
|
||||
if ( ret == QMessageBox::Yes )
|
||||
{
|
||||
if ( m_spotify.isNull() )
|
||||
return;
|
||||
|
||||
// User wants to delete it!
|
||||
QVariantMap msg;
|
||||
msg[ "_msgtype" ] = "deletePlaylist";
|
||||
msg[ "playlistid" ] = m_spotifyId;
|
||||
m_spotify.data()->sendMessage( msg );
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
void
|
||||
SpotifyPlaylistUpdater::playlistRevisionLoaded()
|
||||
{
|
||||
if ( m_queuedOps.isEmpty() ) // nothing queued
|
||||
return;
|
||||
|
||||
if ( playlist()->busy() ) // not ready yet, we'll get another revision loaded
|
||||
return;
|
||||
|
||||
_detail::Closure* next = m_queuedOps.dequeue();
|
||||
next->forceInvoke();
|
||||
}
|
||||
|
||||
|
||||
void
|
||||
SpotifyPlaylistUpdater::saveToSettings( const QString& group ) const
|
||||
{
|
||||
TomahawkSettings::instance()->setValue( QString( "%1/latestrev" ).arg( group ), m_latestRev );
|
||||
TomahawkSettings::instance()->setValue( QString( "%1/sync" ).arg( group ), m_sync );
|
||||
TomahawkSettings::instance()->setValue( QString( "%1/spotifyId" ).arg( group ), m_spotifyId );
|
||||
}
|
||||
|
||||
|
||||
QString
|
||||
SpotifyPlaylistUpdater::type() const
|
||||
{
|
||||
return "spotify";
|
||||
}
|
||||
|
||||
|
||||
#ifndef ENABLE_HEADLESS
|
||||
QPixmap
|
||||
SpotifyPlaylistUpdater::typeIcon() const
|
||||
{
|
||||
if ( !s_typePixmap )
|
||||
{
|
||||
QPixmap pm( RESPATH "images/spotify-logo.png" );
|
||||
s_typePixmap = new QPixmap( pm.scaled( 32, 32, Qt::KeepAspectRatio, Qt::SmoothTransformation ) );
|
||||
}
|
||||
|
||||
if ( !m_sync )
|
||||
return QPixmap();
|
||||
|
||||
return *s_typePixmap;
|
||||
}
|
||||
#endif
|
||||
|
||||
|
||||
void
|
||||
SpotifyPlaylistUpdater::setSync( bool sync )
|
||||
{
|
||||
if ( m_sync == sync )
|
||||
return;
|
||||
|
||||
m_sync = sync;
|
||||
|
||||
emit changed();
|
||||
}
|
||||
|
||||
|
||||
bool
|
||||
SpotifyPlaylistUpdater::sync() const
|
||||
{
|
||||
return m_sync;
|
||||
}
|
||||
|
||||
|
||||
void
|
||||
SpotifyPlaylistUpdater::spotifyTracksAdded( const QVariantList& tracks, const QString& startPosId, const QString& newRev, const QString& oldRev )
|
||||
{
|
||||
if( playlist()->busy() )
|
||||
{
|
||||
// We might still be waiting for a add/remove tracks command to finish, so the entries we get here might be stale
|
||||
// wait for any to be complete
|
||||
m_queuedOps << NewClosure( 0, "", this, SLOT(spotifyTracksAdded(QVariantList, QString, QString, QString)), tracks, startPosId, newRev, oldRev );
|
||||
return;
|
||||
}
|
||||
|
||||
const QList< query_ptr > queries = variantToQueries( tracks );
|
||||
|
||||
qDebug() << Q_FUNC_INFO << "inserting tracks in middle of tomahawk playlist, from spotify command!" << tracks << startPosId << newRev << oldRev;
|
||||
// Uh oh, dont' want to get out of sync!!
|
||||
// Q_ASSERT( m_latestRev == oldRev );
|
||||
// m_latestRev = newRev;
|
||||
|
||||
// Find the position of the track to insert from
|
||||
int pos = -1;
|
||||
QList< plentry_ptr > entries = playlist()->entries();
|
||||
for ( int i = 0; i < entries.size(); i++ )
|
||||
{
|
||||
if ( entries[ i ]->annotation() == startPosId )
|
||||
{
|
||||
pos = i;
|
||||
break;
|
||||
}
|
||||
}
|
||||
pos++; // We found index of item before, so get index of new item.
|
||||
|
||||
if ( pos == -1 || pos > entries.size() )
|
||||
pos = entries.size();
|
||||
|
||||
qDebug() << Q_FUNC_INFO << "inserting tracks at position:" << pos << "(playlist has current size:" << entries << ")";
|
||||
|
||||
m_blockUpdatesForNextRevision = true;
|
||||
playlist()->insertEntries( queries, pos, playlist()->currentrevision() );
|
||||
}
|
||||
|
||||
|
||||
void
|
||||
SpotifyPlaylistUpdater::spotifyTracksRemoved( const QVariantList& trackIds, const QString& newRev, const QString& oldRev )
|
||||
{
|
||||
if( playlist()->busy() )
|
||||
{
|
||||
// We might still be waiting for a add/remove tracks command to finish, so the entries we get here might be stale
|
||||
// wait for any to be complete
|
||||
m_queuedOps << NewClosure( 0, "", this, SLOT(spotifyTracksRemoved(QVariantList, QString, QString)), trackIds, newRev, oldRev );
|
||||
return;
|
||||
}
|
||||
|
||||
qDebug() << Q_FUNC_INFO << "remove tracks in middle of tomahawk playlist, from spotify command!" << trackIds << newRev << oldRev;
|
||||
// Uh oh, dont' want to get out of sync!!
|
||||
// Q_ASSERT( m_latestRev == oldRev );
|
||||
// m_latestRev = newRev;
|
||||
|
||||
QList< plentry_ptr > entries = playlist()->entries();
|
||||
|
||||
// Collect list of tracks to remove (can't remove in-place as that might modify the indices)
|
||||
QList<plentry_ptr> toRemove;
|
||||
foreach( const QVariant trackIdV, trackIds )
|
||||
{
|
||||
const QString id = trackIdV.toString();
|
||||
if ( id.isEmpty() )
|
||||
{
|
||||
qWarning() << Q_FUNC_INFO << "Tried to get track id to remove, but either couldn't convert to qstring:" << trackIdV;
|
||||
continue;
|
||||
}
|
||||
|
||||
foreach ( const plentry_ptr& entry, entries )
|
||||
{
|
||||
if ( entry->annotation() == id )
|
||||
{
|
||||
toRemove << entry;
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
// Now remove them all
|
||||
foreach( const plentry_ptr& torm, toRemove )
|
||||
entries.removeAll( torm );
|
||||
|
||||
const int sizeDiff = playlist()->entries().size() - entries.size();
|
||||
qDebug() << "We were asked to delete:" << trackIds.size() << "tracks from the playlist, and we deleted:" << sizeDiff;
|
||||
if ( trackIds.size() != ( playlist()->entries().size() - entries.size() ) )
|
||||
qWarning() << "========================= Failed to delete all the tracks we were asked for!! Didn't find some indicesss... ===================";
|
||||
|
||||
if ( sizeDiff > 0 )
|
||||
{
|
||||
// Won't get a tomahawkTracksInserted or tomahawkTracksRemoved slot called, no need to block
|
||||
playlist()->createNewRevision( uuid(), playlist()->currentrevision(), entries );
|
||||
}
|
||||
}
|
||||
|
||||
void
|
||||
SpotifyPlaylistUpdater::spotifyPlaylistRenamed( const QString& title, const QString& newRev, const QString& oldRev )
|
||||
{
|
||||
if( playlist()->busy() )
|
||||
{
|
||||
// We might still be waiting for a add/remove tracks command to finish, so the entries we get here might be stale
|
||||
// wait for any to be complete
|
||||
m_queuedOps << NewClosure( 0, "", this, SLOT(spotifyPlaylistRenamed(QString, QString, QString)), title, newRev, oldRev );
|
||||
return;
|
||||
}
|
||||
|
||||
Q_UNUSED( newRev );
|
||||
Q_UNUSED( oldRev );
|
||||
/// @note to self: should do some checking before trying to update
|
||||
playlist()->rename( title );
|
||||
|
||||
}
|
||||
|
||||
void
|
||||
SpotifyPlaylistUpdater::tomahawkPlaylistRenamed(const QString &newT, const QString &oldT)
|
||||
{
|
||||
qDebug() << Q_FUNC_INFO;
|
||||
QVariantMap msg;
|
||||
msg[ "_msgtype" ] = "playlistRenamed";
|
||||
msg[ "oldrev" ] = m_latestRev;
|
||||
msg[ "newTitle" ] = newT;
|
||||
msg[ "oldTitle" ] = oldT;
|
||||
msg[ "playlistid" ] = m_spotifyId;
|
||||
m_spotify.data()->sendMessage( msg, this, "onPlaylistRename" );
|
||||
}
|
||||
|
||||
void
|
||||
SpotifyPlaylistUpdater::spotifyTracksMoved( const QVariantList& tracks, const QString& newStartPos, const QString& newRev, const QString& oldRev )
|
||||
{
|
||||
if( playlist()->busy() )
|
||||
{
|
||||
// We might still be waiting for a add/remove tracks command to finish, so the entries we get here might be stale
|
||||
// wait for any to be complete
|
||||
m_queuedOps << NewClosure( 0, "", this, SLOT(spotifyTracksMoved(QVariantList, QString, QString, QString)), tracks, newStartPos, newRev, oldRev );
|
||||
return;
|
||||
}
|
||||
|
||||
|
||||
qDebug() << "Moving some tracks in a spotify-synced playlist, tracks:" << tracks << "to new startpos:" << newStartPos;
|
||||
// Uh oh, dont' want to get out of sync!!
|
||||
// Q_ASSERT( m_latestRev == oldRev );
|
||||
// m_latestRev = newRev;
|
||||
QList< plentry_ptr > entries = playlist()->entries();
|
||||
|
||||
QList< plentry_ptr > toMove;
|
||||
for ( QList< plentry_ptr >::iterator iter = entries.begin(); iter != entries.end(); )
|
||||
{
|
||||
if ( (*iter)->annotation().isEmpty() )
|
||||
continue;
|
||||
|
||||
if ( tracks.contains( (*iter)->annotation() ) )
|
||||
{
|
||||
toMove << *iter;
|
||||
iter = entries.erase( iter );
|
||||
continue;
|
||||
}
|
||||
|
||||
++iter;
|
||||
}
|
||||
|
||||
|
||||
// Find the position of the track to insert from
|
||||
if ( newStartPos.isEmpty() )
|
||||
{
|
||||
while ( !toMove.isEmpty() )
|
||||
entries.prepend( toMove.takeLast() );
|
||||
}
|
||||
else
|
||||
{
|
||||
for ( QList< plentry_ptr >::iterator iter = entries.begin(); iter != entries.end(); ++iter )
|
||||
{
|
||||
if ( (*iter)->annotation() == newStartPos )
|
||||
{
|
||||
++iter;
|
||||
while ( !toMove.isEmpty() )
|
||||
{
|
||||
qDebug() << "Adding moved track to playlist at pos (end:" << (iter == entries.end());
|
||||
if ( iter != entries.end() )
|
||||
qDebug() << (*iter)->query()->track() << (*iter)->query()->artist();
|
||||
iter = entries.insert( iter, toMove.takeLast() );
|
||||
}
|
||||
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
m_blockUpdatesForNextRevision = true;
|
||||
playlist()->createNewRevision( uuid(), playlist()->currentrevision(), entries );
|
||||
}
|
||||
|
||||
|
||||
void
|
||||
SpotifyPlaylistUpdater::tomahawkTracksInserted( const QList< plentry_ptr >& tracks, int pos )
|
||||
{
|
||||
if ( m_spotify.isNull() )
|
||||
return;
|
||||
|
||||
if ( m_blockUpdatesForNextRevision )
|
||||
{
|
||||
qDebug() << "Ignoring tracks inserted message since we just did an insert ourselves!";
|
||||
m_blockUpdatesForNextRevision = false;
|
||||
return;
|
||||
}
|
||||
|
||||
// Notify the resolver that we've updated
|
||||
qDebug() << Q_FUNC_INFO << "updating spotify resolver with inserted tracks at:" << pos << tracks;
|
||||
QVariantMap msg;
|
||||
msg[ "_msgtype" ] = "addTracksToPlaylist";
|
||||
msg[ "oldrev" ] = m_latestRev;
|
||||
|
||||
// Find the trackid of the nearest spotify track
|
||||
QList< plentry_ptr > plTracks = playlist()->entries();
|
||||
Q_ASSERT( pos-1 < plTracks.size() );
|
||||
const QString startPos = nearestSpotifyTrack( plTracks, pos - 1 );
|
||||
msg[ "startPosition" ] = startPos;
|
||||
|
||||
m_waitingForIds = tracks;
|
||||
|
||||
msg[ "playlistid" ] = m_spotifyId;
|
||||
|
||||
msg[ "tracks" ] = plentryToVariant( tracks );
|
||||
|
||||
m_spotify.data()->sendMessage( msg, this, "onTracksInsertedReturn" );
|
||||
}
|
||||
|
||||
|
||||
QString
|
||||
SpotifyPlaylistUpdater::nearestSpotifyTrack( const QList< plentry_ptr >& entries, int pos )
|
||||
{
|
||||
for ( int i = pos; i >= 0; i-- )
|
||||
{
|
||||
if ( !entries[ i ]->annotation().isEmpty() && entries[ i ]->annotation().contains( "spotify:track") )
|
||||
{
|
||||
return entries[ i ]->annotation();
|
||||
}
|
||||
}
|
||||
|
||||
return QString();
|
||||
}
|
||||
|
||||
|
||||
QVariantList
|
||||
SpotifyPlaylistUpdater::plentryToVariant( const QList< plentry_ptr >& entries )
|
||||
{
|
||||
QVariantList tracksJson;
|
||||
foreach ( const plentry_ptr& ple, entries )
|
||||
{
|
||||
const query_ptr q = ple->query();
|
||||
if ( q.isNull() )
|
||||
{
|
||||
qDebug() << "Got null query_ptr in plentry_ptr!!!" << ple;
|
||||
continue;
|
||||
}
|
||||
|
||||
tracksJson << queryToVariant( q );
|
||||
}
|
||||
|
||||
return tracksJson;
|
||||
}
|
||||
|
||||
|
||||
void
|
||||
SpotifyPlaylistUpdater::onTracksInsertedReturn( const QString& msgType, const QVariantMap& msg )
|
||||
{
|
||||
const bool success = msg.value( "success" ).toBool();
|
||||
|
||||
qDebug() << Q_FUNC_INFO << "GOT RETURN FOR tracksInserted call from spotify!" << msgType << msg << "Succeeded?" << success;
|
||||
m_latestRev = msg.value( "revid" ).toString();
|
||||
|
||||
|
||||
const QVariantList trackPositionsInserted = msg.value( "trackPosInserted" ).toList();
|
||||
const QVariantList trackIdsInserted = msg.value( "trackIdInserted" ).toList();
|
||||
|
||||
Q_ASSERT( trackPositionsInserted.size() == trackIdsInserted.size() );
|
||||
|
||||
const QList< plentry_ptr > curEntries = playlist()->entries();
|
||||
QList< plentry_ptr > changed;
|
||||
|
||||
for ( int i = 0; i < trackPositionsInserted.size(); i++ )
|
||||
{
|
||||
const QVariant posV = trackPositionsInserted[ i ];
|
||||
|
||||
bool ok;
|
||||
const int pos = posV.toInt( &ok );
|
||||
if ( !ok )
|
||||
continue;
|
||||
|
||||
if ( pos < 0 || pos >= m_waitingForIds.size() )
|
||||
{
|
||||
qWarning() << Q_FUNC_INFO << "Got position that's not in the bounds of the tracks that we think we added... WTF?";
|
||||
continue;
|
||||
}
|
||||
|
||||
if ( !curEntries.contains( m_waitingForIds.at( pos ) ) )
|
||||
{
|
||||
qDebug() << Q_FUNC_INFO << "Got an id at a position for a plentry that's no longer in our playlist? WTF";
|
||||
continue;
|
||||
}
|
||||
|
||||
if ( i >= trackIdsInserted.size() )
|
||||
{
|
||||
qWarning() << Q_FUNC_INFO << "Help! Got more track positions than track IDS, wtf?";
|
||||
continue;
|
||||
}
|
||||
|
||||
qDebug() << "Setting annotation for track:" << m_waitingForIds[ pos ]->query()->track() << m_waitingForIds[ pos ]->query()->artist() << trackIdsInserted.at( i ).toString();
|
||||
m_waitingForIds[ pos ]->setAnnotation( trackIdsInserted.at( i ).toString() );
|
||||
changed << m_waitingForIds[ pos ];
|
||||
}
|
||||
|
||||
m_waitingForIds.clear();
|
||||
// Save our changes if we added some IDs
|
||||
if ( changed.size() > 0 )
|
||||
playlist()->updateEntries( uuid(), playlist()->currentrevision(), changed );
|
||||
|
||||
}
|
||||
|
||||
|
||||
void
|
||||
SpotifyPlaylistUpdater::tomahawkTracksRemoved( const QList< query_ptr >& tracks )
|
||||
{
|
||||
if ( m_spotify.isNull() )
|
||||
return;
|
||||
|
||||
if ( m_blockUpdatesForNextRevision )
|
||||
{
|
||||
qDebug() << "Ignoring tracks removed message since we just did a remove ourselves!";
|
||||
m_blockUpdatesForNextRevision = false;
|
||||
return;
|
||||
}
|
||||
|
||||
qDebug() << Q_FUNC_INFO << "updating spotify resolver with removed tracks:" << tracks;
|
||||
QVariantMap msg;
|
||||
msg[ "_msgtype" ] = "removeTracksFromPlaylist";
|
||||
msg[ "playlistid" ] = m_spotifyId;
|
||||
msg[ "oldrev" ] = m_latestRev;
|
||||
msg[ "tracks" ] = queriesToVariant( tracks );
|
||||
|
||||
m_spotify.data()->sendMessage( msg, this, "onTracksRemovedReturn" );
|
||||
}
|
||||
|
||||
|
||||
void
|
||||
SpotifyPlaylistUpdater::onTracksRemovedReturn( const QString& msgType, const QVariantMap& msg )
|
||||
{
|
||||
const bool success = msg.value( "success" ).toBool();
|
||||
|
||||
qDebug() << Q_FUNC_INFO << "GOT RETURN FOR tracksRemoved call from spotify!" << msgType << msg << "Succeeded?" << success;
|
||||
m_latestRev = msg.value( "revid" ).toString();
|
||||
}
|
||||
|
||||
|
||||
void
|
||||
SpotifyPlaylistUpdater::tomahawkTracksMoved( const QList< plentry_ptr >& tracks, int position )
|
||||
{
|
||||
if( playlist()->busy() )
|
||||
{
|
||||
// the playlist has had the new revision set, but it might not be finished, if it's not finished, playlist()->entries() still
|
||||
// contains the *old* order, so we get the wrong data
|
||||
m_queuedOps << NewClosure( 0, "", this, SLOT(tomahawkTracksMoved(QList<Tomahawk::plentry_ptr>,int)), tracks, position );
|
||||
return;
|
||||
}
|
||||
|
||||
qDebug() << Q_FUNC_INFO << "Got tracks moved at position:" << position;
|
||||
foreach ( const plentry_ptr ple, tracks )
|
||||
{
|
||||
qDebug() << ple->query()->track() << ple->query()->artist();
|
||||
}
|
||||
|
||||
qDebug() << Q_FUNC_INFO << "updating spotify resolver with moved tracks to:" << position;
|
||||
QVariantMap msg;
|
||||
msg[ "_msgtype" ] = "moveTracksInPlaylist";
|
||||
msg[ "oldrev" ] = m_latestRev;
|
||||
|
||||
// Find the trackid of the nearest spotify track
|
||||
QList< plentry_ptr > plTracks = playlist()->entries();
|
||||
Q_ASSERT( position-1 < plTracks.size() );
|
||||
|
||||
QString startPos;
|
||||
if ( position > 0 )
|
||||
startPos = nearestSpotifyTrack( plTracks, position );
|
||||
|
||||
msg[ "startPosition" ] = startPos;
|
||||
msg[ "playlistid" ] = m_spotifyId;
|
||||
|
||||
msg[ "tracks" ] = plentryToVariant( tracks );
|
||||
|
||||
m_spotify.data()->sendMessage( msg, this, "onTracksMovedReturn" );
|
||||
}
|
||||
|
||||
|
||||
void
|
||||
SpotifyPlaylistUpdater::onTracksMovedReturn( const QString& msgType, const QVariantMap& msg )
|
||||
{
|
||||
const bool success = msg.value( "success" ).toBool();
|
||||
|
||||
qDebug() << Q_FUNC_INFO << "GOT RETURN FOR tracksMoved call from spotify!" << msgType << msg << "Succeeded?" << success;
|
||||
m_latestRev = msg.value( "revid" ).toString();
|
||||
}
|
||||
|
||||
|
||||
QVariantList
|
||||
SpotifyPlaylistUpdater::queriesToVariant( const QList< query_ptr >& queries )
|
||||
{
|
||||
QVariantList tracksJson;
|
||||
foreach ( const query_ptr& q, queries )
|
||||
{
|
||||
QVariantMap m;
|
||||
if ( q.isNull() )
|
||||
continue;
|
||||
tracksJson << queryToVariant( q );
|
||||
}
|
||||
|
||||
return tracksJson;
|
||||
}
|
||||
|
||||
|
||||
QVariant
|
||||
SpotifyPlaylistUpdater::queryToVariant( const query_ptr& query )
|
||||
{
|
||||
QVariantMap m;
|
||||
m[ "track" ] = query->track();
|
||||
m[ "artist" ] = query->artist();
|
||||
m[ "album" ] = query->album();
|
||||
|
||||
if ( !query->property( "annotation" ).isNull() )
|
||||
m[ "id" ] = query->property( "annotation" );
|
||||
|
||||
return m;
|
||||
}
|
||||
|
||||
|
||||
QList< query_ptr >
|
||||
SpotifyPlaylistUpdater::variantToQueries( const QVariantList& list )
|
||||
{
|
||||
QList< query_ptr > queries;
|
||||
foreach ( const QVariant& blob, list )
|
||||
{
|
||||
QVariantMap trackMap = blob.toMap();
|
||||
const query_ptr q = Query::get( trackMap.value( "artist" ).toString(), trackMap.value( "track" ).toString(), trackMap.value( "album" ).toString(), uuid(), false );
|
||||
if ( trackMap.contains( "id" ) )
|
||||
q->setProperty( "annotation", trackMap.value( "id" ) );
|
||||
|
||||
queries << q;
|
||||
}
|
||||
|
||||
return queries;
|
||||
}
|
||||
|
118
src/accounts/spotify/SpotifyPlaylistUpdater.h
Normal file
118
src/accounts/spotify/SpotifyPlaylistUpdater.h
Normal file
@ -0,0 +1,118 @@
|
||||
/* === This file is part of Tomahawk Player - <http://tomahawk-player.org> ===
|
||||
*
|
||||
* Copyright 2010-2012, Leo Franchi <lfranchi@kde.org>
|
||||
*
|
||||
* Tomahawk is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
* the Free Software Foundation, either version 3 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* Tomahawk is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with Tomahawk. If not, see <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
#ifndef SPOTIFYPLAYLISTUPDATER_H
|
||||
#define SPOTIFYPLAYLISTUPDATER_H
|
||||
|
||||
#include "playlist/PlaylistUpdaterInterface.h"
|
||||
#include "utils/closure.h"
|
||||
|
||||
#include <QQueue>
|
||||
#include <QVariant>
|
||||
|
||||
namespace Tomahawk {
|
||||
namespace Accounts {
|
||||
class SpotifyAccount;
|
||||
}
|
||||
}
|
||||
|
||||
class SpotifyPlaylistUpdater : public Tomahawk::PlaylistUpdaterInterface
|
||||
{
|
||||
Q_OBJECT
|
||||
|
||||
friend class Tomahawk::Accounts::SpotifyAccount;
|
||||
public:
|
||||
SpotifyPlaylistUpdater( Tomahawk::Accounts::SpotifyAccount* acct, const QString& revid, const QString& spotifyId, const Tomahawk::playlist_ptr& pl );
|
||||
|
||||
virtual ~SpotifyPlaylistUpdater();
|
||||
|
||||
virtual QString type() const;
|
||||
virtual void updateNow() {}
|
||||
|
||||
#ifndef ENABLE_HEADLESS
|
||||
virtual QWidget* configurationWidget() const { return 0; }
|
||||
|
||||
virtual QPixmap typeIcon() const;
|
||||
#endif
|
||||
|
||||
bool sync() const;
|
||||
void setSync( bool sync );
|
||||
|
||||
QString spotifyId() const { return m_spotifyId; }
|
||||
|
||||
public slots:
|
||||
/// Spotify callbacks when we are directly instructed from the resolver
|
||||
void spotifyTracksAdded( const QVariantList& tracks, const QString& startPosId, const QString& newRev, const QString& oldRev );
|
||||
void spotifyTracksRemoved( const QVariantList& tracks, const QString& newRev, const QString& oldRev );
|
||||
void spotifyTracksMoved( const QVariantList& tracks, const QString& newStartPos, const QString& newRev, const QString& oldRev );
|
||||
void spotifyPlaylistRenamed( const QString& title, const QString& newRev, const QString& oldRev );
|
||||
|
||||
void tomahawkTracksInserted( const QList<Tomahawk::plentry_ptr>& ,int );
|
||||
void tomahawkTracksRemoved( const QList<Tomahawk::query_ptr>& );
|
||||
void tomahawkTracksMoved( const QList<Tomahawk::plentry_ptr>& ,int );
|
||||
void tomahawkPlaylistRenamed( const QString&, const QString& );
|
||||
protected:
|
||||
virtual void removeFromSettings(const QString& group) const;
|
||||
virtual void saveToSettings(const QString& group) const;
|
||||
|
||||
private slots:
|
||||
// SpotifyResolver message handlers, all take msgtype, msg as argument
|
||||
void onTracksInsertedReturn( const QString& msgType, const QVariantMap& msg );
|
||||
void onTracksRemovedReturn( const QString& msgType, const QVariantMap& msg );
|
||||
void onTracksMovedReturn( const QString& msgType, const QVariantMap& msg );
|
||||
|
||||
void checkDeleteDialog() const;
|
||||
|
||||
void playlistRevisionLoaded();
|
||||
private:
|
||||
void init();
|
||||
/// Finds the nearest spotify id from pos to the beginning of the playlist
|
||||
QString nearestSpotifyTrack( const QList< Tomahawk::plentry_ptr >& entries, int pos );
|
||||
QVariantList plentryToVariant( const QList< Tomahawk::plentry_ptr >& entries );
|
||||
|
||||
static QVariantList queriesToVariant( const QList< Tomahawk::query_ptr >& queries );
|
||||
static QVariant queryToVariant( const Tomahawk::query_ptr& query );
|
||||
static QList< Tomahawk::query_ptr > variantToQueries( const QVariantList& list );
|
||||
|
||||
QWeakPointer<Tomahawk::Accounts::SpotifyAccount> m_spotify;
|
||||
QString m_latestRev, m_spotifyId;
|
||||
QList< Tomahawk::plentry_ptr > m_waitingForIds;
|
||||
|
||||
bool m_blockUpdatesForNextRevision;
|
||||
bool m_sync;
|
||||
|
||||
QQueue<_detail::Closure*> m_queuedOps;
|
||||
#ifndef ENABLE_HEADLESS
|
||||
static QPixmap* s_typePixmap;
|
||||
#endif
|
||||
};
|
||||
|
||||
|
||||
class SpotifyUpdaterFactory : public Tomahawk::PlaylistUpdaterFactory
|
||||
{
|
||||
public:
|
||||
SpotifyUpdaterFactory() {}
|
||||
|
||||
virtual Tomahawk::PlaylistUpdaterInterface* create( const Tomahawk::playlist_ptr& pl, const QString& key );
|
||||
virtual QString type() const { return "spotify"; }
|
||||
|
||||
private:
|
||||
QWeakPointer<Tomahawk::Accounts::SpotifyAccount> m_account;
|
||||
};
|
||||
|
||||
#endif // SPOTIFYPLAYLISTUPDATER_H
|
@ -42,7 +42,7 @@ XmppAccountFactory::createAccount( const QString& accountId )
|
||||
XmppAccount::XmppAccount( const QString &accountId )
|
||||
: Account( accountId )
|
||||
{
|
||||
setAccountServiceName( "XMPP (Jabber)" );
|
||||
setAccountServiceName( "Jabber (XMPP)" );
|
||||
setTypes( SipType );
|
||||
|
||||
m_configWidget = QWeakPointer< QWidget >( new XmppConfigWidget( this, 0 ) );
|
||||
|
@ -47,7 +47,7 @@ public:
|
||||
XmppAccountFactory() {}
|
||||
virtual ~XmppAccountFactory() {}
|
||||
|
||||
QString prettyName() const { return "XMPP (Jabber)"; }
|
||||
QString prettyName() const { return "Jabber (XMPP)"; }
|
||||
QString description() const { return tr( "Log on to your Jabber/XMPP account to connect to your friends" ); }
|
||||
QString factoryId() const { return "xmppaccount"; }
|
||||
QPixmap icon() const { return QPixmap( ":/xmpp-icon.png" ); }
|
||||
|
@ -121,6 +121,7 @@ set( libGuiSources
|
||||
utils/tomahawkutilsgui.cpp
|
||||
utils/closure.cpp
|
||||
utils/PixmapDelegateFader.cpp
|
||||
utils/SmartPointerList.h
|
||||
|
||||
widgets/animatedcounterlabel.cpp
|
||||
widgets/checkdirtree.cpp
|
||||
|
@ -205,4 +205,4 @@ Account::types() const
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
|
@ -2,6 +2,7 @@
|
||||
*
|
||||
* Copyright 2010-2011, Christian Muehlhaeuser <muesli@tomahawk-player.org>
|
||||
* Copyright 2010-2012, Jeff Mitchell <jeff@tomahawk-player.org>
|
||||
* Copyright 2012, Leo Franchi <lfranchi@kde.org>
|
||||
*
|
||||
* Tomahawk is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
@ -37,6 +38,14 @@ ActionCollection::ActionCollection( QObject *parent )
|
||||
}
|
||||
|
||||
|
||||
ActionCollection::~ActionCollection()
|
||||
{
|
||||
s_instance = 0;
|
||||
foreach( QString key, m_actionCollection.keys() )
|
||||
delete m_actionCollection[ key ];
|
||||
}
|
||||
|
||||
|
||||
void
|
||||
ActionCollection::initActions()
|
||||
{
|
||||
@ -76,18 +85,54 @@ ActionCollection::initActions()
|
||||
}
|
||||
|
||||
|
||||
ActionCollection::~ActionCollection()
|
||||
void
|
||||
ActionCollection::addAction( ActionCollection::ActionDestination category, QAction* action, QObject* notify )
|
||||
{
|
||||
s_instance = 0;
|
||||
foreach( QString key, m_actionCollection.keys() )
|
||||
delete m_actionCollection[ key ];
|
||||
QList< QAction* > actions = m_categoryActions.value( category );
|
||||
actions.append( action );
|
||||
m_categoryActions[ category ] = actions;
|
||||
|
||||
if ( notify )
|
||||
m_actionNotifiers[ action ] = notify;
|
||||
}
|
||||
|
||||
|
||||
QAction*
|
||||
ActionCollection::getAction( const QString& name )
|
||||
{
|
||||
return m_actionCollection.contains( name ) ? m_actionCollection[ name ] : 0;
|
||||
return m_actionCollection.value( name, 0 );
|
||||
}
|
||||
|
||||
|
||||
QObject*
|
||||
ActionCollection::actionNotifier( QAction* action )
|
||||
{
|
||||
return m_actionNotifiers.value( action, 0 );
|
||||
}
|
||||
|
||||
|
||||
QList< QAction* >
|
||||
ActionCollection::getAction( ActionCollection::ActionDestination category )
|
||||
{
|
||||
return m_categoryActions.value( category );
|
||||
}
|
||||
|
||||
|
||||
void
|
||||
ActionCollection::removeAction( QAction* action )
|
||||
{
|
||||
removeAction( action, LocalPlaylists );
|
||||
}
|
||||
|
||||
|
||||
void
|
||||
ActionCollection::removeAction( QAction* action, ActionCollection::ActionDestination category )
|
||||
{
|
||||
QList< QAction* > actions = m_categoryActions.value( category );
|
||||
actions.removeAll( action );
|
||||
m_categoryActions[ category ] = actions;
|
||||
|
||||
m_actionNotifiers.remove( action );
|
||||
}
|
||||
|
||||
|
||||
|
@ -1,7 +1,8 @@
|
||||
/* === This file is part of Tomahawk Player - <http://tomahawk-player.org> ===
|
||||
*
|
||||
* Copyright 2010-2011, Christian Muehlhaeuser <muesli@tomahawk-player.org>
|
||||
* Copyright 2010-2012, Jeff Mitchell <jeff@tomahawk-player.org>
|
||||
* Copyright 2010-2011, Christian Muehlhaeuser <muesli@tomahawk-player.org
|
||||
* Copyright 2010-2012, Jeff Mitchell <jeff@tomahawk-player.org>>
|
||||
* Copyright 2012, Leo Franchi <lfranchi@kde.org>
|
||||
*
|
||||
* Tomahawk is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
@ -29,6 +30,12 @@ class DLLEXPORT ActionCollection : public QObject
|
||||
Q_OBJECT
|
||||
|
||||
public:
|
||||
// Categories for custom-registered actions
|
||||
enum ActionDestination {
|
||||
// Tracks, TODO
|
||||
LocalPlaylists = 0
|
||||
};
|
||||
|
||||
static ActionCollection* instance();
|
||||
|
||||
ActionCollection( QObject *parent);
|
||||
@ -37,6 +44,34 @@ public:
|
||||
void initActions();
|
||||
|
||||
QAction* getAction( const QString& name );
|
||||
QList< QAction* > getAction( ActionDestination category );
|
||||
QObject* actionNotifier( QAction* );
|
||||
|
||||
/**
|
||||
* Add an action for a specific category. The action will show up
|
||||
* where the relevant category is displayed.
|
||||
*
|
||||
* e.g. if you register a Playlist action, it will be shown when
|
||||
* there is a context menu shown for a playlist.
|
||||
*
|
||||
* When the QAction* is shown, it will have a "payload" property that is set
|
||||
* to the <specific type> that is being shown.
|
||||
*
|
||||
* Additionally you can pass a QObject* that will be notified before the given
|
||||
* action is shown. The slot "aboutToShow( QAction*, <specific type> ) will be called,
|
||||
*
|
||||
*
|
||||
* <specific type> corresponds to the category: playlist_ptr for Playlists, etc.
|
||||
*
|
||||
* The Action Collection takes ownership of the action. It's time to let go.
|
||||
*/
|
||||
void addAction( ActionDestination category, QAction* action, QObject* notify = 0 );
|
||||
|
||||
/**
|
||||
* Remove an action from one or all specific categories
|
||||
*/
|
||||
void removeAction( QAction* action );
|
||||
void removeAction( QAction* action, ActionDestination category );
|
||||
|
||||
public slots:
|
||||
void togglePrivateListeningMode();
|
||||
@ -48,6 +83,8 @@ private:
|
||||
static ActionCollection* s_instance;
|
||||
|
||||
QHash< QString, QAction* > m_actionCollection;
|
||||
QHash< ActionDestination, QList< QAction* > > m_categoryActions;
|
||||
QHash< QAction*, QObject* > m_actionNotifiers;
|
||||
};
|
||||
|
||||
#endif
|
||||
|
@ -77,6 +77,7 @@ DatabaseCommand_LoadPlaylistEntries::generateEntries( DatabaseImpl* dbi )
|
||||
|
||||
Tomahawk::query_ptr q = Tomahawk::Query::get( query.value( 2 ).toString(), query.value( 1 ).toString(), query.value( 3 ).toString() );
|
||||
q->setResultHint( query.value( 8 ).toString() );
|
||||
q->setProperty( "annotation", e->annotation() );
|
||||
e->setQuery( q );
|
||||
|
||||
m_entrymap.insert( e->guid(), e );
|
||||
|
@ -43,6 +43,7 @@ DatabaseCommand_SetPlaylistRevision::DatabaseCommand_SetPlaylistRevision(
|
||||
, m_oldrev( oldrev )
|
||||
, m_addedentries( addedentries )
|
||||
, m_entries( entries )
|
||||
, m_metadataUpdate( false )
|
||||
{
|
||||
Q_ASSERT( !newrev.isEmpty() );
|
||||
m_localOnly = ( newrev == oldrev );
|
||||
@ -57,6 +58,33 @@ DatabaseCommand_SetPlaylistRevision::DatabaseCommand_SetPlaylistRevision(
|
||||
}
|
||||
|
||||
|
||||
DatabaseCommand_SetPlaylistRevision::DatabaseCommand_SetPlaylistRevision(
|
||||
const source_ptr& s,
|
||||
const QString& playlistguid,
|
||||
const QString& newrev,
|
||||
const QString& oldrev,
|
||||
const QStringList& orderedguids,
|
||||
const QList<plentry_ptr>& entriesToUpdate )
|
||||
: DatabaseCommandLoggable( s )
|
||||
, m_applied( false )
|
||||
, m_newrev( newrev )
|
||||
, m_oldrev( oldrev )
|
||||
, m_entries( entriesToUpdate )
|
||||
, m_metadataUpdate( true )
|
||||
{
|
||||
Q_ASSERT( !newrev.isEmpty() );
|
||||
m_localOnly = false;
|
||||
|
||||
QVariantList tmp;
|
||||
foreach( const QString& s, orderedguids )
|
||||
tmp << s;
|
||||
|
||||
setOrderedguids( tmp );
|
||||
|
||||
setPlaylistguid( playlistguid );
|
||||
}
|
||||
|
||||
|
||||
void
|
||||
DatabaseCommand_SetPlaylistRevision::postCommitHook()
|
||||
{
|
||||
@ -105,7 +133,7 @@ DatabaseCommand_SetPlaylistRevision::exec( DatabaseImpl* lib )
|
||||
else
|
||||
{
|
||||
tDebug() << "Playlist:" << m_playlistguid << m_currentRevision << source()->friendlyName() << source()->id();
|
||||
throw "No such playlist, WTF?";
|
||||
Q_ASSERT_X( false, "DatabaseCommand_SetPlaylistRevision::exec", "No such playlist, WTF?" );
|
||||
return;
|
||||
}
|
||||
|
||||
@ -132,6 +160,26 @@ DatabaseCommand_SetPlaylistRevision::exec( DatabaseImpl* lib )
|
||||
|
||||
return;
|
||||
}
|
||||
else if ( m_metadataUpdate )
|
||||
{
|
||||
QString sql = "UPDATE playlist_item SET trackname = ?, artistname = ?, albumname = ?, annotation = ?, duration = ?, addedon = ?, addedby = ? WHERE guid = ?";
|
||||
adde.prepare( sql );
|
||||
|
||||
foreach( const plentry_ptr& e, m_entries )
|
||||
{
|
||||
|
||||
adde.bindValue( 0, e->query()->track() );
|
||||
adde.bindValue( 1, e->query()->artist() );
|
||||
adde.bindValue( 2, e->query()->album() );
|
||||
adde.bindValue( 3, e->annotation() );
|
||||
adde.bindValue( 4, (int) e->duration() );
|
||||
adde.bindValue( 5, e->lastmodified() );
|
||||
adde.bindValue( 6, source()->isLocal() ? QVariant(QVariant::Int) : source()->id() );
|
||||
adde.bindValue( 7, e->guid() );
|
||||
|
||||
adde.exec();
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
QString sql = "INSERT INTO playlist_item( guid, playlist, trackname, artistname, albumname, "
|
||||
@ -142,6 +190,8 @@ DatabaseCommand_SetPlaylistRevision::exec( DatabaseImpl* lib )
|
||||
qDebug() << "Num new playlist_items to add:" << m_addedentries.length();
|
||||
foreach( const plentry_ptr& e, m_addedentries )
|
||||
{
|
||||
// qDebug() << "Adding:" << e->guid() << e->query()->track() << e->query()->artist();
|
||||
|
||||
m_addedmap.insert( e->guid(), e ); // needed in postcommithook
|
||||
|
||||
QString resultHint;
|
||||
|
@ -35,15 +35,18 @@ Q_PROPERTY( QString newrev READ newrev WRITE setNewrev )
|
||||
Q_PROPERTY( QString oldrev READ oldrev WRITE setOldrev )
|
||||
Q_PROPERTY( QVariantList orderedguids READ orderedguids WRITE setOrderedguids )
|
||||
Q_PROPERTY( QVariantList addedentries READ addedentriesV WRITE setAddedentriesV )
|
||||
Q_PROPERTY( bool metadataUpdate READ metadataUpdate WRITE setMetadataUpdate )
|
||||
|
||||
public:
|
||||
explicit DatabaseCommand_SetPlaylistRevision( QObject* parent = 0 )
|
||||
: DatabaseCommandLoggable( parent )
|
||||
, m_applied( false )
|
||||
, m_localOnly( false )
|
||||
, m_metadataUpdate( false )
|
||||
{}
|
||||
|
||||
explicit DatabaseCommand_SetPlaylistRevision( const source_ptr& s,
|
||||
// Constructor for inserting or removing entries
|
||||
DatabaseCommand_SetPlaylistRevision( const source_ptr& s,
|
||||
const QString& playlistguid,
|
||||
const QString& newrev,
|
||||
const QString& oldrev,
|
||||
@ -51,6 +54,15 @@ public:
|
||||
const QList<Tomahawk::plentry_ptr>& addedentries,
|
||||
const QList<Tomahawk::plentry_ptr>& entries );
|
||||
|
||||
// constructor for updating metadata only
|
||||
DatabaseCommand_SetPlaylistRevision( const source_ptr& s,
|
||||
const QString& playlistguid,
|
||||
const QString& newrev,
|
||||
const QString& oldrev,
|
||||
const QStringList& orderedguids,
|
||||
const QList<Tomahawk::plentry_ptr>& entriesToUpdate );
|
||||
|
||||
|
||||
QString commandname() const { return "setplaylistrevision"; }
|
||||
|
||||
virtual void exec( DatabaseImpl* lib );
|
||||
@ -89,6 +101,8 @@ public:
|
||||
QString newrev() const { return m_newrev; }
|
||||
QString oldrev() const { return m_oldrev; }
|
||||
QString playlistguid() const { return m_playlistguid; }
|
||||
bool metadataUpdate() const { return m_metadataUpdate; }
|
||||
void setMetadataUpdate( bool metadataUpdate ) { m_metadataUpdate = metadataUpdate; }
|
||||
|
||||
void setOrderedguids( const QVariantList& l ) { m_orderedguids = l; }
|
||||
QVariantList orderedguids() const { return m_orderedguids; }
|
||||
@ -106,7 +120,7 @@ private:
|
||||
QVariantList m_orderedguids;
|
||||
QList<Tomahawk::plentry_ptr> m_addedentries, m_entries;
|
||||
|
||||
bool m_localOnly;
|
||||
bool m_localOnly, m_metadataUpdate;
|
||||
};
|
||||
|
||||
#endif // DATABASECOMMAND_SETPLAYLISTREVISION_H
|
||||
|
@ -1097,7 +1097,10 @@ GlobalActionManager::doBookmark( const playlist_ptr& pl, const query_ptr& q )
|
||||
e->setDuration( 0 );
|
||||
|
||||
e->setLastmodified( 0 );
|
||||
e->setAnnotation( "" ); // FIXME
|
||||
QString annotation = "";
|
||||
if ( !q->property( "annotation" ).toString().isEmpty() )
|
||||
annotation = q->property( "annotation" ).toString();
|
||||
e->setAnnotation( annotation );
|
||||
e->setQuery( q );
|
||||
|
||||
pl->createNewRevision( uuid(), pl->currentrevision(), QList< plentry_ptr >( pl->entries() ) << e );
|
||||
|
@ -40,6 +40,8 @@ using namespace Tomahawk;
|
||||
InfoBar::InfoBar( QWidget* parent )
|
||||
: QWidget( parent )
|
||||
, ui( new Ui::InfoBar )
|
||||
, m_updaterInterface( 0 )
|
||||
, m_updaterConfiguration( 0 )
|
||||
, m_queryLabel( 0 )
|
||||
{
|
||||
ui->setupUi( this );
|
||||
@ -59,12 +61,12 @@ InfoBar::InfoBar( QWidget* parent )
|
||||
regFont.setPixelSize( 11 );
|
||||
ui->longDescriptionLabel->setFont( regFont );
|
||||
|
||||
QPalette whitePal = ui->captionLabel->palette();
|
||||
whitePal.setColor( QPalette::Foreground, Qt::white );
|
||||
m_whitePal = ui->captionLabel->palette();
|
||||
m_whitePal.setColor( QPalette::Foreground, Qt::white );
|
||||
|
||||
ui->captionLabel->setPalette( whitePal );
|
||||
ui->descriptionLabel->setPalette( whitePal );
|
||||
ui->longDescriptionLabel->setPalette( whitePal );
|
||||
ui->captionLabel->setPalette( m_whitePal );
|
||||
ui->descriptionLabel->setPalette( m_whitePal );
|
||||
ui->longDescriptionLabel->setPalette( m_whitePal );
|
||||
|
||||
ui->captionLabel->setMargin( 6 );
|
||||
ui->descriptionLabel->setMargin( 6 );
|
||||
@ -83,14 +85,6 @@ InfoBar::InfoBar( QWidget* parent )
|
||||
m_queryLabel->hide();
|
||||
connect( m_queryLabel, SIGNAL( clickedArtist() ), this, SLOT( artistClicked() ) );
|
||||
|
||||
m_autoUpdate = new QCheckBox( this );
|
||||
m_autoUpdate->setText( tr( "Automatically update" ) );
|
||||
m_autoUpdate->setLayoutDirection( Qt::RightToLeft );
|
||||
m_autoUpdate->setPalette( whitePal );
|
||||
connect( m_autoUpdate, SIGNAL( toggled( bool ) ), this, SIGNAL( autoUpdateChanged( bool ) ) );
|
||||
|
||||
ui->horizontalLayout->addWidget( m_autoUpdate );
|
||||
|
||||
m_searchWidget = new QSearchField( this );
|
||||
m_searchWidget->setPlaceholderText( tr( "Filter..." ) );
|
||||
m_searchWidget->setMinimumWidth( 180 );
|
||||
@ -106,7 +100,6 @@ InfoBar::InfoBar( QWidget* parent )
|
||||
createTile();
|
||||
|
||||
connect( ViewManager::instance(), SIGNAL( filterAvailable( bool ) ), SLOT( setFilterAvailable( bool ) ) );
|
||||
connect( ViewManager::instance(), SIGNAL( autoUpdateAvailable( bool ) ), SLOT( setAutoUpdateAvailable( bool ) ) );
|
||||
}
|
||||
|
||||
|
||||
@ -207,13 +200,36 @@ InfoBar::setFilterAvailable( bool b )
|
||||
m_searchWidget->setVisible( b );
|
||||
}
|
||||
|
||||
void
|
||||
InfoBar::setAutoUpdateAvailable( bool b )
|
||||
{
|
||||
if ( b )
|
||||
m_autoUpdate->setChecked( ViewManager::instance()->currentPage()->autoUpdate() );
|
||||
|
||||
m_autoUpdate->setVisible( b );
|
||||
void
|
||||
InfoBar::setAutoUpdateInterface( PlaylistUpdaterInterface *interface )
|
||||
{
|
||||
if ( m_updaterConfiguration )
|
||||
m_updaterConfiguration->hide();
|
||||
|
||||
if ( m_updaterConfiguration && ( interface ? (m_updaterConfiguration != interface->configurationWidget()) : true ) )
|
||||
ui->horizontalLayout->removeWidget( m_updaterConfiguration );
|
||||
|
||||
m_updaterInterface = interface;
|
||||
m_updaterConfiguration = interface ? interface->configurationWidget() : 0;
|
||||
|
||||
if ( !m_updaterInterface || !m_updaterConfiguration )
|
||||
return;
|
||||
|
||||
m_updaterConfiguration->setPalette( m_whitePal );
|
||||
int insertIdx = -1; // Ugh, no indexOf for QSpacerItem*
|
||||
for ( int i = 0; i < ui->horizontalLayout->count(); i++ )
|
||||
{
|
||||
if ( ui->horizontalLayout->itemAt( i )->spacerItem() == ui->horizontalSpacer_4 )
|
||||
{
|
||||
insertIdx = i;
|
||||
break;
|
||||
}
|
||||
}
|
||||
insertIdx++;
|
||||
ui->horizontalLayout->insertWidget( insertIdx, m_updaterConfiguration );
|
||||
|
||||
m_updaterConfiguration->show();
|
||||
}
|
||||
|
||||
|
||||
|
@ -35,6 +35,11 @@ namespace Ui
|
||||
class InfoBar;
|
||||
}
|
||||
|
||||
namespace Tomahawk
|
||||
{
|
||||
class PlaylistUpdaterInterface;
|
||||
}
|
||||
|
||||
class DLLEXPORT InfoBar : public QWidget
|
||||
{
|
||||
Q_OBJECT
|
||||
@ -57,10 +62,9 @@ public slots:
|
||||
void setFilter( const QString& filter );
|
||||
void setFilterAvailable( bool b );
|
||||
|
||||
void setAutoUpdateAvailable( bool b );
|
||||
void setAutoUpdateInterface( Tomahawk::PlaylistUpdaterInterface* interface );
|
||||
signals:
|
||||
void filterTextChanged( const QString& filter );
|
||||
void autoUpdateChanged( bool checked );
|
||||
|
||||
protected:
|
||||
void changeEvent( QEvent* e );
|
||||
@ -76,9 +80,12 @@ private:
|
||||
Ui::InfoBar* ui;
|
||||
|
||||
QPixmap m_bgTile;
|
||||
QPalette m_whitePal;
|
||||
|
||||
Tomahawk::PlaylistUpdaterInterface* m_updaterInterface;
|
||||
QWidget* m_updaterConfiguration;
|
||||
|
||||
QSearchField* m_searchWidget;
|
||||
QCheckBox* m_autoUpdate;
|
||||
QueryLabel* m_queryLabel;
|
||||
};
|
||||
|
||||
|
@ -1,6 +1,7 @@
|
||||
/* === This file is part of Tomahawk Player - <http://tomahawk-player.org> ===
|
||||
*
|
||||
* Copyright 2010-2011, Christian Muehlhaeuser <muesli@tomahawk-player.org>
|
||||
* Copyright 2010-2011, Leo Franchi <lfranchi@kde.org>
|
||||
* Copyright 2010-2012, Jeff Mitchell <jeff@tomahawk-player.org>
|
||||
*
|
||||
* Tomahawk is free software: you can redistribute it and/or modify
|
||||
@ -95,7 +96,6 @@ PlaylistEntry::setLastSource( source_ptr s )
|
||||
Playlist::Playlist( const source_ptr& author )
|
||||
: m_source( author )
|
||||
, m_lastmodified( 0 )
|
||||
, m_updater( 0 )
|
||||
{
|
||||
}
|
||||
|
||||
@ -120,7 +120,6 @@ Playlist::Playlist( const source_ptr& src,
|
||||
, m_lastmodified( lastmod )
|
||||
, m_createdOn( createdOn )
|
||||
, m_shared( shared )
|
||||
, m_updater( 0 )
|
||||
{
|
||||
init();
|
||||
}
|
||||
@ -143,7 +142,6 @@ Playlist::Playlist( const source_ptr& author,
|
||||
, m_createdOn( 0 ) // will be set by db command
|
||||
, m_shared( shared )
|
||||
, m_initEntries( entries )
|
||||
, m_updater( 0 )
|
||||
{
|
||||
init();
|
||||
}
|
||||
@ -180,7 +178,10 @@ Playlist::create( const source_ptr& author,
|
||||
p->setGuid( uuid() );
|
||||
p->setDuration( query->duration() );
|
||||
p->setLastmodified( 0 );
|
||||
p->setAnnotation( "" );
|
||||
QString annotation = "";
|
||||
if ( !query->property( "annotation" ).toString().isEmpty() )
|
||||
annotation = query->property( "annotation" ).toString();
|
||||
p->setAnnotation( annotation );
|
||||
p->setQuery( query );
|
||||
|
||||
entries << p;
|
||||
@ -244,6 +245,18 @@ Playlist::rename( const QString& title )
|
||||
Database::instance()->enqueue( QSharedPointer<DatabaseCommand>(cmd) );
|
||||
}
|
||||
|
||||
void
|
||||
Playlist::setTitle( const QString& title )
|
||||
{
|
||||
if ( title == m_title )
|
||||
return;
|
||||
|
||||
const QString oldTitle = m_title;
|
||||
m_title = title;
|
||||
|
||||
emit changed();
|
||||
emit renamed( m_title, oldTitle );
|
||||
}
|
||||
|
||||
void
|
||||
Playlist::reportCreated( const playlist_ptr& self )
|
||||
@ -257,8 +270,8 @@ void
|
||||
Playlist::reportDeleted( const Tomahawk::playlist_ptr& self )
|
||||
{
|
||||
Q_ASSERT( self.data() == this );
|
||||
if ( m_updater )
|
||||
m_updater->remove();
|
||||
if ( !m_updater.isNull() )
|
||||
m_updater.data()->remove();
|
||||
|
||||
m_deleted = true;
|
||||
m_source->collection()->deletePlaylist( self );
|
||||
@ -266,6 +279,28 @@ Playlist::reportDeleted( const Tomahawk::playlist_ptr& self )
|
||||
emit deleted( self );
|
||||
}
|
||||
|
||||
void
|
||||
Playlist::setUpdater( PlaylistUpdaterInterface* pluinterface )
|
||||
{
|
||||
if ( !m_updater.isNull() )
|
||||
disconnect( m_updater.data(), SIGNAL( changed() ), this, SIGNAL( changed() ) );
|
||||
|
||||
m_updater = QWeakPointer< PlaylistUpdaterInterface >( pluinterface );
|
||||
|
||||
connect( m_updater.data(), SIGNAL( changed() ), this, SIGNAL( changed() ), Qt::UniqueConnection );
|
||||
connect( m_updater.data(), SIGNAL( destroyed( QObject* ) ), this, SLOT( updaterDestroyed() ), Qt::QueuedConnection );
|
||||
|
||||
emit changed();
|
||||
}
|
||||
|
||||
|
||||
void
|
||||
Playlist::updaterDestroyed()
|
||||
{
|
||||
m_updater.clear();
|
||||
emit changed();
|
||||
}
|
||||
|
||||
|
||||
void
|
||||
Playlist::loadRevision( const QString& rev )
|
||||
@ -312,8 +347,15 @@ Playlist::createNewRevision( const QString& newrev, const QString& oldrev, const
|
||||
// calc list of newly added entries:
|
||||
QList<plentry_ptr> added = newEntries( entries );
|
||||
QStringList orderedguids;
|
||||
qDebug() << "Inserting ordered GUIDs:";
|
||||
foreach( const plentry_ptr& p, entries )
|
||||
{
|
||||
qDebug() << p->guid() << p->query()->track() << p->query()->artist();
|
||||
orderedguids << p->guid();
|
||||
}
|
||||
|
||||
foreach( const plentry_ptr& p, added )
|
||||
qDebug() << p->guid();
|
||||
|
||||
// source making the change (local user in this case)
|
||||
source_ptr author = SourceList::instance()->getLocal();
|
||||
@ -331,6 +373,38 @@ Playlist::createNewRevision( const QString& newrev, const QString& oldrev, const
|
||||
}
|
||||
|
||||
|
||||
void
|
||||
Playlist::updateEntries( const QString& newrev, const QString& oldrev, const QList< plentry_ptr >& entries )
|
||||
{
|
||||
tDebug() << Q_FUNC_INFO << newrev << oldrev << entries.count();
|
||||
Q_ASSERT( m_source->isLocal() || newrev == oldrev );
|
||||
|
||||
if ( busy() )
|
||||
{
|
||||
m_updateQueue.enqueue( RevisionQueueItem( newrev, oldrev, entries, oldrev == currentrevision() ) );
|
||||
return;
|
||||
}
|
||||
|
||||
if ( newrev != oldrev )
|
||||
setBusy( true );
|
||||
|
||||
QStringList orderedguids;
|
||||
foreach( const plentry_ptr& p, m_entries )
|
||||
orderedguids << p->guid();
|
||||
|
||||
qDebug() << "Updating playlist metadata:" << entries;
|
||||
DatabaseCommand_SetPlaylistRevision* cmd =
|
||||
new DatabaseCommand_SetPlaylistRevision( SourceList::instance()->getLocal(),
|
||||
guid(),
|
||||
newrev,
|
||||
oldrev,
|
||||
orderedguids,
|
||||
entries );
|
||||
|
||||
Database::instance()->enqueue( QSharedPointer<DatabaseCommand>( cmd ) );
|
||||
}
|
||||
|
||||
|
||||
// private, called when we loadRevision, or by our friend class DatabaseCommand_SetPlaylistRevision
|
||||
// used to save new revision data (either originating locally, or via network msg for syncing)
|
||||
void
|
||||
@ -395,20 +469,24 @@ Playlist::setNewRevision( const QString& rev,
|
||||
// existing ones, and the ones that have been added
|
||||
QMap<QString, plentry_ptr> entriesmap;
|
||||
foreach ( const plentry_ptr& p, m_entries )
|
||||
{
|
||||
qDebug() << p->guid() << p->query()->track() << p->query()->artist();
|
||||
entriesmap.insert( p->guid(), p );
|
||||
}
|
||||
|
||||
|
||||
// re-build m_entries from neworderedguids. plentries come either from the old m_entries OR addedmap.
|
||||
m_entries.clear();
|
||||
|
||||
QList<plentry_ptr> entries;
|
||||
foreach ( const QString& id, neworderedguids )
|
||||
{
|
||||
if ( entriesmap.contains( id ) )
|
||||
{
|
||||
entries.append( entriesmap.value( id ) );
|
||||
m_entries.append( entriesmap.value( id ) );
|
||||
}
|
||||
else if ( addedmap.contains( id ) )
|
||||
{
|
||||
entries.append( addedmap.value( id ) );
|
||||
if ( is_newest_rev )
|
||||
m_entries.append( addedmap.value( id ) );
|
||||
m_entries.append( addedmap.value( id ) );
|
||||
}
|
||||
else
|
||||
{
|
||||
@ -426,35 +504,8 @@ Playlist::setNewRevision( const QString& rev,
|
||||
PlaylistRevision pr;
|
||||
pr.oldrevisionguid = m_currentrevision;
|
||||
pr.revisionguid = rev;
|
||||
|
||||
// entries that have been removed:
|
||||
QSet<QString> removedguids = oldorderedguids.toSet().subtract( neworderedguids.toSet() );
|
||||
//qDebug() << "Removedguids:" << removedguids << "oldorederedguids" << oldorderedguids << "newog" << neworderedguids;
|
||||
foreach ( QString remid, removedguids )
|
||||
{
|
||||
// NB: entriesmap will contain old/removed entries only if the removal was done
|
||||
// in the same session - after a restart, history is not in memory.
|
||||
if ( entriesmap.contains( remid ) )
|
||||
{
|
||||
pr.removed << entriesmap.value( remid );
|
||||
if ( is_newest_rev )
|
||||
{
|
||||
//qDebug() << "Removing from m_entries" << remid;
|
||||
for ( int k = 0 ; k < m_entries.length(); ++k )
|
||||
{
|
||||
if ( m_entries.at( k )->guid() == remid )
|
||||
{
|
||||
//qDebug() << "removed at" << k;
|
||||
m_entries.removeAt( k );
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
pr.added = addedmap.values();
|
||||
pr.newlist = entries;
|
||||
pr.newlist = m_entries;
|
||||
|
||||
return pr;
|
||||
}
|
||||
@ -514,8 +565,44 @@ Playlist::addEntries( const QList<query_ptr>& queries, const QString& oldrev )
|
||||
{
|
||||
QList<plentry_ptr> el = entriesFromQueries( queries );
|
||||
|
||||
const int prevSize = m_entries.size();
|
||||
|
||||
QString newrev = uuid();
|
||||
createNewRevision( newrev, oldrev, el );
|
||||
|
||||
|
||||
// We are appending at end, so notify listeners.
|
||||
// PlaylistModel also emits during appends, but since we call
|
||||
// createNewRevision, it reloads instead of appends.
|
||||
const QList<plentry_ptr> added = el.mid( prevSize );
|
||||
qDebug() << "Playlist got" << queries.size() << "tracks added, emitting tracksInserted with:" << added.size() << "at pos:" << prevSize - 1;
|
||||
emit tracksInserted( added, prevSize );
|
||||
}
|
||||
|
||||
void
|
||||
Playlist::insertEntries( const QList< query_ptr >& queries, const int position, const QString& oldrev )
|
||||
{
|
||||
QList<plentry_ptr> toInsert = entriesFromQueries( queries, true );
|
||||
QList<plentry_ptr> entries = m_entries;
|
||||
|
||||
Q_ASSERT( position <= m_entries.size() );
|
||||
if ( position > m_entries.size() )
|
||||
{
|
||||
qWarning() << "ERROR trying to insert tracks past end of playlist! Appending!!";
|
||||
addEntries( queries, oldrev );
|
||||
return;
|
||||
}
|
||||
|
||||
for ( int i = toInsert.size()-1; i >= 0; --i )
|
||||
entries.insert( position, toInsert.at(i) );
|
||||
|
||||
createNewRevision( uuid(), oldrev, entries );
|
||||
|
||||
// We are appending at end, so notify listeners.
|
||||
// PlaylistModel also emits during appends, but since we call
|
||||
// createNewRevision, it reloads instead of appends.
|
||||
qDebug() << "Playlist got" << toInsert.size() << "tracks added, emitting tracksInserted at pos:" << position;
|
||||
emit tracksInserted( toInsert, position );
|
||||
}
|
||||
|
||||
|
||||
@ -537,7 +624,10 @@ Playlist::entriesFromQueries( const QList<Tomahawk::query_ptr>& queries, bool cl
|
||||
e->setDuration( 0 );
|
||||
|
||||
e->setLastmodified( 0 );
|
||||
e->setAnnotation( "" ); // FIXME
|
||||
QString annotation = "";
|
||||
if ( !query->property( "annotation" ).toString().isEmpty() )
|
||||
annotation = query->property( "annotation" ).toString();
|
||||
e->setAnnotation( annotation ); // FIXME
|
||||
e->setQuery( query );
|
||||
|
||||
el << e;
|
||||
@ -592,6 +682,23 @@ Playlist::checkRevisionQueue()
|
||||
}
|
||||
createNewRevision( item.newRev, item.oldRev, item.entries );
|
||||
}
|
||||
if ( !m_updateQueue.isEmpty() )
|
||||
{
|
||||
RevisionQueueItem item = m_updateQueue.dequeue();
|
||||
|
||||
if ( item.oldRev != currentrevision() && item.applyToTip )
|
||||
{
|
||||
// this was applied to the then-latest, but the already-running operation changed it so it's out of date now. fix it
|
||||
if ( item.oldRev == item.newRev )
|
||||
{
|
||||
checkRevisionQueue();
|
||||
return;
|
||||
}
|
||||
|
||||
item.oldRev = currentrevision();
|
||||
}
|
||||
updateEntries( item.newRev, item.oldRev, item.entries );
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
|
@ -1,6 +1,7 @@
|
||||
/* === This file is part of Tomahawk Player - <http://tomahawk-player.org> ===
|
||||
*
|
||||
* Copyright 2010-2011, Christian Muehlhaeuser <muesli@tomahawk-player.org>
|
||||
* Copyright 2011920-2011, Christian Muehlhaeuser <muesli@tomahawk-player.org>
|
||||
* Copyright 2010-2011, Leo Franchi <lfranchi@kde.org>
|
||||
* Copyright 2010-2012, Jeff Mitchell <jeff@tomahawk-player.org>
|
||||
*
|
||||
* Tomahawk is free software: you can redistribute it and/or modify
|
||||
@ -38,6 +39,8 @@ class DatabaseCommand_LoadAllPlaylists;
|
||||
class DatabaseCommand_LoadAllSortedPlaylists;
|
||||
class DatabaseCommand_SetPlaylistRevision;
|
||||
class DatabaseCommand_CreatePlaylist;
|
||||
class PlaylistModel;
|
||||
|
||||
namespace Tomahawk
|
||||
{
|
||||
|
||||
@ -117,20 +120,21 @@ public:
|
||||
|
||||
class DLLEXPORT Playlist : public QObject
|
||||
{
|
||||
Q_OBJECT
|
||||
Q_PROPERTY( QString guid READ guid WRITE setGuid )
|
||||
Q_PROPERTY( QString currentrevision READ currentrevision WRITE setCurrentrevision )
|
||||
Q_PROPERTY( QString title READ title WRITE setTitle )
|
||||
Q_PROPERTY( QString info READ info WRITE setInfo )
|
||||
Q_PROPERTY( QString creator READ creator WRITE setCreator )
|
||||
Q_PROPERTY( unsigned int createdon READ createdOn WRITE setCreatedOn )
|
||||
Q_PROPERTY( bool shared READ shared WRITE setShared )
|
||||
Q_OBJECT
|
||||
Q_PROPERTY( QString guid READ guid WRITE setGuid )
|
||||
Q_PROPERTY( QString currentrevision READ currentrevision WRITE setCurrentrevision )
|
||||
Q_PROPERTY( QString title READ title WRITE setTitle )
|
||||
Q_PROPERTY( QString info READ info WRITE setInfo )
|
||||
Q_PROPERTY( QString creator READ creator WRITE setCreator )
|
||||
Q_PROPERTY( unsigned int createdon READ createdOn WRITE setCreatedOn )
|
||||
Q_PROPERTY( bool shared READ shared WRITE setShared )
|
||||
|
||||
friend class ::DatabaseCommand_LoadAllPlaylists;
|
||||
friend class ::DatabaseCommand_LoadAllSortedPlaylists;
|
||||
friend class ::DatabaseCommand_SetPlaylistRevision;
|
||||
friend class ::DatabaseCommand_CreatePlaylist;
|
||||
friend class DynamicPlaylist;
|
||||
friend class ::PlaylistModel;
|
||||
|
||||
public:
|
||||
virtual ~Playlist();
|
||||
@ -166,6 +170,7 @@ public:
|
||||
const QList< plentry_ptr >& entries() { return m_entries; }
|
||||
virtual void addEntry( const Tomahawk::query_ptr& query, const QString& oldrev );
|
||||
virtual void addEntries( const QList<Tomahawk::query_ptr>& queries, const QString& oldrev );
|
||||
virtual void insertEntries( const QList<Tomahawk::query_ptr>& queries, const int position, const QString& oldrev );
|
||||
|
||||
// <IGNORE hack="true">
|
||||
// these need to exist and be public for the json serialization stuff
|
||||
@ -173,18 +178,18 @@ public:
|
||||
// maybe friend QObjectHelper and make them private?
|
||||
explicit Playlist( const source_ptr& author );
|
||||
void setCurrentrevision( const QString& s ) { m_currentrevision = s; }
|
||||
void setTitle( const QString& s ) { m_title = s; emit changed(); }
|
||||
void setInfo( const QString& s ) { m_info = s; }
|
||||
void setCreator( const QString& s ) { m_creator = s; }
|
||||
void setGuid( const QString& s ) { m_guid = s; }
|
||||
void setShared( bool b ) { m_shared = b; }
|
||||
void setCreatedOn( uint createdOn ) { m_createdOn = createdOn; }
|
||||
void setTitle( const QString& s );
|
||||
// </IGNORE>
|
||||
|
||||
|
||||
QList<plentry_ptr> entriesFromQueries( const QList<Tomahawk::query_ptr>& queries, bool clearFirst = false );
|
||||
void setUpdater( PlaylistUpdaterInterface* pluinterface ) { m_updater = pluinterface; }
|
||||
PlaylistUpdaterInterface* updater() const { return m_updater; }
|
||||
void setUpdater( PlaylistUpdaterInterface* pluinterface );
|
||||
PlaylistUpdaterInterface* updater() const { return m_updater.data(); }
|
||||
|
||||
Tomahawk::playlistinterface_ptr playlistInterface();
|
||||
|
||||
@ -197,6 +202,7 @@ signals:
|
||||
|
||||
/// renamed etc.
|
||||
void changed();
|
||||
void renamed( const QString& newTitle, const QString& oldTitle );
|
||||
|
||||
/**
|
||||
* delete command is scheduled but not completed. Do not call remove() again once this
|
||||
@ -207,10 +213,27 @@ signals:
|
||||
/// was deleted, eh?
|
||||
void deleted( const Tomahawk::playlist_ptr& pl );
|
||||
|
||||
/// Notification for tracks being inserted at a specific point
|
||||
/// Contiguous range from startPosition
|
||||
void tracksInserted( const QList< Tomahawk::plentry_ptr >& tracks, int startPosition );
|
||||
|
||||
/// Notification for tracks being removed from playlist
|
||||
void tracksRemoved( const QList< Tomahawk::query_ptr >& tracks );
|
||||
|
||||
/// Notification for tracks being moved in a playlist. List is of new tracks, and new position of first track
|
||||
/// Contiguous range from startPosition
|
||||
void tracksMoved( const QList< Tomahawk::plentry_ptr >& tracks, int startPosition );
|
||||
|
||||
public slots:
|
||||
// want to update the playlist from the model?
|
||||
// generate a newrev using uuid() and call this:
|
||||
void createNewRevision( const QString& newrev, const QString& oldrev, const QList< plentry_ptr >& entries );
|
||||
|
||||
// Want to update some metadata of a plentry_ptr? this gets you a new revision too.
|
||||
// entries should be <= entries(), with changed metadata.
|
||||
void updateEntries( const QString& newrev, const QString& oldrev, const QList< plentry_ptr >& entries );
|
||||
|
||||
|
||||
void reportCreated( const Tomahawk::playlist_ptr& self );
|
||||
void reportDeleted( const Tomahawk::playlist_ptr& self );
|
||||
|
||||
@ -255,6 +278,7 @@ protected:
|
||||
private slots:
|
||||
void onResultsFound( const QList<Tomahawk::result_ptr>& results );
|
||||
void onResolvingFinished();
|
||||
void updaterDestroyed();
|
||||
|
||||
private:
|
||||
Playlist();
|
||||
@ -274,8 +298,9 @@ private:
|
||||
QList< plentry_ptr > m_entries;
|
||||
|
||||
QQueue<RevisionQueueItem> m_revisionQueue;
|
||||
QQueue<RevisionQueueItem> m_updateQueue;
|
||||
|
||||
PlaylistUpdaterInterface* m_updater;
|
||||
QWeakPointer<PlaylistUpdaterInterface> m_updater;
|
||||
|
||||
bool m_locallyChanged;
|
||||
bool m_deleted;
|
||||
@ -287,5 +312,6 @@ private:
|
||||
}
|
||||
|
||||
Q_DECLARE_METATYPE( QSharedPointer< Tomahawk::Playlist > )
|
||||
Q_DECLARE_METATYPE( QList< QSharedPointer< Tomahawk::PlaylistEntry > > )
|
||||
|
||||
#endif // PLAYLIST_H
|
||||
|
@ -23,6 +23,8 @@
|
||||
#include <QTextOption>
|
||||
|
||||
#include "dllmacro.h"
|
||||
#include "typedefs.h"
|
||||
|
||||
|
||||
namespace Tomahawk {
|
||||
class PixmapDelegateFader;
|
||||
|
@ -24,7 +24,7 @@
|
||||
#include <QTextOption>
|
||||
|
||||
#include "dllmacro.h"
|
||||
#include <signal.h>
|
||||
#include "typedefs.h"
|
||||
|
||||
namespace Tomahawk {
|
||||
class PixmapDelegateFader;
|
||||
|
@ -18,10 +18,19 @@
|
||||
|
||||
#include "PlaylistUpdaterInterface.h"
|
||||
#include "tomahawksettings.h"
|
||||
#include "XspfUpdater.h"
|
||||
|
||||
using namespace Tomahawk;
|
||||
|
||||
QMap< QString, PlaylistUpdaterFactory* > PlaylistUpdaterInterface::s_factories = QMap< QString, PlaylistUpdaterFactory* >();
|
||||
|
||||
void
|
||||
PlaylistUpdaterInterface::registerUpdaterFactory( PlaylistUpdaterFactory* f )
|
||||
{
|
||||
s_factories[ f->type() ] = f;
|
||||
}
|
||||
|
||||
|
||||
|
||||
PlaylistUpdaterInterface*
|
||||
PlaylistUpdaterInterface::loadForPlaylist( const playlist_ptr& pl )
|
||||
{
|
||||
@ -32,19 +41,15 @@ PlaylistUpdaterInterface::loadForPlaylist( const playlist_ptr& pl )
|
||||
// Ok, we have one we can try to load
|
||||
const QString type = s->value( QString( "%1/type" ).arg( key ) ).toString();
|
||||
PlaylistUpdaterInterface* updater = 0;
|
||||
if ( type == "xspf" )
|
||||
updater = new XspfUpdater( pl );
|
||||
|
||||
// You forgot to register your new updater type with the factory above. 00ps.
|
||||
if ( !updater )
|
||||
if ( !s_factories.contains( type ) )
|
||||
{
|
||||
Q_ASSERT( false );
|
||||
// You forgot to register your new updater type with the factory....
|
||||
return 0;
|
||||
}
|
||||
updater->setAutoUpdate( s->value( QString( "%1/autoupdate" ).arg( key ) ).toBool() );
|
||||
updater->setInterval( s->value( QString( "%1/interval" ).arg( key ) ).toInt() );
|
||||
updater->loadFromSettings( key );
|
||||
|
||||
updater = s_factories[ type ]->create( pl, key );
|
||||
return updater;
|
||||
}
|
||||
|
||||
@ -54,46 +59,26 @@ PlaylistUpdaterInterface::loadForPlaylist( const playlist_ptr& pl )
|
||||
|
||||
PlaylistUpdaterInterface::PlaylistUpdaterInterface( const playlist_ptr& pl )
|
||||
: QObject( 0 )
|
||||
, m_timer( new QTimer( this ) )
|
||||
, m_autoUpdate( true )
|
||||
, m_playlist( pl )
|
||||
{
|
||||
Q_ASSERT( !m_playlist.isNull() );
|
||||
|
||||
m_playlist->setUpdater( this );
|
||||
connect( m_timer, SIGNAL( timeout() ), this, SLOT( updateNow() ) );
|
||||
|
||||
QTimer::singleShot( 0, this, SLOT( doSave() ) );
|
||||
}
|
||||
|
||||
PlaylistUpdaterInterface::PlaylistUpdaterInterface( const playlist_ptr& pl, int interval, bool autoUpdate )
|
||||
: QObject( 0 )
|
||||
, m_timer( new QTimer( this ) )
|
||||
, m_autoUpdate( autoUpdate )
|
||||
, m_playlist( pl )
|
||||
{
|
||||
Q_ASSERT( !m_playlist.isNull() );
|
||||
|
||||
m_playlist->setUpdater( this );
|
||||
m_timer->setInterval( interval );
|
||||
connect( m_timer, SIGNAL( timeout() ), this, SLOT( updateNow() ) );
|
||||
|
||||
QTimer::singleShot( 0, this, SLOT( doSave() ) );
|
||||
QTimer::singleShot( 0, this, SLOT( save() ) );
|
||||
}
|
||||
|
||||
|
||||
void
|
||||
PlaylistUpdaterInterface::doSave()
|
||||
PlaylistUpdaterInterface::save()
|
||||
{
|
||||
TomahawkSettings* s = TomahawkSettings::instance();
|
||||
const QString key = QString( "playlistupdaters/%1" ).arg( m_playlist->guid() );
|
||||
if ( !s->contains( QString( "%1/type" ).arg( key ) ) )
|
||||
{
|
||||
s->setValue( QString( "%1/type" ).arg( key ), type() );
|
||||
s->setValue( QString( "%1/autoupdate" ).arg( key ), m_autoUpdate );
|
||||
s->setValue( QString( "%1/interval" ).arg( key ), m_timer->interval() );
|
||||
saveToSettings( key );
|
||||
}
|
||||
saveToSettings( key );
|
||||
}
|
||||
|
||||
void
|
||||
@ -106,36 +91,6 @@ PlaylistUpdaterInterface::remove()
|
||||
const QString key = QString( "playlistupdaters/%1" ).arg( m_playlist->guid() );
|
||||
removeFromSettings( key );
|
||||
s->remove( QString( "%1/type" ).arg( key ) );
|
||||
s->remove( QString( "%1/autoupdate" ).arg( key ) );
|
||||
s->remove( QString( "%1/interval" ).arg( key ) );
|
||||
|
||||
deleteLater();
|
||||
}
|
||||
|
||||
|
||||
void
|
||||
PlaylistUpdaterInterface::setAutoUpdate( bool autoUpdate )
|
||||
{
|
||||
m_autoUpdate = autoUpdate;
|
||||
if ( m_autoUpdate )
|
||||
m_timer->start();
|
||||
else
|
||||
m_timer->stop();
|
||||
|
||||
const QString key = QString( "playlistupdaters/%1/autoupdate" ).arg( m_playlist->guid() );
|
||||
TomahawkSettings::instance()->setValue( key, m_autoUpdate );
|
||||
|
||||
// Update immediately as well
|
||||
if ( m_autoUpdate )
|
||||
QTimer::singleShot( 0, this, SLOT( updateNow() ) );
|
||||
}
|
||||
|
||||
void
|
||||
PlaylistUpdaterInterface::setInterval( int intervalMsecs )
|
||||
{
|
||||
const QString key = QString( "playlistupdaters/%1/interval" ).arg( m_playlist->guid() );
|
||||
TomahawkSettings::instance()->setValue( key, intervalMsecs );
|
||||
|
||||
m_timer->setInterval( intervalMsecs );
|
||||
}
|
||||
|
||||
|
@ -22,33 +22,42 @@
|
||||
#include "dllmacro.h"
|
||||
#include "typedefs.h"
|
||||
#include "playlist.h"
|
||||
|
||||
#include <QTimer>
|
||||
#include <QMutex>
|
||||
|
||||
#ifndef ENABLE_HEADLESS
|
||||
#include <QPixmap>
|
||||
#endif
|
||||
|
||||
namespace Tomahawk
|
||||
{
|
||||
/**
|
||||
* If a playlist needs periodic updating, implement a updater interface.
|
||||
*
|
||||
* Default is auto-updating.
|
||||
* PlaylistUpdaters are attached to playlists. They usually manipulate the playlist in some way
|
||||
* due to external input (spotify syncing) or timers (xspf updating)
|
||||
*/
|
||||
|
||||
class PlaylistUpdaterFactory;
|
||||
|
||||
class DLLEXPORT PlaylistUpdaterInterface : public QObject
|
||||
{
|
||||
Q_OBJECT
|
||||
public:
|
||||
PlaylistUpdaterInterface( const playlist_ptr& pl );
|
||||
PlaylistUpdaterInterface( const playlist_ptr& pl, int interval, bool autoUpdate );
|
||||
explicit PlaylistUpdaterInterface( const playlist_ptr& pl );
|
||||
|
||||
virtual ~PlaylistUpdaterInterface(){}
|
||||
|
||||
// What type you are. If you add a new updater, add the creation code as well.
|
||||
virtual QString type() const = 0;
|
||||
|
||||
bool autoUpdate() const { return m_autoUpdate; }
|
||||
void setAutoUpdate( bool autoUpdate );
|
||||
#ifndef ENABLE_HEADLESS
|
||||
// Small widget to show in playlist header that configures the updater
|
||||
virtual QWidget* configurationWidget() const = 0;
|
||||
|
||||
void setInterval( int intervalMsecs ) ;
|
||||
int intervalMsecs() const { return m_timer->interval(); }
|
||||
// Small overlay over playlist icon in the sidebar to indicate that it has this updater type
|
||||
// Should be around 16x16 or something
|
||||
virtual QPixmap typeIcon() const { return QPixmap(); }
|
||||
#endif
|
||||
|
||||
void remove();
|
||||
|
||||
@ -58,21 +67,35 @@ public:
|
||||
/// updater if one was saved
|
||||
static PlaylistUpdaterInterface* loadForPlaylist( const playlist_ptr& pl );
|
||||
|
||||
static void registerUpdaterFactory( PlaylistUpdaterFactory* f );
|
||||
|
||||
signals:
|
||||
void changed();
|
||||
|
||||
public slots:
|
||||
virtual void updateNow() {}
|
||||
|
||||
private slots:
|
||||
void doSave();
|
||||
void save();
|
||||
|
||||
protected:
|
||||
virtual void loadFromSettings( const QString& group ) = 0;
|
||||
virtual void saveToSettings( const QString& group ) const = 0;
|
||||
virtual void removeFromSettings( const QString& group ) const = 0;
|
||||
|
||||
private:
|
||||
QTimer* m_timer;
|
||||
bool m_autoUpdate;
|
||||
playlist_ptr m_playlist;
|
||||
|
||||
static QMap< QString, PlaylistUpdaterFactory* > s_factories;
|
||||
};
|
||||
|
||||
|
||||
class DLLEXPORT PlaylistUpdaterFactory
|
||||
{
|
||||
public:
|
||||
PlaylistUpdaterFactory() {}
|
||||
virtual ~PlaylistUpdaterFactory() {}
|
||||
|
||||
virtual QString type() const = 0;
|
||||
virtual PlaylistUpdaterInterface* create( const playlist_ptr&, const QString& settingsKey ) = 0;
|
||||
};
|
||||
|
||||
}
|
||||
|
@ -26,32 +26,58 @@
|
||||
|
||||
#include <QTimer>
|
||||
|
||||
#ifndef ENABLE_HEADLESS
|
||||
#include <QCheckBox>
|
||||
#endif
|
||||
|
||||
using namespace Tomahawk;
|
||||
|
||||
XspfUpdater::XspfUpdater( const playlist_ptr& pl, const QString& xUrl )
|
||||
: PlaylistUpdaterInterface( pl )
|
||||
, m_url( xUrl )
|
||||
PlaylistUpdaterInterface*
|
||||
XspfUpdaterFactory::create( const playlist_ptr &pl, const QString& settingsKey )
|
||||
{
|
||||
const bool autoUpdate = TomahawkSettings::instance()->value( QString( "%1/autoupdate" ).arg( settingsKey ) ).toBool();
|
||||
const int interval = TomahawkSettings::instance()->value( QString( "%1/interval" ).arg( settingsKey ) ).toInt();
|
||||
const QString url = TomahawkSettings::instance()->value( QString( "%1/xspfurl" ).arg( settingsKey ) ).toString();
|
||||
|
||||
XspfUpdater* updater = new XspfUpdater( pl, interval, autoUpdate, url );
|
||||
|
||||
return updater;
|
||||
}
|
||||
|
||||
|
||||
XspfUpdater::XspfUpdater( const playlist_ptr& pl, int interval, bool autoUpdate, const QString& xspfUrl )
|
||||
: PlaylistUpdaterInterface( pl, interval, autoUpdate )
|
||||
: PlaylistUpdaterInterface( pl )
|
||||
, m_timer( new QTimer( this ) )
|
||||
, m_autoUpdate( autoUpdate )
|
||||
, m_url( xspfUrl )
|
||||
{
|
||||
m_timer->setInterval( interval );
|
||||
|
||||
}
|
||||
connect( m_timer, SIGNAL( timeout() ), this, SLOT( updateNow() ) );
|
||||
|
||||
#ifndef ENABLE_HEADLESS
|
||||
m_toggleCheckbox = new QCheckBox( );
|
||||
m_toggleCheckbox->setText( tr( "Automatically update" ) );
|
||||
m_toggleCheckbox->setLayoutDirection( Qt::RightToLeft );
|
||||
m_toggleCheckbox->setChecked( m_autoUpdate );
|
||||
m_toggleCheckbox->hide();
|
||||
|
||||
XspfUpdater::XspfUpdater( const playlist_ptr& pl )
|
||||
: PlaylistUpdaterInterface( pl )
|
||||
{
|
||||
|
||||
connect( m_toggleCheckbox, SIGNAL( toggled( bool ) ), this, SLOT( setAutoUpdate( bool ) ) );
|
||||
#endif
|
||||
}
|
||||
|
||||
|
||||
XspfUpdater::~XspfUpdater()
|
||||
{}
|
||||
|
||||
|
||||
QWidget*
|
||||
XspfUpdater::configurationWidget() const
|
||||
{
|
||||
return m_toggleCheckbox;
|
||||
}
|
||||
|
||||
|
||||
void
|
||||
XspfUpdater::updateNow()
|
||||
{
|
||||
@ -68,6 +94,7 @@ XspfUpdater::updateNow()
|
||||
connect( l, SIGNAL( tracks( QList<Tomahawk::query_ptr> ) ), this, SLOT( playlistLoaded( QList<Tomahawk::query_ptr> ) ) );
|
||||
}
|
||||
|
||||
|
||||
void
|
||||
XspfUpdater::playlistLoaded( const QList<Tomahawk::query_ptr>& newEntries )
|
||||
{
|
||||
@ -85,20 +112,51 @@ XspfUpdater::playlistLoaded( const QList<Tomahawk::query_ptr>& newEntries )
|
||||
playlist()->createNewRevision( uuid(), playlist()->currentrevision(), el );
|
||||
}
|
||||
|
||||
|
||||
void
|
||||
XspfUpdater::saveToSettings( const QString& group ) const
|
||||
{
|
||||
TomahawkSettings::instance()->setValue( QString( "%1/autoupdate" ).arg( group ), m_autoUpdate );
|
||||
TomahawkSettings::instance()->setValue( QString( "%1/interval" ).arg( group ), m_timer->interval() );
|
||||
TomahawkSettings::instance()->setValue( QString( "%1/xspfurl" ).arg( group ), m_url );
|
||||
}
|
||||
|
||||
void
|
||||
XspfUpdater::loadFromSettings( const QString& group )
|
||||
{
|
||||
m_url = TomahawkSettings::instance()->value( QString( "%1/xspfurl" ).arg( group ) ).toString();
|
||||
}
|
||||
|
||||
void
|
||||
XspfUpdater::removeFromSettings( const QString& group ) const
|
||||
{
|
||||
TomahawkSettings::instance()->remove( QString( "%1/autoupdate" ).arg( group ) );
|
||||
TomahawkSettings::instance()->remove( QString( "%1/interval" ).arg( group ) );
|
||||
TomahawkSettings::instance()->remove( QString( "%1/xspfurl" ).arg( group ) );
|
||||
}
|
||||
|
||||
|
||||
void
|
||||
XspfUpdater::setAutoUpdate( bool autoUpdate )
|
||||
{
|
||||
m_autoUpdate = autoUpdate;
|
||||
|
||||
if ( m_autoUpdate )
|
||||
m_timer->start();
|
||||
else
|
||||
m_timer->stop();
|
||||
|
||||
const QString key = QString( "playlistupdaters/%1/autoupdate" ).arg( playlist()->guid() );
|
||||
TomahawkSettings::instance()->setValue( key, m_autoUpdate );
|
||||
|
||||
// Update immediately as well
|
||||
if ( m_autoUpdate )
|
||||
QTimer::singleShot( 0, this, SLOT( updateNow() ) );
|
||||
}
|
||||
|
||||
void
|
||||
XspfUpdater::setInterval( int intervalMsecs )
|
||||
{
|
||||
const QString key = QString( "playlistupdaters/%1/interval" ).arg( playlist()->guid() );
|
||||
TomahawkSettings::instance()->setValue( key, intervalMsecs );
|
||||
|
||||
if ( !m_timer )
|
||||
m_timer = new QTimer( this );
|
||||
|
||||
m_timer->setInterval( intervalMsecs );
|
||||
}
|
||||
|
@ -20,37 +20,62 @@
|
||||
#define XSPFUPDATER_H
|
||||
|
||||
#include "PlaylistUpdaterInterface.h"
|
||||
#include "dllmacro.h"
|
||||
|
||||
class QTimer;
|
||||
class QCheckBox;
|
||||
|
||||
namespace Tomahawk
|
||||
{
|
||||
|
||||
class XspfUpdater : public PlaylistUpdaterInterface
|
||||
|
||||
class DLLEXPORT XspfUpdater : public PlaylistUpdaterInterface
|
||||
{
|
||||
Q_OBJECT
|
||||
public:
|
||||
XspfUpdater( const playlist_ptr& pl, const QString& xspfUrl );
|
||||
XspfUpdater( const playlist_ptr& pl, int interval, bool autoUpdate, const QString& xspfUrl );
|
||||
explicit XspfUpdater( const playlist_ptr& pl ); // used by factory
|
||||
|
||||
virtual ~XspfUpdater();
|
||||
|
||||
virtual QString type() const { return "xspf"; }
|
||||
|
||||
#ifndef ENABLE_HEADLESS
|
||||
virtual QWidget* configurationWidget() const;
|
||||
#endif
|
||||
|
||||
bool autoUpdate() const { return m_autoUpdate; }
|
||||
|
||||
void setInterval( int intervalMsecs ) ;
|
||||
int intervalMsecs() const { return m_timer->interval(); }
|
||||
|
||||
public slots:
|
||||
void updateNow();
|
||||
void setAutoUpdate( bool autoUpdate );
|
||||
|
||||
protected:
|
||||
void loadFromSettings( const QString& group );
|
||||
void saveToSettings( const QString& group ) const;
|
||||
virtual void removeFromSettings(const QString& group) const;
|
||||
void removeFromSettings(const QString& group) const;
|
||||
|
||||
private slots:
|
||||
void playlistLoaded( const QList<Tomahawk::query_ptr> & );
|
||||
|
||||
private:
|
||||
QTimer* m_timer;
|
||||
bool m_autoUpdate;
|
||||
QString m_url;
|
||||
|
||||
#ifndef ENABLE_HEADLESS
|
||||
QCheckBox* m_toggleCheckbox;
|
||||
#endif
|
||||
};
|
||||
|
||||
class DLLEXPORT XspfUpdaterFactory : public PlaylistUpdaterFactory
|
||||
{
|
||||
public:
|
||||
XspfUpdaterFactory() {}
|
||||
|
||||
virtual QString type() const { return "xspf"; }
|
||||
virtual PlaylistUpdaterInterface* create( const playlist_ptr& pl, const QString& settingsKey );
|
||||
};
|
||||
|
||||
}
|
||||
|
@ -181,7 +181,7 @@ PlaylistItemDelegate::paintShort( QPainter* painter, const QStyleOptionViewItem&
|
||||
painter->setPen( opt.palette.text().color() );
|
||||
|
||||
QRect ir = r.adjusted( 4, 0, -option.rect.width() + option.rect.height() - 8 + r.left(), 0 );
|
||||
|
||||
|
||||
if ( useAvatars )
|
||||
{
|
||||
if ( !source.isNull() )
|
||||
|
@ -41,6 +41,8 @@ PlaylistModel::PlaylistModel( QObject* parent )
|
||||
: TrackModel( parent )
|
||||
, m_isTemporary( false )
|
||||
, m_changesOngoing( false )
|
||||
, m_isLoading( false )
|
||||
, m_savedInsertPos( -1 )
|
||||
{
|
||||
m_dropStorage.parent = QPersistentModelIndex();
|
||||
m_dropStorage.row = -10;
|
||||
@ -64,6 +66,8 @@ PlaylistModel::loadPlaylist( const Tomahawk::playlist_ptr& playlist, bool loadEn
|
||||
disconnect( m_playlist.data(), SIGNAL( changed() ), this, SIGNAL( playlistChanged() ) );
|
||||
}
|
||||
|
||||
m_isLoading = true;
|
||||
|
||||
if ( loadEntries )
|
||||
clear();
|
||||
|
||||
@ -79,11 +83,22 @@ PlaylistModel::loadPlaylist( const Tomahawk::playlist_ptr& playlist, bool loadEn
|
||||
.arg( TomahawkUtils::ageToString( QDateTime::fromTime_t( playlist->createdOn() ), true ) ) );
|
||||
|
||||
m_isTemporary = false;
|
||||
|
||||
if ( !loadEntries )
|
||||
{
|
||||
m_isLoading = false;
|
||||
return;
|
||||
}
|
||||
|
||||
QList<plentry_ptr> entries = playlist->entries();
|
||||
|
||||
qDebug() << "playlist loading entries:";
|
||||
foreach( const plentry_ptr& p, entries )
|
||||
qDebug() << p->guid() << p->query()->track() << p->query()->artist();
|
||||
|
||||
append( entries );
|
||||
|
||||
m_isLoading = false;
|
||||
}
|
||||
|
||||
|
||||
@ -178,7 +193,11 @@ PlaylistModel::insert( const QList< Tomahawk::query_ptr >& queries, int row )
|
||||
entry->setDuration( 0 );
|
||||
|
||||
entry->setLastmodified( 0 );
|
||||
entry->setAnnotation( "" ); // FIXME
|
||||
QString annotation = "";
|
||||
if ( !query->property( "annotation" ).toString().isEmpty() )
|
||||
annotation = query->property( "annotation" ).toString();
|
||||
entry->setAnnotation( annotation );
|
||||
|
||||
entry->setQuery( query );
|
||||
entry->setGuid( uuid() );
|
||||
|
||||
@ -203,6 +222,12 @@ PlaylistModel::insert( const QList< Tomahawk::plentry_ptr >& entries, int row )
|
||||
crows.first = c;
|
||||
crows.second = c + entries.count() - 1;
|
||||
|
||||
if ( !m_isLoading )
|
||||
{
|
||||
m_savedInsertPos = row;
|
||||
m_savedInsertTracks = entries;
|
||||
}
|
||||
|
||||
emit beginInsertRows( QModelIndex(), crows.first, crows.second );
|
||||
|
||||
QList< Tomahawk::query_ptr > queries;
|
||||
@ -402,6 +427,46 @@ PlaylistModel::endPlaylistChanges()
|
||||
{
|
||||
m_playlist->createNewRevision( newrev, m_playlist->currentrevision(), l );
|
||||
}
|
||||
|
||||
if ( m_savedInsertPos >= 0 && !m_savedInsertTracks.isEmpty() &&
|
||||
!m_savedRemoveTracks.isEmpty() )
|
||||
{
|
||||
// If we have *both* an insert and remove, then it's a move action
|
||||
// However, since we got the insert before the remove (Qt...), the index we have as the saved
|
||||
// insert position is no longer valid. Find the proper one by finding the location of the first inserted
|
||||
// track
|
||||
for ( int i = 0; i < rowCount( QModelIndex() ); i++ )
|
||||
{
|
||||
const QModelIndex idx = index( i, 0, QModelIndex() );
|
||||
if ( !idx.isValid() )
|
||||
continue;
|
||||
const TrackModelItem* item = itemFromIndex( idx );
|
||||
if ( !item || item->entry().isNull() )
|
||||
continue;
|
||||
|
||||
// qDebug() << "Checking for equality:" << (item->entry() == m_savedInsertTracks.first()) << m_savedInsertTracks.first()->query()->track() << m_savedInsertTracks.first()->query()->artist();
|
||||
if ( item->entry() == m_savedInsertTracks.first() )
|
||||
{
|
||||
// Found our index
|
||||
emit m_playlist->tracksMoved( m_savedInsertTracks, i );
|
||||
break;
|
||||
}
|
||||
}
|
||||
m_savedInsertPos = -1;
|
||||
m_savedInsertTracks.clear();
|
||||
m_savedRemoveTracks.clear();
|
||||
}
|
||||
else if ( m_savedInsertPos >= 0 )
|
||||
{
|
||||
emit m_playlist->tracksInserted( m_savedInsertTracks, m_savedInsertPos );
|
||||
m_savedInsertPos = -1;
|
||||
m_savedInsertTracks.clear();
|
||||
}
|
||||
else if ( !m_savedRemoveTracks.isEmpty() )
|
||||
{
|
||||
emit m_playlist->tracksRemoved( m_savedRemoveTracks );
|
||||
m_savedRemoveTracks.clear();
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@ -447,6 +512,9 @@ PlaylistModel::remove( const QModelIndex& index, bool moreToCome )
|
||||
if ( !m_changesOngoing )
|
||||
beginPlaylistChanges();
|
||||
|
||||
if ( item && !m_isLoading )
|
||||
m_savedRemoveTracks << item->query();
|
||||
|
||||
TrackModel::remove( index, moreToCome );
|
||||
|
||||
if ( !moreToCome )
|
||||
|
@ -97,9 +97,14 @@ private:
|
||||
Tomahawk::playlist_ptr m_playlist;
|
||||
bool m_isTemporary;
|
||||
bool m_changesOngoing;
|
||||
bool m_isLoading;
|
||||
QList< Tomahawk::Query* > m_waitingForResolved;
|
||||
QStringList m_waitForRevision;
|
||||
|
||||
int m_savedInsertPos;
|
||||
QList< Tomahawk::plentry_ptr > m_savedInsertTracks;
|
||||
QList< Tomahawk::query_ptr > m_savedRemoveTracks;
|
||||
|
||||
DropStorageData m_dropStorage;
|
||||
};
|
||||
|
||||
|
@ -119,23 +119,13 @@ PlaylistView::canAutoUpdate() const
|
||||
}
|
||||
|
||||
|
||||
bool
|
||||
PlaylistView::autoUpdate() const
|
||||
PlaylistUpdaterInterface*
|
||||
PlaylistView::autoUpdateInterface() const
|
||||
{
|
||||
if ( canAutoUpdate() )
|
||||
return m_model->playlist()->updater()->autoUpdate();
|
||||
if ( !m_model->playlist().isNull() && m_model->playlist()->updater() )
|
||||
return m_model->playlist()->updater();
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
|
||||
void
|
||||
PlaylistView::setAutoUpdate( bool autoUpdate )
|
||||
{
|
||||
if ( !canAutoUpdate() )
|
||||
return;
|
||||
|
||||
m_model->playlist()->updater()->setAutoUpdate( autoUpdate );
|
||||
return 0;
|
||||
}
|
||||
|
||||
|
||||
|
@ -44,8 +44,7 @@ public:
|
||||
virtual bool showFilter() const { return true; }
|
||||
|
||||
virtual bool canAutoUpdate() const;
|
||||
virtual void setAutoUpdate( bool autoUpdate );
|
||||
virtual bool autoUpdate() const;
|
||||
virtual Tomahawk::PlaylistUpdaterInterface* autoUpdateInterface() const;
|
||||
|
||||
virtual QString title() const { return playlistModel()->title(); }
|
||||
virtual QString description() const { return m_model->description(); }
|
||||
|
@ -22,6 +22,7 @@
|
||||
#include <QStyledItemDelegate>
|
||||
|
||||
#include "dllmacro.h"
|
||||
#include "typedefs.h"
|
||||
|
||||
namespace Tomahawk {
|
||||
class PixmapDelegateFader;
|
||||
|
@ -221,4 +221,6 @@ private:
|
||||
|
||||
}; //ns
|
||||
|
||||
Q_DECLARE_METATYPE(Tomahawk::query_ptr);
|
||||
|
||||
#endif // QUERY_H
|
||||
|
@ -1,6 +1,7 @@
|
||||
/* === This file is part of Tomahawk Player - <http://tomahawk-player.org> ===
|
||||
*
|
||||
* Copyright 2010-2011, Christian Muehlhaeuser <muesli@tomahawk-player.org>
|
||||
* Copyright 2010-2011, Leo Franchi <lfranchi@kde.org>
|
||||
*
|
||||
* Tomahawk is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
|
@ -1,6 +1,7 @@
|
||||
/* === This file is part of Tomahawk Player - <http://tomahawk-player.org> ===
|
||||
*
|
||||
* Copyright 2010-2011, Christian Muehlhaeuser <muesli@tomahawk-player.org>
|
||||
* Copyright 2010-2011, Leo Franchi <lfranchi@kde.org>
|
||||
*
|
||||
* Tomahawk is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
|
@ -65,8 +65,11 @@ ScriptResolver::~ScriptResolver()
|
||||
disconnect( &m_proc, SIGNAL( finished( int, QProcess::ExitStatus ) ), this, SLOT( cmdExited( int, QProcess::ExitStatus ) ) );
|
||||
m_deleting = true;
|
||||
|
||||
m_proc.kill();
|
||||
m_proc.waitForFinished(); // might call handleMsg
|
||||
QVariantMap msg;
|
||||
msg[ "_msgtype" ] = "quit";
|
||||
sendMessage( msg );
|
||||
|
||||
m_proc.waitForFinished( 1000 ); // might call handleMsg
|
||||
|
||||
Tomahawk::Pipeline::instance()->removeResolver( this );
|
||||
|
||||
@ -146,6 +149,13 @@ ScriptResolver::running() const
|
||||
return !m_stopped;
|
||||
}
|
||||
|
||||
void
|
||||
ScriptResolver::sendMessage( const QVariantMap& map )
|
||||
{
|
||||
QByteArray data = m_serializer.serialize( map );
|
||||
sendMsg( data );
|
||||
}
|
||||
|
||||
|
||||
void
|
||||
ScriptResolver::readStderr()
|
||||
@ -272,24 +282,10 @@ ScriptResolver::handleMsg( const QByteArray& msg )
|
||||
|
||||
Tomahawk::Pipeline::instance()->reportResults( qid, results );
|
||||
}
|
||||
else if ( msgtype == "playlist" )
|
||||
else
|
||||
{
|
||||
|
||||
QList< Tomahawk::query_ptr > tracks;
|
||||
const QString qid = m.value( "qid" ).toString();
|
||||
const QString title = m.value( "identifier" ).toString();
|
||||
const QVariantList reslist = m.value( "playlist" ).toList();
|
||||
|
||||
if( !reslist.isEmpty() )
|
||||
{
|
||||
foreach( const QVariant& rv, reslist )
|
||||
{
|
||||
QVariantMap m = rv.toMap();
|
||||
qDebug() << "Found playlist result:" << m;
|
||||
Tomahawk::query_ptr q = Tomahawk::Query::get( m.value( "artist" ).toString() , m.value( "track" ).toString() , QString(), uuid(), false );
|
||||
tracks << q;
|
||||
}
|
||||
}
|
||||
// Unknown message, give up for custom implementations
|
||||
emit customMessage( msgtype, m );
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -54,8 +54,11 @@ public:
|
||||
|
||||
virtual bool running() const;
|
||||
|
||||
void sendMessage( const QVariantMap& map );
|
||||
|
||||
signals:
|
||||
void terminated();
|
||||
void customMessage( const QString& msgType, const QVariantMap& msg );
|
||||
|
||||
public slots:
|
||||
virtual void stop();
|
||||
|
@ -1121,3 +1121,20 @@ TomahawkSettings::updateIndex()
|
||||
DatabaseCommand* cmd = new DatabaseCommand_UpdateSearchIndex();
|
||||
Database::instance()->enqueue( QSharedPointer<DatabaseCommand>( cmd ) );
|
||||
}
|
||||
|
||||
|
||||
QString
|
||||
TomahawkSettings::importXspfPath() const
|
||||
{
|
||||
if ( contains( "importXspfPath" ) )
|
||||
return value( "importXspfPath" ).toString();
|
||||
else
|
||||
return QDir::homePath();
|
||||
}
|
||||
|
||||
|
||||
void
|
||||
TomahawkSettings::setImportXspfPath( const QString& path )
|
||||
{
|
||||
setValue( "importXspfPath", path );
|
||||
}
|
||||
|
@ -198,6 +198,9 @@ public:
|
||||
PrivateListeningMode privateListeningMode() const;
|
||||
void setPrivateListeningMode( PrivateListeningMode mode );
|
||||
|
||||
void setImportXspfPath( const QString& path );
|
||||
QString importXspfPath() const;
|
||||
|
||||
signals:
|
||||
void changed();
|
||||
void recentlyPlayedPlaylistAdded( const Tomahawk::playlist_ptr& playlist );
|
||||
|
@ -23,6 +23,7 @@
|
||||
#include <QSharedPointer>
|
||||
#include <QUuid>
|
||||
#include <QPair>
|
||||
#include <QPersistentModelIndex>
|
||||
#include <boost/function.hpp>
|
||||
|
||||
//template <typename T> class QSharedPointer;
|
||||
@ -90,4 +91,6 @@ inline static QString uuid()
|
||||
return q;
|
||||
}
|
||||
|
||||
Q_DECLARE_METATYPE( QPersistentModelIndex )
|
||||
|
||||
#endif // TYPEDEFS_H
|
||||
|
204
src/libtomahawk/utils/SmartPointerList.h
Normal file
204
src/libtomahawk/utils/SmartPointerList.h
Normal file
@ -0,0 +1,204 @@
|
||||
/****************************************************************************************
|
||||
* Copyright (c) 2009 Mark Kretschmann <kretschmann@kde.org> *
|
||||
* Copyright (c) 2009 Ian Monroe <ian@monroe.nu> *
|
||||
* Copyright (c) 2009 Max Howell <max@last.fm> *
|
||||
* *
|
||||
* This program is free software; you can redistribute it and/or modify it under *
|
||||
* the terms of the GNU General Public License as published by the Free Software *
|
||||
* Foundation; either version 2 of the License, or (at your option) any later *
|
||||
* version. *
|
||||
* *
|
||||
* This program is distributed in the hope that it will be useful, but WITHOUT ANY *
|
||||
* WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A *
|
||||
* PARTICULAR PURPOSE. See the GNU General Public License for more details. *
|
||||
* *
|
||||
* You should have received a copy of the GNU General Public License along with *
|
||||
* this program. If not, see <http://www.gnu.org/licenses/>. *
|
||||
****************************************************************************************/
|
||||
|
||||
#ifndef SMART_POINTER_LIST_H
|
||||
#define SMART_POINTER_LIST_H
|
||||
|
||||
#include <QList> //baseclass
|
||||
#include <QObject> //baseclass
|
||||
|
||||
#include "dllmacro.h"
|
||||
|
||||
class DLLEXPORT SmartPointerListDaddy : public QObject
|
||||
{
|
||||
Q_OBJECT
|
||||
QList<QObject*>& m_list;
|
||||
|
||||
public:
|
||||
SmartPointerListDaddy( QList<QObject*>* list ) : m_list( *list )
|
||||
{}
|
||||
|
||||
private slots:
|
||||
void onDestroyed()
|
||||
{
|
||||
m_list.removeAll( sender() );
|
||||
}
|
||||
};
|
||||
|
||||
/** QList has no virtual functions, so we inherit privately and define the
|
||||
* interface exactly to ensure users can't write code that breaks the
|
||||
* class's internal behaviour.
|
||||
*
|
||||
* I deliberately didn't define clear. I worried people would assume it
|
||||
* deleted the pointers. Or assume it didn't. I didn't expose a few other
|
||||
* functions for that reason.
|
||||
*
|
||||
* non-const iterator functions are not exposed as they access the QList
|
||||
* baseclass, and then the Daddy wouldn't be watching newly inserted items.
|
||||
*
|
||||
* --mxcl
|
||||
* Exposed clear. This class doesn't have a QPtrList autodelete functionality
|
||||
* ever, so if people think that, they're really confused! -- Ian Monroe
|
||||
*
|
||||
*/
|
||||
template <class T> class SmartPointerList : private QList<T*>
|
||||
{
|
||||
class SmartPointerListDaddy* m_daddy;
|
||||
|
||||
public:
|
||||
SmartPointerList() : m_daddy( new SmartPointerListDaddy( (QList<QObject*>*)this ) )
|
||||
{}
|
||||
|
||||
~SmartPointerList()
|
||||
{
|
||||
delete m_daddy;
|
||||
}
|
||||
|
||||
SmartPointerList( const SmartPointerList<T>& that )
|
||||
: QList<T*>()
|
||||
, m_daddy( new SmartPointerListDaddy( (QList<QObject*>*)this ) )
|
||||
{
|
||||
QListIterator<T*> i( that );
|
||||
while (i.hasNext())
|
||||
append( i.next() );
|
||||
}
|
||||
|
||||
SmartPointerList& operator=( const SmartPointerList<T>& that )
|
||||
{
|
||||
QListIterator<T*> i( *this);
|
||||
while (i.hasNext())
|
||||
QObject::disconnect( m_daddy, 0, i.next(), 0 );
|
||||
|
||||
QList<T*>::operator=( that );
|
||||
|
||||
if (this != &that) {
|
||||
QListIterator<T*> i( that );
|
||||
while (i.hasNext())
|
||||
m_daddy->connect( i.next(), SIGNAL(destroyed()), SLOT(onDestroyed()) );
|
||||
}
|
||||
|
||||
return *this;
|
||||
}
|
||||
|
||||
// keep same function names as Qt
|
||||
void append( T* o )
|
||||
{
|
||||
m_daddy->connect( o, SIGNAL(destroyed()), SLOT(onDestroyed()) );
|
||||
QList<T*>::append( o );
|
||||
}
|
||||
|
||||
void prepend( T* o )
|
||||
{
|
||||
m_daddy->connect( o, SIGNAL(destroyed()), SLOT(onDestroyed()) );
|
||||
QList<T*>::prepend( o );
|
||||
}
|
||||
|
||||
SmartPointerList& operator+=( T* o )
|
||||
{
|
||||
append( o );
|
||||
return *this;
|
||||
}
|
||||
|
||||
SmartPointerList& operator<<( T* o )
|
||||
{
|
||||
return operator+=( o );
|
||||
}
|
||||
|
||||
SmartPointerList operator+( const SmartPointerList that )
|
||||
{
|
||||
SmartPointerList<T> copy = *this;
|
||||
QListIterator<T*> i( that );
|
||||
while (i.hasNext())
|
||||
copy.append( i.next() );
|
||||
return copy;
|
||||
}
|
||||
|
||||
SmartPointerList& operator+=( const SmartPointerList that )
|
||||
{
|
||||
QListIterator<T*> i( that );
|
||||
while (i.hasNext())
|
||||
append( i.next() );
|
||||
return *this;
|
||||
}
|
||||
|
||||
void push_back( T* o )
|
||||
{
|
||||
append( o );
|
||||
}
|
||||
|
||||
void push_front( T* o )
|
||||
{
|
||||
prepend( o );
|
||||
}
|
||||
|
||||
void replace( int i, T* o )
|
||||
{
|
||||
QList<T*>::replace( i, o );
|
||||
m_daddy->connect( o, SIGNAL(destroyed()), SLOT(onDestroyed()) );
|
||||
}
|
||||
|
||||
/** this is a "safe" class. We always bounds check */
|
||||
inline T* operator[]( int index ) const { return QList<T*>::value( index ); }
|
||||
inline T* at( int index ) const { return QList<T*>::value( index ); }
|
||||
|
||||
// make public safe functions again
|
||||
using QList<T*>::back;
|
||||
using QList<T*>::constBegin;
|
||||
using QList<T*>::constEnd;
|
||||
using typename QList<T*>::const_iterator;
|
||||
using QList<T*>::contains;
|
||||
using QList<T*>::count;
|
||||
using QList<T*>::empty;
|
||||
using QList<T*>::erase;
|
||||
using QList<T*>::first;
|
||||
using QList<T*>::front;
|
||||
using QList<T*>::indexOf;
|
||||
using QList<T*>::insert;
|
||||
using QList<T*>::isEmpty;
|
||||
using QList<T*>::last;
|
||||
using QList<T*>::lastIndexOf;
|
||||
using QList<T*>::mid;
|
||||
using QList<T*>::move;
|
||||
using QList<T*>::pop_back;
|
||||
using QList<T*>::pop_front;
|
||||
using QList<T*>::size;
|
||||
using QList<T*>::swap;
|
||||
using QList<T*>::value;
|
||||
using QList<T*>::operator!=;
|
||||
using QList<T*>::operator==;
|
||||
|
||||
// can't use using directive here since we only want the const versions
|
||||
typename QList<T*>::const_iterator begin() const { return QList<T*>::constBegin(); }
|
||||
typename QList<T*>::const_iterator end() const { return QList<T*>::constEnd(); }
|
||||
|
||||
// it can lead to poor performance situations if we don't disconnect
|
||||
// but I think it's not worth making this class more complicated for such
|
||||
// an edge case
|
||||
using QList<T*>::clear;
|
||||
using QList<T*>::removeAll;
|
||||
using QList<T*>::removeAt;
|
||||
using QList<T*>::removeFirst;
|
||||
using QList<T*>::removeLast;
|
||||
using QList<T*>::removeOne;
|
||||
using QList<T*>::takeAt;
|
||||
using QList<T*>::takeFirst;
|
||||
using QList<T*>::takeLast;
|
||||
};
|
||||
|
||||
#endif //HEADER_GUARD
|
||||
|
@ -54,7 +54,17 @@ Closure::Closure(QObject* sender,
|
||||
Closure::~Closure() {
|
||||
}
|
||||
|
||||
|
||||
void Closure::forceInvoke()
|
||||
{
|
||||
Invoked();
|
||||
}
|
||||
|
||||
|
||||
void Closure::Connect(QObject* sender, const char* signal) {
|
||||
if (!sender)
|
||||
return;
|
||||
|
||||
bool success = connect(sender, signal, SLOT(Invoked()));
|
||||
Q_ASSERT(success);
|
||||
success = connect(sender, SIGNAL(destroyed()), SLOT(Cleanup()));
|
||||
|
@ -18,6 +18,8 @@
|
||||
#ifndef CLOSURE_H
|
||||
#define CLOSURE_H
|
||||
|
||||
#include "dllmacro.h"
|
||||
|
||||
#include <tr1/functional>
|
||||
|
||||
#include <QMetaMethod>
|
||||
@ -29,7 +31,7 @@
|
||||
|
||||
namespace _detail {
|
||||
|
||||
class ClosureArgumentWrapper {
|
||||
class DLLEXPORT ClosureArgumentWrapper {
|
||||
public:
|
||||
virtual ~ClosureArgumentWrapper() {}
|
||||
|
||||
@ -42,14 +44,14 @@ class ClosureArgument : public ClosureArgumentWrapper {
|
||||
explicit ClosureArgument(const T& data) : data_(data) {}
|
||||
|
||||
virtual QGenericArgument arg() const {
|
||||
return Q_ARG(T, data_);
|
||||
return QArgument<T>(QMetaType::typeName(qMetaTypeId<T>()), data_);
|
||||
}
|
||||
|
||||
private:
|
||||
T data_;
|
||||
};
|
||||
|
||||
class Closure : public QObject, boost::noncopyable {
|
||||
class DLLEXPORT Closure : public QObject, boost::noncopyable {
|
||||
Q_OBJECT
|
||||
|
||||
public:
|
||||
@ -67,6 +69,15 @@ class Closure : public QObject, boost::noncopyable {
|
||||
|
||||
virtual ~Closure();
|
||||
|
||||
/**
|
||||
* If you don't this Closure to act on a signal, but just act like
|
||||
* a closure in that it saves some args and delivers them on demand later
|
||||
*
|
||||
* Only call this is you passed a null QObject* as a sender! Otherwise you
|
||||
* might delete your object twice :)
|
||||
*/
|
||||
void forceInvoke();
|
||||
|
||||
private slots:
|
||||
void Invoked();
|
||||
void Cleanup();
|
||||
@ -84,7 +95,7 @@ class Closure : public QObject, boost::noncopyable {
|
||||
boost::scoped_ptr<const ClosureArgumentWrapper> val3_;
|
||||
};
|
||||
|
||||
class SharedPointerWrapper {
|
||||
class DLLEXPORT SharedPointerWrapper {
|
||||
public:
|
||||
virtual ~SharedPointerWrapper() {}
|
||||
virtual QObject* data() const = 0;
|
||||
|
@ -267,7 +267,6 @@ XSPFLoader::gotBody()
|
||||
m_entries );
|
||||
|
||||
// 10 minute default---for now, no way to change it
|
||||
connect( m_playlist.data(), SIGNAL( revisionLoaded( Tomahawk::PlaylistRevision ) ), this, SLOT( playlistCreated() ) );
|
||||
new Tomahawk::XspfUpdater( m_playlist, 600000, m_autoUpdate, m_url.toString() );
|
||||
emit ok( m_playlist );
|
||||
}
|
||||
|
@ -117,7 +117,6 @@ ViewManager::ViewManager( QObject* parent )
|
||||
|
||||
connect( &m_filterTimer, SIGNAL( timeout() ), SLOT( applyFilter() ) );
|
||||
connect( m_infobar, SIGNAL( filterTextChanged( QString ) ), SLOT( setFilter( QString ) ) );
|
||||
connect( m_infobar, SIGNAL( autoUpdateChanged( bool ) ), SLOT( autoUpdateChanged( bool ) ) );
|
||||
|
||||
connect( this, SIGNAL( tomahawkLoaded() ), m_whatsHotWidget, SLOT( fetchData() ) );
|
||||
connect( this, SIGNAL( tomahawkLoaded() ), m_newReleasesWidget, SLOT( fetchData() ) );
|
||||
@ -583,13 +582,6 @@ ViewManager::applyFilter()
|
||||
}
|
||||
|
||||
|
||||
void
|
||||
ViewManager::autoUpdateChanged( bool toggled )
|
||||
{
|
||||
currentPage()->setAutoUpdate( toggled );
|
||||
}
|
||||
|
||||
|
||||
void
|
||||
ViewManager::setPage( ViewPage* page, bool trackHistory )
|
||||
{
|
||||
@ -745,8 +737,6 @@ ViewManager::updateView()
|
||||
emit modesAvailable( currentPage()->showModes() );
|
||||
emit filterAvailable( currentPage()->showFilter() );
|
||||
|
||||
emit autoUpdateAvailable( currentPage()->canAutoUpdate() );
|
||||
|
||||
/* if ( !currentPage()->showStatsBar() && !currentPage()->showModes() && !currentPage()->showFilter() )
|
||||
m_topbar->setVisible( false );
|
||||
else
|
||||
@ -754,6 +744,9 @@ ViewManager::updateView()
|
||||
|
||||
m_infobar->setVisible( currentPage()->showInfoBar() );
|
||||
m_infobar->setCaption( currentPage()->title() );
|
||||
|
||||
m_infobar->setAutoUpdateInterface( currentPage()->autoUpdateInterface() );
|
||||
|
||||
switch( currentPage()->descriptionType() )
|
||||
{
|
||||
case ViewPage::TextType:
|
||||
|
@ -124,7 +124,6 @@ signals:
|
||||
void statsAvailable( bool b );
|
||||
void modesAvailable( bool b );
|
||||
void filterAvailable( bool b );
|
||||
void autoUpdateAvailable( bool b );
|
||||
void modeChanged( Tomahawk::PlaylistInterface::ViewMode mode );
|
||||
|
||||
void playClicked();
|
||||
@ -180,8 +179,6 @@ private slots:
|
||||
void setFilter( const QString& filter );
|
||||
void applyFilter();
|
||||
|
||||
void autoUpdateChanged( bool );
|
||||
|
||||
void onWidgetDestroyed( QWidget* widget );
|
||||
|
||||
private:
|
||||
|
@ -31,6 +31,8 @@
|
||||
namespace Tomahawk
|
||||
{
|
||||
|
||||
class PlaylistUpdaterInterface;
|
||||
|
||||
class DLLEXPORT ViewPage
|
||||
{
|
||||
public:
|
||||
@ -68,8 +70,7 @@ public:
|
||||
virtual bool isBeingPlayed() const { return false; }
|
||||
|
||||
virtual bool canAutoUpdate() const { return false; }
|
||||
virtual void setAutoUpdate( bool ) {}
|
||||
virtual bool autoUpdate() const { return false; }
|
||||
virtual PlaylistUpdaterInterface* autoUpdateInterface() const { return 0; }
|
||||
|
||||
/** subclasses implementing ViewPage can emit the following signals:
|
||||
* nameChanged( const QString& )
|
||||
|
@ -59,7 +59,6 @@ ChartDataLoader::go()
|
||||
|
||||
foreach ( const Tomahawk::InfoSystem::InfoStringHash& album, m_data )
|
||||
{
|
||||
tDebug( LOGVERBOSE) << Q_FUNC_INFO << "Getting album" << album[ "album" ] << "By" << album[ "artist" ];
|
||||
artist_ptr artistPtr = Artist::get( album[ "artist" ], false );
|
||||
album_ptr albumPtr = Album::get( artistPtr, album[ "album" ], false );
|
||||
album_ptrs << albumPtr;
|
||||
|
@ -125,7 +125,7 @@ main( int argc, char *argv[] )
|
||||
#endif
|
||||
|
||||
|
||||
#ifndef ENABLE_HEADLESS
|
||||
#ifndef ENABLE_HEADLESSs
|
||||
#ifdef WITH_BREAKPAD
|
||||
new BreakPad( QDir::tempPath(), TomahawkSettings::instance()->crashReporterEnabled() );
|
||||
#endif
|
||||
|
@ -20,6 +20,7 @@
|
||||
#include "playlistitems.h"
|
||||
|
||||
#include <QMimeData>
|
||||
#include <QPainter>
|
||||
|
||||
#include "query.h"
|
||||
#include "viewmanager.h"
|
||||
@ -44,12 +45,15 @@ PlaylistItem::PlaylistItem( SourcesModel* mdl, SourceTreeItem* parent, const pla
|
||||
connect( pl.data(), SIGNAL( revisionLoaded( Tomahawk::PlaylistRevision ) ),
|
||||
SLOT( onPlaylistLoaded( Tomahawk::PlaylistRevision ) ), Qt::QueuedConnection );
|
||||
connect( pl.data(), SIGNAL( changed() ),
|
||||
SIGNAL( updated() ), Qt::QueuedConnection );
|
||||
SLOT( onUpdated() ), Qt::QueuedConnection );
|
||||
|
||||
m_icon = QIcon( RESPATH "images/playlist-icon.png" );
|
||||
|
||||
if( ViewManager::instance()->pageForPlaylist( pl ) )
|
||||
model()->linkSourceItemToPage( this, ViewManager::instance()->pageForPlaylist( pl ) );
|
||||
|
||||
if ( m_playlist->updater() && !m_playlist->updater()->typeIcon().isNull() )
|
||||
createOverlay();
|
||||
}
|
||||
|
||||
|
||||
@ -244,10 +248,54 @@ PlaylistItem::parsedDroppedTracks( const QList< query_ptr >& tracks )
|
||||
}
|
||||
|
||||
|
||||
void
|
||||
PlaylistItem::onUpdated()
|
||||
{
|
||||
if ( m_playlist->updater() && !m_playlist->updater()->typeIcon().isNull() &&
|
||||
m_overlaidIcon.isNull() ) // create overlay
|
||||
{
|
||||
createOverlay();
|
||||
}
|
||||
else if ( !m_playlist->updater() || m_playlist->updater()->typeIcon().isNull() &&
|
||||
!m_overlaidIcon.isNull() )
|
||||
{
|
||||
// No longer an updater with an icon
|
||||
m_overlaidIcon = QIcon();
|
||||
}
|
||||
|
||||
emit updated();
|
||||
}
|
||||
|
||||
|
||||
void
|
||||
PlaylistItem::createOverlay()
|
||||
{
|
||||
Q_ASSERT( !m_playlist.isNull() );
|
||||
Q_ASSERT( m_playlist->updater() );
|
||||
Q_ASSERT( !m_playlist->updater()->typeIcon().isNull() );
|
||||
|
||||
m_overlaidIcon = QIcon();
|
||||
|
||||
QPixmap base = m_icon.pixmap( 48, 48 );
|
||||
const QPixmap overlay = m_playlist->updater()->typeIcon();
|
||||
|
||||
QPainter p( &base );
|
||||
const int w = base.width() / 2;
|
||||
const QRect overlayRect( base.rect().right() - w, base.rect().height() - w, w, w );
|
||||
p.drawPixmap( overlayRect, overlay );
|
||||
p.end();
|
||||
|
||||
m_overlaidIcon.addPixmap( base );
|
||||
}
|
||||
|
||||
|
||||
QIcon
|
||||
PlaylistItem::icon() const
|
||||
{
|
||||
return m_icon;
|
||||
if ( !m_overlaidIcon.isNull() )
|
||||
return m_overlaidIcon;
|
||||
else
|
||||
return m_icon;
|
||||
}
|
||||
|
||||
|
||||
|
@ -54,10 +54,13 @@ private slots:
|
||||
void onPlaylistChanged();
|
||||
void parsedDroppedTracks( const QList<Tomahawk::query_ptr>& tracks );
|
||||
|
||||
void onUpdated();
|
||||
private:
|
||||
void createOverlay();
|
||||
|
||||
bool m_loaded;
|
||||
Tomahawk::playlist_ptr m_playlist;
|
||||
QIcon m_icon;
|
||||
QIcon m_icon, m_overlaidIcon;
|
||||
};
|
||||
Q_DECLARE_OPERATORS_FOR_FLAGS(PlaylistItem::DropTypes)
|
||||
|
||||
|
@ -143,11 +143,12 @@ SourceTreeView::setupMenus()
|
||||
|
||||
bool readonly = true;
|
||||
SourcesModel::RowType type = ( SourcesModel::RowType )model()->data( m_contextMenuIndex, SourcesModel::SourceTreeItemTypeRole ).toInt();
|
||||
|
||||
if ( type == SourcesModel::StaticPlaylist || type == SourcesModel::AutomaticPlaylist || type == SourcesModel::Station )
|
||||
{
|
||||
const PlaylistItem* item = itemFromIndex< PlaylistItem >( m_contextMenuIndex );
|
||||
const playlist_ptr playlist = item->playlist();
|
||||
|
||||
PlaylistItem* item = itemFromIndex< PlaylistItem >( m_contextMenuIndex );
|
||||
playlist_ptr playlist = item->playlist();
|
||||
if ( !playlist.isNull() )
|
||||
{
|
||||
readonly = !playlist->author()->isLocal();
|
||||
@ -190,7 +191,7 @@ SourceTreeView::setupMenus()
|
||||
|
||||
QString addToText = QString( "Add to my %1" );
|
||||
if ( type == SourcesModel::StaticPlaylist )
|
||||
addToText = addToText.arg( "Playlists" );
|
||||
addToText = addToText.arg( "playlists" );
|
||||
if ( type == SourcesModel::AutomaticPlaylist )
|
||||
addToText = addToText.arg( "Automatic Playlists" );
|
||||
else if ( type == SourcesModel::Station )
|
||||
@ -203,6 +204,28 @@ SourceTreeView::setupMenus()
|
||||
renamePlaylistAction->setEnabled( !readonly );
|
||||
addToLocalAction->setEnabled( readonly );
|
||||
|
||||
// Handle any custom actions registered for playlists
|
||||
if ( type == SourcesModel::StaticPlaylist && !readonly &&
|
||||
!ActionCollection::instance()->getAction( ActionCollection::LocalPlaylists ).isEmpty() )
|
||||
{
|
||||
m_playlistMenu.addSeparator();
|
||||
|
||||
|
||||
const PlaylistItem* item = itemFromIndex< PlaylistItem >( m_contextMenuIndex );
|
||||
const playlist_ptr playlist = item->playlist();
|
||||
foreach ( QAction* action, ActionCollection::instance()->getAction( ActionCollection::LocalPlaylists ) )
|
||||
{
|
||||
if ( QObject* notifier = ActionCollection::instance()->actionNotifier( action ) )
|
||||
{
|
||||
QMetaObject::invokeMethod( notifier, "aboutToShow", Qt::DirectConnection, Q_ARG( QAction*, action ), Q_ARG( Tomahawk::playlist_ptr, playlist ) );
|
||||
}
|
||||
|
||||
action->setProperty( "payload", QVariant::fromValue< playlist_ptr >( playlist ) );
|
||||
m_playlistMenu.addAction( action );
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
if ( type == SourcesModel::StaticPlaylist )
|
||||
copyPlaylistAction->setText( tr( "&Export Playlist" ) );
|
||||
|
||||
|
@ -44,6 +44,7 @@
|
||||
#include "playlist/dynamic/GeneratorFactory.h"
|
||||
#include "playlist/dynamic/echonest/EchonestGenerator.h"
|
||||
#include "playlist/dynamic/database/DatabaseGenerator.h"
|
||||
#include "playlist/XspfUpdater.h"
|
||||
#include "network/servent.h"
|
||||
#include "web/api_v1.h"
|
||||
#include "sourcelist.h"
|
||||
@ -62,6 +63,9 @@
|
||||
#include "utils/jspfloader.h"
|
||||
#include "utils/logger.h"
|
||||
#include "utils/tomahawkutilsgui.h"
|
||||
#include "accounts/lastfm/LastFmAccount.h"
|
||||
#include "accounts/spotify/SpotifyAccount.h"
|
||||
#include "accounts/spotify/SpotifyPlaylistUpdater.h"
|
||||
#include "utils/tomahawkcache.h"
|
||||
|
||||
#include "config.h"
|
||||
@ -283,6 +287,9 @@ TomahawkApp::init()
|
||||
// Set up echonest catalog synchronizer
|
||||
Tomahawk::EchonestCatalogSynchronizer::instance();
|
||||
|
||||
PlaylistUpdaterInterface::registerUpdaterFactory( new XspfUpdaterFactory );
|
||||
PlaylistUpdaterInterface::registerUpdaterFactory( new SpotifyUpdaterFactory );
|
||||
|
||||
#ifndef ENABLE_HEADLESS
|
||||
// Make sure to init GAM in the gui thread
|
||||
GlobalActionManager::instance();
|
||||
@ -660,13 +667,15 @@ TomahawkApp::instanceStarted( KDSingleApplicationGuard::Instance instance )
|
||||
{
|
||||
tDebug( LOGINFO ) << "Instance started!" << instance.pid << instance.arguments;
|
||||
|
||||
activate();
|
||||
if ( instance.arguments.size() < 2 )
|
||||
return;
|
||||
|
||||
QString arg1 = instance.arguments[ 1 ];
|
||||
if ( loadUrl( arg1 ) )
|
||||
{
|
||||
activate();
|
||||
return;
|
||||
}
|
||||
|
||||
if ( instance.arguments.contains( "--next" ) )
|
||||
AudioEngine::instance()->next();
|
||||
|
@ -147,8 +147,6 @@ private:
|
||||
QWeakPointer< QxtHttpSessionManager > m_session;
|
||||
};
|
||||
|
||||
Q_DECLARE_METATYPE( QPersistentModelIndex )
|
||||
Q_DECLARE_METATYPE( PairList )
|
||||
|
||||
#endif // TOMAHAWKAPP_H
|
||||
|
||||
|
@ -2,6 +2,7 @@
|
||||
*
|
||||
* Copyright 2010-2011, Christian Muehlhaeuser <muesli@tomahawk-player.org>
|
||||
* Copyright 2010-2011, Jeff Mitchell <jeff@tomahawk-player.org>
|
||||
* Copyright 2010-2011, Leo Franchi <lfranchi@kde.org>
|
||||
*
|
||||
* Tomahawk is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
|
@ -1,6 +1,7 @@
|
||||
/* === This file is part of Tomahawk Player - <http://tomahawk-player.org> ===
|
||||
*
|
||||
* Copyright 2010-2011, Christian Muehlhaeuser <muesli@tomahawk-player.org>
|
||||
* Copyright 2010-2011, Leo Franchi <lfranchi@kde.org>
|
||||
*
|
||||
* Tomahawk is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
|
Loading…
x
Reference in New Issue
Block a user