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

Compare commits

...

21 Commits

Author SHA1 Message Date
Uwe L. Korn
e9237c1703 Remove SpotifyInfoPlugin 2014-11-19 23:19:54 +01:00
Uwe L. Korn
2d8f249f3c Move to Tomahawk namespace 2014-11-19 23:09:05 +01:00
Uwe L. Korn
0c0b207497 Readd accidentially removed files 2014-11-19 23:07:36 +01:00
Uwe L. Korn
5854c6531f Initialise all fields in the constructor 2014-11-17 23:20:34 +01:00
Uwe L. Korn
cf32037a8d Catch exceptions by reference 2014-11-17 23:20:34 +01:00
Uwe L. Korn
21df2a2775 Use nullptr instead of 0 2014-11-17 23:20:33 +01:00
Uwe L. Korn
665a3e97a5 Remove some more Spotify traces 2014-11-17 23:20:33 +01:00
Uwe L. Korn
b25672006c Remove SpotifyParser 2014-11-17 23:20:33 +01:00
Uwe L. Korn
d6ad1121b2 Remove ScriptResolver 2014-11-17 23:20:33 +01:00
Uwe L. Korn
167d46b685 Remove redundant semicolon 2014-11-17 23:20:05 +01:00
Uwe L. Korn
1c26564231 Remove SpotifyAccount 2014-11-17 23:20:05 +01:00
Uwe L. Korn
1e11ae8b5e Remove SpotifyPlaylistUpdater 2014-11-17 23:19:21 +01:00
Uwe L. Korn
a5a4faf9d5 Remove internal SpotifyInfoPlugin 2014-11-17 23:19:20 +01:00
Uwe L. Korn
d6572f951f Remove SpotifyAccountFactory 2014-11-17 23:19:20 +01:00
Uwe L. Korn
266d10988d Initialise timer in the constructor 2014-11-17 23:19:20 +01:00
Uwe L. Korn
fee310bd29 Remove useless comments 2014-11-17 23:19:20 +01:00
Uwe L. Korn
1072e3cf2b Refactor platform check into its own function 2014-11-17 23:19:20 +01:00
Uwe L. Korn
7022b2ad1e Only define done signal once 2014-11-17 23:19:20 +01:00
Uwe L. Korn
8e60f16821 Return type-safe nullptr 2014-11-17 23:19:19 +01:00
Uwe L. Korn
f9f1f358ec Remove star rating display 2014-11-17 23:19:19 +01:00
Uwe L. Korn
c2bd1cf917 Remove ExternalResolverGui
This abstraction step is not explicitly needed anymore and most of its
functionality will be changed/removed/replaced in the following commits.
2014-11-17 23:19:19 +01:00
51 changed files with 232 additions and 6154 deletions

View File

@@ -20,7 +20,6 @@ list(APPEND simple_plugins
Echonest
Charts
NewReleases
Spotify
Hypem
MusixMatch
MusicBrainz

View File

@@ -1,389 +0,0 @@
/* === This file is part of Tomahawk Player - <http://tomahawk-player.org> ===
*
* Copyright 2010-2011, Hugo Lindström <hugolm84@gmail.com>
* Copyright 2010-2011, Jeff Mitchell <jeff@tomahawk-player.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 "SpotifyPlugin.h"
#include <QDir>
#include <QSettings>
#include <QCryptographicHash>
#include <QNetworkConfiguration>
#include <QNetworkReply>
#include "Album.h"
#include "Typedefs.h"
#include "audio/AudioEngine.h"
#include "TomahawkSettings.h"
#include "utils/Json.h"
#include "utils/TomahawkUtils.h"
#include "utils/Logger.h"
#include "utils/NetworkAccessManager.h"
#include "CountryUtils.h"
#include "Source.h"
#define SPOTIFY_API_URL "http://spotikea.tomahawk-player.org/"
using namespace Tomahawk::InfoSystem;
SpotifyPlugin::SpotifyPlugin()
: InfoPlugin()
, m_chartsFetchJobs( 0 )
{
m_supportedGetTypes << InfoChart << InfoChartCapabilities;
}
SpotifyPlugin::~SpotifyPlugin()
{
qDebug() << Q_FUNC_INFO;
}
void
SpotifyPlugin::dataError( Tomahawk::InfoSystem::InfoRequestData requestData )
{
emit info( requestData, QVariant() );
return;
}
void
SpotifyPlugin::getInfo( Tomahawk::InfoSystem::InfoRequestData requestData )
{
qDebug() << Q_FUNC_INFO << requestData.caller;
qDebug() << Q_FUNC_INFO << requestData.customData;
InfoStringHash hash = requestData.input.value< Tomahawk::InfoSystem::InfoStringHash >();
switch ( requestData.type )
{
case InfoChart:
if ( !hash.contains( "chart_source" ) || hash["chart_source"] != "spotify" )
{
dataError( requestData );
break;
}
qDebug() << Q_FUNC_INFO << "InfoCHart req for" << hash["chart_source"];
fetchChart( requestData );
break;
case InfoChartCapabilities:
fetchChartCapabilities( requestData );
break;
default:
dataError( requestData );
}
}
void
SpotifyPlugin::fetchChart( Tomahawk::InfoSystem::InfoRequestData requestData )
{
if ( !requestData.input.canConvert< Tomahawk::InfoSystem::InfoStringHash >() )
{
dataError( requestData );
return;
}
InfoStringHash hash = requestData.input.value< Tomahawk::InfoSystem::InfoStringHash >();
Tomahawk::InfoSystem::InfoStringHash criteria;
/// Each request needs to contain both a id and source
if ( !hash.contains( "chart_id" ) && !hash.contains( "chart_source" ) )
{
tDebug( LOGVERBOSE ) << Q_FUNC_INFO << "Hash did not contain required params!";
dataError( requestData );
return;
}
/// Set the criterias for current chart
criteria["chart_id"] = hash["chart_id"];
criteria["chart_source"] = hash["chart_source"];
emit getCachedInfo( criteria, Q_INT64_C(86400000) /* Expire chart cache in 1 day */, requestData );
}
void
SpotifyPlugin::fetchChartCapabilities( Tomahawk::InfoSystem::InfoRequestData requestData )
{
if ( !requestData.input.canConvert< Tomahawk::InfoSystem::InfoStringHash >() )
{
dataError( requestData );
return;
}
Tomahawk::InfoSystem::InfoStringHash criteria;
criteria[ "InfoChartCapabilities" ] = "spotifyplugin";
emit getCachedInfo( criteria, Q_INT64_C(604800000), requestData );
}
void
SpotifyPlugin::notInCacheSlot( Tomahawk::InfoSystem::InfoStringHash criteria, Tomahawk::InfoSystem::InfoRequestData requestData )
{
switch ( requestData.type )
{
case InfoChart:
{
/// Fetch the chart, we need source and id
tDebug( LOGVERBOSE ) << Q_FUNC_INFO << "InfoChart not in cache! Fetching...";
QUrl url = QUrl( QString( SPOTIFY_API_URL "toplist/%1/" ).arg( criteria["chart_id"] ) );
qDebug() << Q_FUNC_INFO << "Getting chart url" << url;
QNetworkReply* reply = Tomahawk::Utils::nam()->get( QNetworkRequest( url ) );
reply->setProperty( "requestData", QVariant::fromValue< Tomahawk::InfoSystem::InfoRequestData >( requestData ) );
connect( reply, SIGNAL( finished() ), SLOT( chartReturned() ) );
return;
}
case InfoChartCapabilities:
{
tDebug( LOGVERBOSE ) << Q_FUNC_INFO << "InfoChartCapabilities not in cache! Fetching...";
// we never need to re-fetch
if ( !m_allChartsMap.isEmpty() )
return;
/// We need to fetch possible types before they are asked for
tDebug() << "SpotifyPlugin: InfoChart fetching possible resources";
QUrl url = QUrl( QString( SPOTIFY_API_URL "toplist/charts" ) );
QNetworkReply* reply = Tomahawk::Utils::nam()->get( QNetworkRequest( url ) );
tDebug() << Q_FUNC_INFO << "fetching:" << url;
connect( reply, SIGNAL( finished() ), SLOT( chartTypes() ) );
m_chartsFetchJobs++;
if ( m_chartsFetchJobs > 0 )
{
qDebug() << Q_FUNC_INFO << "InfoChartCapabilities still fetching!";
m_cachedRequests.append( requestData );
return;
}
emit info( requestData, m_allChartsMap );
return;
}
default:
{
tLog() << Q_FUNC_INFO << "Couldn't figure out what to do with this type of request after cache miss";
emit info( requestData, QVariant() );
return;
}
}
}
void
SpotifyPlugin::chartTypes()
{
/// Get possible chart type for specificSpotifyPlugin: InfoChart types returned chart source
tDebug() << Q_FUNC_INFO << "Got spotifychart type result";
QNetworkReply* reply = qobject_cast<QNetworkReply*>( sender() );
if ( reply->error() == QNetworkReply::NoError )
{
bool ok;
QByteArray jsonData = reply->readAll();
const QVariantMap res = TomahawkUtils::parseJson( jsonData, &ok ).toMap();
const QVariantMap chartObj = res;
if ( !ok )
{
tLog() << Q_FUNC_INFO << "Failed to parse resources:" << jsonData;
return;
}
QVariantMap charts;
foreach( QVariant geos, chartObj.value( "Charts" ).toList().takeLast().toMap().value( "geo" ).toList() )
{
const QString geo = geos.toMap().value( "name" ).toString();
const QString geoId = geos.toMap().value( "id" ).toString();
QString country;
if( geo == "For me" )
continue; /// country = geo; Lets use this later, when we can get the spotify username from tomahawk
else if( geo == "Everywhere" )
country = geo;
else
{
QLocale l( QString( "en_%1" ).arg( geo ) );
country = Tomahawk::CountryUtils::fullCountryFromCode( geo );
for ( int i = 1; i < country.size(); i++ )
{
if ( country.at( i ).isUpper() )
{
country.insert( i, " " );
i++;
}
}
}
QList< InfoStringHash > chart_types;
foreach( QVariant types, chartObj.value( "Charts" ).toList().takeFirst().toMap().value( "types" ).toList() )
{
QString type = types.toMap().value( "id" ).toString();
QString label = types.toMap().value( "name" ).toString();
InfoStringHash c;
c[ "id" ] = type + "/" + geoId;
c[ "label" ] = label;
c[ "type" ] = type;
chart_types.append( c );
}
charts.insert( country.toUtf8(), QVariant::fromValue<QList< InfoStringHash > >( chart_types ) );
}
QVariantMap defaultMap;
defaultMap[ "spotify" ] = QStringList() << "United States" << "Top Albums";
m_allChartsMap[ "defaults" ] = defaultMap;
m_allChartsMap.insert( "Spotify", QVariant::fromValue<QVariantMap>( charts ) );
}
else
{
tLog() << Q_FUNC_INFO << "Error fetching charts:" << reply->errorString();
}
m_chartsFetchJobs--;
if ( !m_cachedRequests.isEmpty() && m_chartsFetchJobs == 0 )
{
foreach ( InfoRequestData request, m_cachedRequests )
{
emit info( request, m_allChartsMap );
Tomahawk::InfoSystem::InfoStringHash criteria;
criteria[ "InfoChartCapabilities" ] = "spotifyplugin";
emit updateCache( criteria, Q_INT64_C(604800000), request.type, m_allChartsMap );
}
m_cachedRequests.clear();
}
}
void
SpotifyPlugin::chartReturned()
{
/// Chart request returned something! Woho
QNetworkReply* reply = qobject_cast<QNetworkReply*>( sender() );
QString url = reply->url().toString();
QVariantMap returnedData;
if ( reply->error() == QNetworkReply::NoError )
{
bool ok;
QByteArray jsonData = reply->readAll();
QVariantMap res = TomahawkUtils::parseJson( jsonData, &ok ).toMap();
if ( !ok )
{
tLog() << "Failed to parse json from chart lookup:" << jsonData;
return;
}
/// SO we have a result, parse it!
QList< InfoStringHash > top_tracks;
QList< InfoStringHash > top_albums;
QStringList top_artists;
if( url.contains( "albums" ) )
setChartType( Album );
else if( url.contains( "tracks" ) )
setChartType( Track );
else if( url.contains( "artists" ) )
setChartType( Artist );
else
setChartType( None );
foreach( QVariant result, res.value( "toplist" ).toMap().value( "result" ).toList() )
{
QString title, artist;
QVariantMap chartMap = result.toMap();
if ( !chartMap.isEmpty() )
{
title = chartMap.value( "title" ).toString();
artist = chartMap.value( "artist" ).toString();
if( chartType() == Track )
{
InfoStringHash pair;
pair["artist"] = artist;
pair["track"] = title;
top_tracks << pair;
qDebug() << "SpotifyChart type is track";
}
if( chartType() == Album )
{
InfoStringHash pair;
pair["artist"] = artist;
pair["album"] = title;
top_albums << pair;
qDebug() << "SpotifyChart type is album";
}
if( chartType() == Artist )
{
top_artists << chartMap.value( "name" ).toString();
qDebug() << "SpotifyChart type is artist";
}
}
}
if( chartType() == Track )
{
tDebug() << "ChartsPlugin:" << "\tgot " << top_tracks.size() << " tracks";
returnedData["tracks"] = QVariant::fromValue( top_tracks );
returnedData["type"] = "tracks";
}
if( chartType() == Album )
{
tDebug() << "ChartsPlugin:" << "\tgot " << top_albums.size() << " albums";
returnedData["albums"] = QVariant::fromValue( top_albums );
returnedData["type"] = "albums";
}
if( chartType() == Artist )
{
tDebug() << "ChartsPlugin:" << "\tgot " << top_artists.size() << " artists";
returnedData["artists"] = top_artists;
returnedData["type"] = "artists";
}
Tomahawk::InfoSystem::InfoRequestData requestData = reply->property( "requestData" ).value< Tomahawk::InfoSystem::InfoRequestData >();
emit info( requestData, returnedData );
// update cache
Tomahawk::InfoSystem::InfoStringHash criteria;
Tomahawk::InfoSystem::InfoStringHash origData = requestData.input.value< Tomahawk::InfoSystem::InfoStringHash >();
criteria[ "chart_id" ] = origData[ "chart_id" ];
criteria[ "chart_source" ] = origData[ "chart_source" ];
emit updateCache( criteria, Q_INT64_C(86400000), requestData.type, returnedData );
}
else
qDebug() << "Network error in fetching chart:" << reply->url().toString();
}
Q_EXPORT_PLUGIN2( Tomahawk::InfoSystem::InfoPlugin, Tomahawk::InfoSystem::SpotifyPlugin )

View File

@@ -1,89 +0,0 @@
/* === This file is part of Tomahawk Player - <http://tomahawk-player.org> ===
*
* Copyright 2010-2011, Hugo Lindström <hugolm84@gmail.com>
* Copyright 2010-2011, Jeff Mitchell <jeff@tomahawk-player.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 SpotifyPlugin_H
#define SpotifyPlugin_H
#include "infosystem/InfoSystem.h"
#include "infosystem/InfoSystemWorker.h"
#include "../../InfoPluginDllMacro.h"
#include <QNetworkReply>
#include <QObject>
class QNetworkReply;
namespace Tomahawk
{
namespace InfoSystem
{
class INFOPLUGINDLLEXPORT SpotifyPlugin : public InfoPlugin
{
Q_PLUGIN_METADATA( IID "org.tomahawk-player.Player.InfoPlugin" )
Q_OBJECT
Q_INTERFACES( Tomahawk::InfoSystem::InfoPlugin )
public:
SpotifyPlugin();
virtual ~SpotifyPlugin();
enum ChartType {
None = 0x00,
Track = 0x01,
Album = 0x02,
Artist = 0x04
};
void setChartType( ChartType type ) { m_chartType = type; }
ChartType chartType() const { return m_chartType; }
public slots:
void chartReturned();
void chartTypes();
protected slots:
virtual void init() {}
virtual void getInfo( Tomahawk::InfoSystem::InfoRequestData requestData );
virtual void notInCacheSlot( Tomahawk::InfoSystem::InfoStringHash criteria, Tomahawk::InfoSystem::InfoRequestData requestData );
virtual void pushInfo( Tomahawk::InfoSystem::InfoPushData pushData )
{
Q_UNUSED( pushData );
}
private:
void fetchChart( Tomahawk::InfoSystem::InfoRequestData requestData );
void fetchChartCapabilities( Tomahawk::InfoSystem::InfoRequestData requestData );
void dataError( Tomahawk::InfoSystem::InfoRequestData requestData );
ChartType m_chartType;
QVariantMap m_allChartsMap;
uint m_chartsFetchJobs;
QList< InfoRequestData > m_cachedRequests;
};
}
}
#endif // SpotifyPlugin_H

View File

@@ -188,5 +188,5 @@ protected:
CustomAtticaAccount( const QString& id ) : Tomahawk::Accounts::Account( id ) {}
};
Q_DECLARE_METATYPE( Attica::Content );
Q_DECLARE_METATYPE( Attica::Content )
#endif // ATTICAMANAGER_H

View File

@@ -82,8 +82,6 @@ set( libGuiSources
playlist/dynamic/widgets/CollapsibleControls.cpp
playlist/dynamic/widgets/DynamicSetupWidget.cpp
resolvers/ExternalResolverGui.cpp
resolvers/ScriptResolver.cpp
resolvers/JSInfoPlugin.cpp
resolvers/JSInfoSystemHelper.cpp
resolvers/JSResolver.cpp
@@ -95,7 +93,6 @@ set( libGuiSources
utils/WidgetDragFilter.cpp
utils/XspfGenerator.cpp
utils/JspfLoader.cpp
utils/SpotifyParser.cpp
utils/M3uLoader.cpp
utils/ItunesParser.cpp
utils/ItunesLoader.cpp
@@ -211,11 +208,6 @@ list(APPEND libSources
accounts/ConfigStorage.cpp
accounts/LocalConfigStorage.cpp
accounts/spotify/SpotifyAccount.cpp
accounts/spotify/SpotifyAccountConfig.cpp
# accounts/spotify/SpotifyPlaylistUpdater.cpp
accounts/spotify/SpotifyInfoPlugin.cpp
audio/AudioEngine.cpp
audio/AudioOutput.cpp
audio/MediaStream.cpp
@@ -337,6 +329,7 @@ list(APPEND libSources
resolvers/ExternalResolver.cpp
resolvers/Resolver.cpp
resolvers/ScriptCollection.cpp
resolvers/ScriptCommand.cpp
resolvers/ScriptCommand_AllArtists.cpp
resolvers/ScriptCommand_AllAlbums.cpp
resolvers/ScriptCommand_AllTracks.cpp
@@ -382,7 +375,6 @@ set( libUI ${libUI}
playlist/QueueView.ui
filemetadata/MetadataEditor.ui
accounts/AccountFactoryWrapper.ui
accounts/spotify/SpotifyAccountConfig.ui
accounts/lastfm/LastFmConfig.ui
)
@@ -540,7 +532,6 @@ INSTALL( TARGETS tomahawklib
file( GLOB rootHeaders "*.h" )
file( GLOB accountsHeaders "accounts/*.h" )
file( GLOB accountsLastfmHeaders "accounts/lastfm/*.h" )
file( GLOB accountsSpotifyHeaders "accounts/spotify/*.h" )
file( GLOB audioHeaders "audio/*.h" )
file( GLOB collectionHeaders "collection/*.h" )
file( GLOB contextHeaders "context/*.h" )
@@ -569,7 +560,6 @@ install( FILES ${CMAKE_CURRENT_BINARY_DIR}/config.h DESTINATION include/libtomah
install( FILES ${rootHeaders} DESTINATION include/libtomahawk )
install( FILES ${accountsHeaders} DESTINATION include/libtomahawk/accounts )
install( FILES ${accountsLastfmHeaders} DESTINATION include/libtomahawk/accounts/lastfm )
install( FILES ${accountsSpotifyHeaders} DESTINATION include/libtomahawk/accounts/spotify )
install( FILES ${audioHeaders} DESTINATION include/libtomahawk/audio )
install( FILES ${collectionHeaders} DESTINATION include/libtomahawk/collection )
install( FILES ${contextHeaders} DESTINATION include/libtomahawk/context )

View File

@@ -27,7 +27,6 @@
#include "jobview/ErrorStatusMessage.h"
#include "playlist/PlaylistTemplate.h"
#include "resolvers/ExternalResolver.h"
#include "utils/SpotifyParser.h"
#include "utils/ItunesParser.h"
#include "utils/ItunesLoader.h"
#include "utils/M3uLoader.h"
@@ -52,7 +51,6 @@
using namespace Tomahawk;
bool DropJob::s_canParseSpotifyPlaylists = false;
static QString s_dropJobInfoId = "dropjob";
DropJob::DropJob( QObject *parent )
@@ -165,10 +163,6 @@ DropJob::acceptsMimeData( const QMimeData* data, DropJob::DropTypes acceptedType
if ( urlList.contains( "xspf" ) )
return true;
// Not the most elegant
if ( url.contains( "spotify" ) && url.contains( "playlist" ) && s_canParseSpotifyPlaylists )
return true;
if ( url.contains( "grooveshark.com" ) && url.contains( "playlist" ) )
return true;
@@ -191,9 +185,6 @@ DropJob::acceptsMimeData( const QMimeData* data, DropJob::DropTypes acceptedType
if ( url.contains( "itunes" ) && url.contains( "album" ) ) // YES itunes is fucked up and song links have album/ in the url.
return true;
if ( url.contains( "spotify" ) && url.contains( "track" ) )
return true;
if ( url.contains( "rdio.com" ) && ( ( ( url.contains( "track" ) && url.contains( "artist" ) && url.contains( "album" ) )
|| url.contains( "playlists" ) ) ) )
return true;
@@ -210,8 +201,6 @@ DropJob::acceptsMimeData( const QMimeData* data, DropJob::DropTypes acceptedType
{
if ( url.contains( "itunes" ) && url.contains( "album" ) ) // YES itunes is fucked up and song links have album/ in the url.
return true;
if ( url.contains( "spotify" ) && url.contains( "album" ) )
return true;
if ( url.contains( "rdio.com" ) && ( url.contains( "artist" ) && url.contains( "album" ) && !url.contains( "track" ) ) )
return true;
@@ -227,8 +216,6 @@ DropJob::acceptsMimeData( const QMimeData* data, DropJob::DropTypes acceptedType
{
if ( url.contains( "itunes" ) && url.contains( "artist" ) ) // YES itunes is fucked up and song links have album/ in the url.
return true;
if ( url.contains( "spotify" ) && url.contains( "artist" ) )
return true;
if ( url.contains( "rdio.com" ) && ( url.contains( "artist" ) && !url.contains( "album" ) && !url.contains( "track" ) ) )
return true;
@@ -290,10 +277,6 @@ DropJob::isDropType( DropJob::DropType desired, const QMimeData* data )
if ( url.contains( "m3u" ) || urlList.contains( "m3u" ) )
return true;
// Not the most elegant
if ( url.contains( "spotify" ) && url.contains( "playlist" ) && s_canParseSpotifyPlaylists )
return true;
if ( url.contains( "rdio.com" ) && url.contains( "people" ) && url.contains( "playlist" ) )
return true;
#ifdef QCA2_FOUND
@@ -632,33 +615,6 @@ DropJob::handleXspfs( const QString& fileUrls )
}
void
DropJob::handleSpotifyUrls( const QString& urlsRaw )
{
// Todo: Allow search querys, and split these in a better way.
// Example: spotify:search:artist:Madonna year:<1970 year:>1990
QStringList urls = urlsRaw.split( QRegExp( "\\s+" ), QString::SkipEmptyParts );
qDebug() << "Got spotify browse uris!" << urls;
/// Lets allow parsing all spotify uris here, if parse server is not available
/// fallback to spotify metadata for tracks /hugo
if ( dropAction() == Default )
setDropAction( Create );
tDebug() << "Got a spotify browse uri in dropjob!" << urls;
SpotifyParser* spot = new SpotifyParser( urls, dropAction() == Create, this );
spot->setSingleMode( false );
/// This currently supports draging and dropping a spotify playlist and artist
if ( dropAction() == Append )
{
tDebug() << Q_FUNC_INFO << "Asking for spotify browse contents from" << urls;
connect( spot, SIGNAL( tracks( QList<Tomahawk::query_ptr> ) ), this, SLOT( onTracksAdded( QList< Tomahawk::query_ptr > ) ) );
m_queryCount++;
}
}
void
DropJob::handleGroovesharkUrls ( const QString& urlsRaw )
{
@@ -684,20 +640,6 @@ DropJob::handleGroovesharkUrls ( const QString& urlsRaw )
}
bool
DropJob::canParseSpotifyPlaylists()
{
return s_canParseSpotifyPlaylists;
}
void
DropJob::setCanParseSpotifyPlaylists( bool parseable )
{
s_canParseSpotifyPlaylists = parseable;
}
void
DropJob::handleAllUrls( const QString& urls )
{
@@ -705,10 +647,6 @@ DropJob::handleAllUrls( const QString& urls )
handleXspfs( urls );
else if ( urls.contains( "m3u" ) )
handleM3u( urls );
else if ( urls.contains( "spotify" ) /// Handle all the spotify uris on internal server, if not avail. fallback to spotify
&& ( urls.contains( "playlist" ) || urls.contains( "artist" ) || urls.contains( "album" ) || urls.contains( "track" ) )
&& s_canParseSpotifyPlaylists )
handleSpotifyUrls( urls );
#ifdef QCA2_FOUND
else if ( urls.contains( "grooveshark.com" ) )
handleGroovesharkUrls( urls );
@@ -736,15 +674,6 @@ DropJob::handleTrackUrls( const QString& urls )
connect( itunes, SIGNAL( tracks( QList<Tomahawk::query_ptr> ) ), this, SLOT( onTracksAdded( QList< Tomahawk::query_ptr > ) ) );
m_queryCount++;
}
else if ( urls.contains( "open.spotify.com/track") || urls.contains( "spotify:track" ) )
{
QStringList tracks = urls.split( QRegExp( "\\s+" ), QString::SkipEmptyParts );
tDebug() << "Got a list of spotify urls!" << tracks;
SpotifyParser* spot = new SpotifyParser( tracks, false, this );
connect( spot, SIGNAL( tracks( QList<Tomahawk::query_ptr> ) ), this, SLOT( onTracksAdded( QList< Tomahawk::query_ptr > ) ) );
m_queryCount++;
}
else if ( ShortenedLinkParser::handlesUrl( urls ) )
{
QStringList tracks = urls.split( QRegExp( "\\s+" ), QString::SkipEmptyParts );

View File

@@ -112,12 +112,8 @@ public:
void tracksFromMimeData( const QMimeData* data, bool allowDuplicates = false, bool onlyLocal = false, bool top10 = false );
void handleXspfs( const QString& files );
void handleM3u( const QString& urls );
void handleSpotifyUrls( const QString& urls );
void handleGroovesharkUrls( const QString& urls );
static bool canParseSpotifyPlaylists();
static void setCanParseSpotifyPlaylists( bool parseable );
signals:
/// QMimeData parsing results
void tracks( const QList< Tomahawk::query_ptr >& tracks );
@@ -161,8 +157,6 @@ private:
QList< Tomahawk::query_ptr > m_resultList;
QSet< Tomahawk::album_ptr > m_albumsToKeep;
QSet< Tomahawk::artist_ptr > m_artistsToKeep;
static bool s_canParseSpotifyPlaylists;
};
Q_DECLARE_OPERATORS_FOR_FLAGS(DropJob::DropTypes)

View File

@@ -24,7 +24,6 @@
#include "GlobalActionManager.h"
#include "accounts/AccountManager.h"
#include "accounts/spotify/SpotifyAccount.h"
#include "accounts/ResolverAccount.h"
#include "audio/AudioEngine.h"
#include "database/LocalCollection.h"
@@ -43,7 +42,6 @@
#include "utils/NetworkAccessManager.h"
#include "utils/ShortenedLinkParser.h"
#include "utils/ShortLinkHelper.h"
#include "utils/SpotifyParser.h"
#include "utils/TomahawkUtils.h"
#include "utils/XspfLoader.h"
#include "utils/XspfGenerator.h"
@@ -152,32 +150,6 @@ GlobalActionManager::installResolverFromFile( const QString& resolverPath )
const QFileInfo resolverAbsoluteFilePath( resolverPath );
TomahawkSettings::instance()->setScriptDefaultPath( resolverAbsoluteFilePath.absolutePath() );
if ( resolverAbsoluteFilePath.baseName() == "spotify_tomahawkresolver" )
{
// HACK if this is a spotify resolver, we treat it specially.
// usually we expect the user to just download the spotify resolver from attica,
// however developers, those who build their own tomahawk, can't do that, or linux
// users can't do that. However, we have an already-existing SpotifyAccount that we
// know exists that we need to use this resolver path.
//
// Hence, we special-case the spotify resolver and directly set the path on it here.
Accounts::SpotifyAccount* acct = 0;
foreach ( Accounts::Account* account, Accounts::AccountManager::instance()->accounts() )
{
if ( Accounts::SpotifyAccount* spotify = qobject_cast< Accounts::SpotifyAccount* >( account ) )
{
acct = spotify;
break;
}
}
if ( acct )
{
acct->setManualResolverPath( resolverPath );
return;
}
}
Accounts::Account* acct =
Accounts::AccountManager::instance()->accountFromPath( resolverPath );
@@ -221,8 +193,6 @@ GlobalActionManager::openUrl( const QString& url )
// Native Implementations
if ( url.startsWith( "tomahawk://" ) )
return parseTomahawkLink( url );
else if ( url.contains( "open.spotify.com" ) || url.startsWith( "spotify:" ) )
return openSpotifyLink( url );
// Can we parse the Url using a ScriptResolver?
bool canParse = false;
@@ -786,9 +756,6 @@ GlobalActionManager::doQueueAdd( const QStringList& parts, const QList< QPair< Q
{
if ( parts.size() && parts[ 0 ] == "track" )
{
if ( queueSpotify( parts, queryItems ) )
return true;
QPair< QString, QString > pair;
QString title, artist, album, urlStr;
foreach ( pair, queryItems )
@@ -903,29 +870,6 @@ GlobalActionManager::doQueueAdd( const QStringList& parts, const QList< QPair< Q
}
bool
GlobalActionManager::queueSpotify( const QStringList& , const QList< QPair< QString, QString > >& queryItems )
{
QString url;
QPair< QString, QString > pair;
foreach ( pair, queryItems )
{
if ( pair.first == "spotifyURL" )
url = pair.second;
else if ( pair.first == "spotifyURI" )
url = pair.second;
}
if ( url.isEmpty() )
return false;
openSpotifyLink( url );
return true;
}
bool
GlobalActionManager::handleSearchCommand( const QUrl& url )
{
@@ -1229,9 +1173,6 @@ GlobalActionManager::handlePlayCommand( const QUrl& url )
if ( parts[ 0 ] == "track" )
{
if ( playSpotify( url ) )
return true;
QPair< QString, QString > pair;
QString title, artist, album, urlStr;
foreach ( pair, urlQueryItems( url ) )
@@ -1264,20 +1205,6 @@ GlobalActionManager::handlePlayCommand( const QUrl& url )
}
bool
GlobalActionManager::playSpotify( const QUrl& url )
{
if ( !urlHasQueryItem( url, "spotifyURI" ) && !urlHasQueryItem( url, "spotifyURL" ) )
return false;
QString spotifyUrl = urlHasQueryItem( url, "spotifyURI" ) ? urlQueryItemValue( url, "spotifyURI" ) : urlQueryItemValue( url, "spotifyURL" );
SpotifyParser* p = new SpotifyParser( spotifyUrl, false, this );
connect( p, SIGNAL( track( Tomahawk::query_ptr ) ), this, SLOT( playOrQueueNow( Tomahawk::query_ptr ) ) );
return true;
}
void
GlobalActionManager::playNow( const query_ptr& q )
{
@@ -1342,18 +1269,6 @@ GlobalActionManager::waitingForResolved( bool /* success */ )
}
/// SPOTIFY URL HANDLING
bool
GlobalActionManager::openSpotifyLink( const QString& link )
{
SpotifyParser* spot = new SpotifyParser( link, false, this );
connect( spot, SIGNAL( track( Tomahawk::query_ptr ) ), this, SLOT( handleOpenTrack( Tomahawk::query_ptr ) ) );
return true;
}
QString
GlobalActionManager::hostname() const
{

View File

@@ -55,9 +55,6 @@ public slots:
*/
bool openUrl( const QString& url );
/// Takes a spotify link and performs the default open action on it
bool openSpotifyLink( const QString& link );
/// Creates a link from the requested data and copies it to the clipboard
void copyToClipboard( const Tomahawk::query_ptr& query );
@@ -90,7 +87,7 @@ private slots:
void playNow( const Tomahawk::query_ptr& );
private:
explicit GlobalActionManager( QObject* parent = 0 );
explicit GlobalActionManager( QObject* parent = nullptr );
/// handle opening of urls
bool handlePlaylistCommand( const QUrl& url );
@@ -102,9 +99,6 @@ private:
bool handleImportCommand( const QUrl& url );
bool doQueueAdd( const QStringList& parts, const QList< QPair< QString, QString > >& queryItems );
bool playSpotify( const QUrl& url );
bool queueSpotify( const QStringList& parts, const QList< QPair< QString, QString > >& queryItems );
bool handleCollectionCommand( const QUrl& url );
bool handlePlayCommand( const QUrl& url );
bool handleOpenCommand( const QUrl& url );

View File

@@ -22,7 +22,6 @@
#include "database/Database.h"
#include "resolvers/ExternalResolver.h"
#include "resolvers/ScriptResolver.h"
#include "resolvers/JSResolver.h"
#include "utils/ResultUrlChecker.h"
#include "utils/Logger.h"

View File

@@ -24,7 +24,7 @@
#include "database/DatabaseCommand_AllTracks.h"
#include "database/DatabaseCommand_AddFiles.h"
#include "filemetadata/MetadataEditor.h"
#include "resolvers/ExternalResolverGui.h"
#include "resolvers/ExternalResolver.h"
#include "resolvers/Resolver.h"
#include "utils/TomahawkUtilsGui.h"
#include "utils/Logger.h"

View File

@@ -318,60 +318,8 @@ AccountDelegate::paint ( QPainter* painter, const QStyleOptionViewItem& option,
painter->drawText( descRect, Qt::AlignLeft | Qt::TextWordWrap | Qt::AlignTop, desc );
runningBottom = descRect.bottom();
if ( index.data( AccountModel::CanRateRole ).toBool() )
{
// rating stars
const int rating = index.data( AccountModel::RatingRole ).toInt();
// int runningEdge = opt.rect.right() - 2*PADDING - ratingWidth;
int runningEdge = textRect.left();
// int starsTop = opt.rect.bottom() - 3*PADDING - m_ratingStarNegative.height();
int starsTop = runningBottom + PADDING;
for ( int i = 1; i < 6; i++ )
{
QRect r( runningEdge, starsTop, STAR_SIZE, STAR_SIZE );
// QRect r( runningEdge, opt.rect.top() + PADDING, m_ratingStarPositive.width(), m_ratingStarPositive.height() );
if ( i == 1 )
m_cachedStarRects[ index ] = r;
const bool userHasRated = index.data( AccountModel::UserHasRatedRole ).toBool();
if ( !userHasRated && // Show on-hover animation if the user hasn't rated it yet, and is hovering over it
m_hoveringOver > -1 &&
m_hoveringItem == index )
{
if ( i <= m_hoveringOver ) // positive star
painter->drawPixmap( r, TomahawkUtils::defaultPixmap( TomahawkUtils::StarHovered, TomahawkUtils::Original, r.size() ) );
else
painter->drawPixmap( r, TomahawkUtils::defaultPixmap( TomahawkUtils::Unstarred, TomahawkUtils::Original, r.size() ) );
}
else
{
if ( i <= rating ) // positive or rated star
{
if ( userHasRated )
painter->drawPixmap( r, TomahawkUtils::defaultPixmap( TomahawkUtils::StarHovered, TomahawkUtils::Original, r.size() ) );
else
painter->drawPixmap( r, TomahawkUtils::defaultPixmap( TomahawkUtils::Starred, TomahawkUtils::Original, r.size() ) );
}
else
painter->drawPixmap( r, TomahawkUtils::defaultPixmap( TomahawkUtils::Unstarred, TomahawkUtils::Original, r.size() ) );
}
runningEdge += STAR_SIZE + PADDING_BETWEEN_STARS;
}
// downloaded num times
QString count = tr( "%1 downloads" ).arg( index.data( AccountModel::DownloadCounterRole ).toInt() );
painter->setFont( descFont );
const int countW = painter->fontMetrics().width( count );
const QRect countRect( runningEdge + 50, starsTop, countW, painter->fontMetrics().height() );
count = painter->fontMetrics().elidedText( count, Qt::ElideRight, rightEdge - PADDING - countRect.left() );
painter->drawText( countRect, Qt::AlignLeft, count );
// runningEdge = authorRect.x();
}
else //no rating, it's not attica, let's show other stuff...
{
// TODO: Readd download count
QString versionString = index.data( AccountModel::VersionRole ).toString();
if ( !versionString.isEmpty() )
{
int runningEdge = textRect.left();
@@ -384,7 +332,6 @@ AccountDelegate::paint ( QPainter* painter, const QStyleOptionViewItem& option,
QRect textRect( runningEdge + PADDING + h, pkgTop, painter->fontMetrics().width( versionString ), h );
painter->drawText( textRect, Qt::AlignLeft, versionString );
}
}
// Title and description!
return;
@@ -501,35 +448,6 @@ AccountDelegate::editorEvent( QEvent* event, QAbstractItemModel* model, const QS
}
}
if ( m_cachedStarRects.contains( index ) )
{
QRect fullStars = m_cachedStarRects[ index ];
const int starsWidth = 5 * ( STAR_SIZE + PADDING_BETWEEN_STARS );
fullStars.setWidth( starsWidth );
QMouseEvent* me = static_cast< QMouseEvent* >( event );
if ( fullStars.contains( me->pos() ) )
{
const int eachStar = starsWidth / 5;
const int clickOffset = me->pos().x() - fullStars.x();
const int whichStar = (clickOffset / eachStar) + 1;
if ( event->type() == QEvent::MouseButtonRelease )
{
model->setData( index, whichStar, AccountModel::RatingRole );
}
else if ( event->type() == QEvent::MouseMove )
{
// 0-indexed
m_hoveringOver = whichStar;
m_hoveringItem = index;
}
return true;
}
}
if ( m_hoveringOver > -1 )
{
emit update( m_hoveringItem );

View File

@@ -83,7 +83,6 @@ private:
int m_hoveringOver;
QPersistentModelIndex m_hoveringItem, m_configPressed;
mutable QHash< QPersistentModelIndex, QRect > m_cachedButtonRects;
mutable QHash< QPersistentModelIndex, QRect > m_cachedStarRects;
mutable QHash< QPersistentModelIndex, QRect > m_cachedConfigRects;
mutable QHash< QPersistentModelIndex, QSize > m_sizeHints;
mutable QHash< QPersistentModelIndex, AnimatedSpinner* > m_loadingSpinners;

View File

@@ -264,8 +264,6 @@ AccountModel::data( const QModelIndex& index, int role ) const
return c.author();
case RowTypeRole:
return TopLevelAccount;
case RatingRole:
return c.rating() / 20; // rating is out of 100
case DownloadCounterRole:
return c.downloads();
case CanRateRole:
@@ -411,8 +409,6 @@ AccountModel::data( const QModelIndex& index, int role ) const
return hasAttica;
case AuthorRole:
return hasAttica ? content.author() : QString();
case RatingRole:
return hasAttica ? content.rating() / 20 : 0; // rating is out of 100
case DownloadCounterRole:
return hasAttica ? content.downloads() : QVariant();
case RowTypeRole:
@@ -595,39 +591,6 @@ AccountModel::setData( const QModelIndex& index, const QVariant& value, int role
return false;
}
if ( role == RatingRole )
{
// We only support rating Attica resolvers for the moment.
Attica::Content content;
if ( node->type == AccountModelNode::AtticaType )
{
content = node->atticaContent;
AtticaManager::ResolverState state = AtticaManager::instance()->resolverState( content );
// For now only allow rating if a resolver is installed!
if ( state != AtticaManager::Installed && state != AtticaManager::NeedsUpgrade )
return false;
} // Allow rating custom attica accounts regardless as user may have installed manually
else if ( node->type == AccountModelNode::CustomAccountType && qobject_cast< CustomAtticaAccount* >( node->customAccount ) )
content = qobject_cast< CustomAtticaAccount* >( node->customAccount )->atticaContent();
Q_ASSERT( !content.id().isNull() );
if ( AtticaManager::instance()->userHasRated( content ) )
return false;
content.setRating( value.toInt() * 20 );
AtticaManager::instance()->uploadRating( content );
if ( node->type == AccountModelNode::AtticaType )
node->atticaContent = content;
emit dataChanged( index, index );
return true;
}
return false;
}

View File

@@ -27,7 +27,6 @@
#include "AtticaManager.h"
#include "ConfigStorage.h"
#include "resolvers/ExternalResolver.h"
#include "resolvers/ExternalResolverGui.h"
#include "utils/Json.h"
#include "utils/Logger.h"
@@ -73,6 +72,38 @@ ResolverAccountFactory::createFromPath( const QString& path )
}
/**
* Check if the bundle specifies a platform, and if so, reject the
* resolver if the platform is wrong.
*/
bool
isPlatformSupported( const QVariant& variant )
{
// No platform specified, so accept it.
if ( variant.isNull() )
return true;
const QString platform( variant.toString() );
// We accept any platform
if ( platform == "any" )
return true;
QString myPlatform( "any" );
#if defined( Q_OS_WIN )
myPlatform = "win";
#elif defined( Q_OS_MAC )
myPlatform = "osx";
#elif defined( Q_OS_LINUX )
if ( __WORDSIZE == 32 )
myPlatform = "linux-x86";
else if ( __WORDSIZE == 64 )
myPlatform = "linux-x64";
#endif
return myPlatform.contains( platform );
}
Account*
ResolverAccountFactory::createFromPath( const QString& path, const QString& factory, bool isAttica )
{
@@ -108,16 +139,16 @@ ResolverAccountFactory::createFromPath( const QString& path, const QString& fact
QDir dir( TomahawkUtils::extractScriptPayload( pathInfo.filePath(),
uniqueName,
MANUALRESOLVERS_DIR ) );
if ( !( dir.exists() && dir.isReadable() ) ) //decompression fubar
if ( !( dir.exists() && dir.isReadable() ) )
{
displayError( tr( "Resolver installation error: cannot open bundle." ) );
return 0;
return nullptr;
}
if ( !dir.cd( "content" ) ) //more fubar
if ( !dir.cd( "content" ) )
{
displayError( tr( "Resolver installation error: incomplete bundle." ) );
return 0;
return nullptr;
}
QString metadataFilePath = dir.absoluteFilePath( "metadata.json" );
@@ -129,7 +160,7 @@ ResolverAccountFactory::createFromPath( const QString& path, const QString& fact
{
dir.cdUp();
if ( !dir.cdUp() ) //we're in MANUALRESOLVERS_DIR
return 0;
return nullptr;
QString name = configuration[ "pluginName" ].toString();
@@ -146,7 +177,7 @@ ResolverAccountFactory::createFromPath( const QString& path, const QString& fact
configuration[ "bundleDir" ] = name;
if ( !dir.cd( QString( "%1/content" ).arg( name ) ) ) //should work if it worked once
return 0;
return nullptr;
}
expandPaths( dir, configuration );
@@ -155,7 +186,7 @@ ResolverAccountFactory::createFromPath( const QString& path, const QString& fact
if ( realPath.isEmpty() )
{
displayError( tr( "Resolver installation error: bad metadata in bundle." ) );
return 0;
return nullptr;
}
}
else //either legacy resolver or uncompressed bundle, so we look for a metadata file
@@ -171,28 +202,10 @@ ResolverAccountFactory::createFromPath( const QString& path, const QString& fact
//else we just have empty metadata (legacy resolver without desktop file)
}
//check if the bundle specifies a platform, and if so, reject the resolver if the platform is wrong
if ( !configuration[ "platform" ].isNull() && configuration[ "platform" ].toString() != "any" )
{
QString platform( configuration[ "platform" ].toString() );
QString myPlatform( "any" );
#if defined( Q_OS_WIN )
myPlatform = "win";
#elif defined( Q_OS_MAC )
myPlatform = "osx";
#elif defined( Q_OS_LINUX )
if ( __WORDSIZE == 32 )
myPlatform = "linux-x86";
else if ( __WORDSIZE == 64 )
myPlatform = "linux-x64";
#endif
if ( !myPlatform.contains( platform ) )
if ( !isPlatformSupported( configuration[ "platform" ] ) )
{
displayError( tr( "Resolver installation error: platform mismatch." ) );
return 0;
}
return nullptr;
}
if ( !configuration[ "tomahawkVersion" ].isNull() )
@@ -204,7 +217,7 @@ ResolverAccountFactory::createFromPath( const QString& path, const QString& fact
{
displayError( tr( "Resolver installation error: Tomahawk %1 or newer is required." )
.arg( requiredVer ) );
return 0;
return nullptr;
}
}
@@ -352,8 +365,7 @@ ResolverAccount::hookupResolver()
if ( configuration().contains( "scripts" ) )
additionalPaths = configuration().value( "scripts" ).toStringList();
Tomahawk::ExternalResolver* er = Pipeline::instance()->addScriptResolver( accountId(), mainScriptPath, additionalPaths );
m_resolver = QPointer< ExternalResolverGui >( qobject_cast< ExternalResolverGui* >( er ) );
m_resolver = Pipeline::instance()->addScriptResolver( accountId(), mainScriptPath, additionalPaths );
connect( m_resolver.data(), SIGNAL( changed() ), this, SLOT( resolverChanged() ) );
// What resolver do we have here? Should only be types that are 'real' resolvers

View File

@@ -29,7 +29,7 @@ class QDir;
namespace Tomahawk {
class ExternalResolverGui;
class ExternalResolver;
namespace Accounts {
@@ -107,7 +107,7 @@ protected:
ResolverAccount( const QString& accountId, const QString& path, const QVariantHash& initialConfiguration = QVariantHash() );
void hookupResolver();
QPointer<ExternalResolverGui> m_resolver;
QPointer<ExternalResolver> m_resolver;
private:
void init( const QString& path );

View File

@@ -21,7 +21,7 @@
#include "infosystem/InfoSystem.h"
#include "LastFmInfoPlugin.h"
#include "resolvers/ExternalResolverGui.h"
#include "resolvers/ExternalResolver.h"
#include "utils/TomahawkUtilsGui.h"
#include "AtticaManager.h"
#include "Pipeline.h"
@@ -286,11 +286,7 @@ LastFmAccount::hookupResolver()
const AtticaManager::Resolver data = AtticaManager::instance()->resolverData( res.id() );
m_resolver = QPointer< ExternalResolverGui >(
qobject_cast< ExternalResolverGui* >(
Pipeline::instance()->addScriptResolver( accountId(), data.scriptPath )
)
);
m_resolver = Pipeline::instance()->addScriptResolver( accountId(), data.scriptPath );
m_resolver.data()->setIcon( icon() );
connect( m_resolver.data(), SIGNAL( changed() ), this, SLOT( resolverChanged() ) );
}

View File

@@ -30,7 +30,7 @@
namespace Tomahawk
{
class ExternalResolverGui;
class ExternalResolver;
namespace InfoSystem
{
@@ -104,7 +104,7 @@ private slots:
private:
void hookupResolver();
QPointer<Tomahawk::ExternalResolverGui> m_resolver;
QPointer<Tomahawk::ExternalResolver> m_resolver;
QPointer<Tomahawk::InfoSystem::LastFmInfoPlugin> m_infoPlugin;
QPointer<LastFmConfig> m_configWidget;
};

View File

@@ -182,7 +182,7 @@ LastFmConfig::onHistoryLoaded()
else
finished = true;
}
catch( lastfm::ws::ParseError e )
catch( lastfm::ws::ParseError& e )
{
tDebug() << "XmlQuery error:" << e.message();
finished = true;
@@ -324,7 +324,7 @@ LastFmConfig::onLovedFinished( QNetworkReply* reply )
emit sizeHintChanged();
}
}
catch( lastfm::ws::ParseError e )
catch( lastfm::ws::ParseError& e )
{
m_ui->syncLovedTracks->setText( "Failed" );
m_ui->progressBar->hide();

File diff suppressed because it is too large Load Diff

View File

@@ -1,198 +0,0 @@
/* === This file is part of Tomahawk Player - <http://tomahawk-player.org> ===
*
* Copyright 2010-2011, Leo Franchi <lfranchi@kde.org>
* Copyright 2010-2011, Hugo Lindström <hugolm84@gmail.com>
*
* 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 SpotifyAccount_H
#define SpotifyAccount_H
#include "accounts/ResolverAccount.h"
#include "SourceList.h"
#include "AtticaManager.h"
#include "Playlist.h"
#include "utils/TomahawkUtils.h"
#include "utils/SmartPointerList.h"
#include "DllMacro.h"
#include <QAction>
//class SpotifyPlaylistUpdater;
class QTimer;
namespace Tomahawk {
class SpotifyParser;
class ScriptResolver;
namespace InfoSystem
{
class SpotifyInfoPlugin;
}
namespace Accounts {
class SpotifyAccountConfig;
// metadata for a playlist
struct SpotifyPlaylistInfo {
QString name, plid, revid;
bool sync, subscribed, changed, isOwner, starContainer;
bool loveSync;
SpotifyPlaylistInfo( const QString& nname, const QString& pid, const QString& rrevid, bool ssync, bool ssubscribed, bool isowner = false, bool star = false )
: name( nname ), plid( pid ), revid( rrevid ), sync( ssync ), subscribed( ssubscribed )
, changed( false ), isOwner( isowner ), starContainer( star ), loveSync( false ) {}
SpotifyPlaylistInfo() : sync( false ), changed( false ), starContainer( false ), loveSync( false ) {}
};
class DLLEXPORT SpotifyAccountFactory : public AccountFactory
{
Q_OBJECT
public:
SpotifyAccountFactory() {}
virtual Account* createAccount( const QString& accountId = QString() );
virtual QString description() const { return tr( "Play music from and sync your playlists with Spotify Premium" ); }
virtual QString factoryId() const { return "spotifyaccount"; }
virtual QString prettyName() const { return "Spotify"; }
virtual AccountTypes types() const { return AccountTypes( ResolverType ); }
virtual bool allowUserCreation() const { return false; }
virtual QPixmap icon() const;
virtual bool isUnique() const { return true; }
};
class DLLEXPORT SpotifyAccount : public CustomAtticaAccount
{
Q_OBJECT
public:
SpotifyAccount( const QString& accountId );
SpotifyAccount( const QString& accountId, const QString& path );
virtual ~SpotifyAccount();
static SpotifyAccount* instance();
virtual QPixmap icon() const;
virtual AccountConfigWidget* configurationWidget();
virtual QWidget* aboutWidget();
virtual void saveConfig();
virtual Attica::Content atticaContent() const;
virtual void authenticate();
virtual ConnectionState connectionState() const;
virtual bool isAuthenticated() const;
virtual void deauthenticate();
virtual QWidget* aclWidget() { return 0; }
virtual Tomahawk::InfoSystem::InfoPluginPtr infoPlugin();
virtual SipPlugin* sipPlugin( bool ) { return 0; }
virtual bool preventEnabling() const { return m_preventEnabling; }
// bool hasPlaylist( const QString& plId );
// Tomahawk::playlist_ptr playlistForURI( const QString& plId );
// void registerUpdaterForPlaylist( const QString& plId, SpotifyPlaylistUpdater* updater );
void registerPlaylistInfo( const QString& name, const QString& plid, const QString &revid, const bool sync, const bool subscribed , const bool owner = false );
void registerPlaylistInfo( SpotifyPlaylistInfo* info );
// void unregisterUpdater( const QString& plid );
bool deleteOnUnsync() const;
bool loveSync() const;
bool persitentPrivacy() const;
void starTrack( const QString& artist, const QString& title, const bool starred );
void setManualResolverPath( const QString& resolverPath );
bool loggedIn() const;
public slots:
QString sendMessage( const QVariantMap& msg, QObject* receiver = 0, const QString& slot = QString(), const QVariant& extraData = QVariant() );
void aboutToShow( QAction* action, const Tomahawk::playlist_ptr& playlist );
// void syncActionTriggered( QAction* action );
// void subscribeActionTriggered( QAction* action );
void atticaLoaded( Attica::Content::List );
// void collaborateActionTriggered( QAction* action );
private slots:
void resolverChanged();
void resolverInstalled( const QString& resolverId );
void resolverMessage( const QString& msgType, const QVariantMap& msg );
void privateModeChanged();
void login( const QString& username, const QString& password );
void logout();
// SpotifyResolver message handlers, all take msgtype, msg as argument
// void <here>( const QString& msgType, const QVariantMap& msg, const QVariant& extraData );
// void startPlaylistSyncWithPlaylist( const QString& msgType, const QVariantMap& msg, const QVariant& extraData );
// void playlistCreated( const QString& msgType, const QVariantMap& msg, const QVariant& extraData );
// void playlistCopyCreated( const QString& msgType, const QVariantMap& msg, const QVariant& extraData );
void delayedInit();
void hookupAfterDeletion( bool autoEnable );
private:
void init();
bool checkForResolver();
void hookupResolver();
void killExistingResolvers();
void loadPlaylists();
void clearUser( bool permanentlyDelete = false );
/* void startPlaylistSync( SpotifyPlaylistInfo* playlist );
void stopPlaylistSync( SpotifyPlaylistInfo* playlist, bool forceDontDelete = false );
void fetchFullPlaylist( SpotifyPlaylistInfo* playlist );
void setSyncForPlaylist( const QString& spotifyPlaylistId, bool sync );
void setSubscribedForPlaylist( const playlist_ptr& pl, bool subscribed );*/
// void createActions();
// void removeActions();
// playlist_ptr playlistFromAction( QAction* action ) const;
// SpotifyPlaylistUpdater* getPlaylistUpdater( const playlist_ptr plptr);
// SpotifyPlaylistUpdater* getPlaylistUpdater( QObject* sender );
static SpotifyAccount* s_instance;
QPointer<SpotifyAccountConfig> m_configWidget;
QPointer<QWidget> m_aboutWidget;
QPointer<ScriptResolver> m_spotifyResolver;
QPointer< InfoSystem::SpotifyInfoPlugin > m_infoPlugin;
QMap<QString, QPair<QObject*, QString> > m_qidToSlotMap;
QMap<QString, QVariant > m_qidToExtraData;
// List of synced spotify playlists in config UI
QHash< QString, SpotifyPlaylistInfo* > m_allSpotifyPlaylists;
// QHash< QString, SpotifyPlaylistUpdater* > m_updaters;
QHash< QString, playlist_ptr > m_waitingForCreateReply;
bool m_preventEnabling, m_loggedIn;
SmartPointerList< QAction > m_customActions;
// friend class ::SpotifyPlaylistUpdater;
friend class Tomahawk::SpotifyParser;
};
}
}
Q_DECLARE_METATYPE( Tomahawk::Accounts::SpotifyPlaylistInfo* )
Q_DECLARE_METATYPE( QAction* )
#endif // SpotifyAccount_H

View File

@@ -1,318 +0,0 @@
/* === 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 "utils/AnimatedSpinner.h"
#include "ui_SpotifyAccountConfig.h"
#include <QListWidget>
#include <QListWidgetItem>
#include <QShowEvent>
#include <QLabel>
using namespace Tomahawk;
using namespace Accounts;
bool InfoSorter( const SpotifyPlaylistInfo* left, const SpotifyPlaylistInfo* right )
{
return left->name < right->name;
}
SpotifyAccountConfig::SpotifyAccountConfig( SpotifyAccount *account )
: AccountConfigWidget( 0 )
, m_ui( new Ui::SpotifyConfig )
, m_loggedInUser( 0 )
, m_account( account )
, m_playlistsLoading( 0 )
, m_loggedInManually( false )
, m_isLoggedIn( false )
{
m_ui->setupUi( this );
m_ui->loginButton->setDefault( true );
connect( m_ui->loginButton, SIGNAL( clicked( bool ) ), this, SLOT( doLogin() ) );
connect( m_ui->loveSync, SIGNAL( toggled( bool ) ), this, SLOT( showStarredPlaylist( bool ) ) );
connect( m_ui->persitentPrivacy, SIGNAL( toggled( bool ) ), this, SIGNAL( updatePrivacy( bool ) ) );
connect( m_ui->usernameEdit, SIGNAL( textEdited( QString ) ), this, SLOT( resetLoginButton() ) );
connect( m_ui->passwordEdit, SIGNAL( textEdited( QString ) ), this, SLOT( resetLoginButton() ) );
connect( m_ui->selectAllCheckbox, SIGNAL( stateChanged( int ) ), this, SLOT( selectAllPlaylists() ) );
loadFromConfig();
m_ui->label->setVisible( false );
m_ui->label_3->setVisible( false );
m_ui->loveSync->setVisible( false );
m_ui->selectAllCheckbox->setVisible( false );
m_ui->deleteOnUnsync->setVisible( false );
m_ui->playlistList->setVisible( false );
// m_playlistsLoading = new AnimatedSpinner( m_ui->playlistList );
}
void
SpotifyAccountConfig::showEvent( QShowEvent *event )
{
Q_UNUSED( event );
loadFromConfig();
m_loggedInManually = false;
}
void
SpotifyAccountConfig::loadFromConfig()
{
const QString username = m_account->credentials().value( "username" ).toString();
m_ui->usernameEdit->setText( username );
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() );
m_ui->loveSync->setChecked( m_account->loveSync() );
m_ui->persitentPrivacy->setChecked( m_account->persitentPrivacy() );
if ( m_account->loggedIn() )
{
qDebug() << "Loading spotify config widget with logged in username:" << username;
if ( !username.isEmpty() )
m_verifiedUsername = username;
showLoggedIn();
}
else
showLoggedOut();
}
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 )
{
qDebug() << Q_FUNC_INFO << "Setting sync";
pl->changed = true;
pl->sync = toSync;
}
if ( ( pl->starContainer && loveSync() ) && ( pl->loveSync != loveSync() ) )
{
qDebug() << Q_FUNC_INFO << "Setting lovesync";
pl->loveSync = loveSync();
pl->changed = true;
}
}
}
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();
}
bool
SpotifyAccountConfig::loveSync() const
{
return m_ui->loveSync->isChecked();
}
bool
SpotifyAccountConfig::persitentPrivacy() const
{
return m_ui->persitentPrivacy->isChecked();
}
void
SpotifyAccountConfig::setPlaylists( const QList<SpotifyPlaylistInfo *>& playlists )
{
return;
// User always has at least 1 playlist (starred tracks)
if ( !playlists.isEmpty() )
m_playlistsLoading->fadeOut();
m_ui->playlistList->clear();
QList<SpotifyPlaylistInfo *> myList = playlists;
qSort( myList.begin(), myList.end(), InfoSorter );
foreach ( SpotifyPlaylistInfo* pl, myList )
{
bool starContainer = ( pl->starContainer || pl->name == "Starred Tracks" );
QListWidgetItem* item = new QListWidgetItem( pl->name, m_ui->playlistList );
item->setData( Qt::UserRole, QVariant::fromValue< SpotifyPlaylistInfo* >( pl ) );
item->setData( Qt::UserRole+2, starContainer );
if( loveSync() && starContainer )
item->setHidden(true);
item->setFlags( Qt::ItemIsUserCheckable | Qt::ItemIsSelectable | Qt::ItemIsEnabled );
item->setCheckState( pl->sync ? Qt::Checked : Qt::Unchecked );
}
}
void
SpotifyAccountConfig::doLogin()
{
if ( !m_isLoggedIn )
{
m_ui->loginButton->setText( tr( "Logging in..." ) );
m_ui->loginButton->setEnabled( false );
// m_playlistsLoading->fadeIn();
m_loggedInManually = true;
emit login( username(), password() );
}
else
{
// Log out
m_isLoggedIn = false;
m_loggedInManually = true;
m_verifiedUsername.clear();
m_ui->playlistList->clear();
m_ui->passwordEdit->clear();
emit logout();
showLoggedOut();
}
}
void
SpotifyAccountConfig::loginResponse( bool success, const QString& msg, const QString& username )
{
if ( success )
{
qDebug() << Q_FUNC_INFO << "Login response with username:" << username;
m_verifiedUsername = username;
m_isLoggedIn = true;
showLoggedIn();
}
else
{
/* setPlaylists( QList< SpotifyPlaylistInfo* >() );
m_playlistsLoading->fadeOut();*/
m_ui->loginButton->setText( tr( "Failed: %1" ).arg( msg ) );
m_ui->loginButton->setEnabled( true );
}
}
void
SpotifyAccountConfig::showStarredPlaylist( bool hide )
{
for ( int i = 0; i < m_ui->playlistList->count(); i++ )
{
QListWidgetItem* item = m_ui->playlistList->item( i );
if ( item->data( Qt::UserRole+2 ).toBool() )
item->setHidden( hide );
}
}
void
SpotifyAccountConfig::selectAllPlaylists()
{
for( int i = 0; i < m_ui->playlistList->count(); i++ )
{
QListWidgetItem* item = m_ui->playlistList->item( i );
item->setCheckState( m_ui->selectAllCheckbox->checkState() );
}
}
void
SpotifyAccountConfig::showLoggedIn()
{
m_ui->passwordEdit->hide();
m_ui->passwordLabel->hide();
m_ui->usernameEdit->hide();
m_ui->usernameLabel->hide();
if ( !m_loggedInUser )
{
m_loggedInUser = new QLabel( this );
m_ui->verticalLayout->insertWidget( 1, m_loggedInUser, 0, Qt::AlignCenter );
}
qDebug() << "Showing logged in withuserame:" << m_verifiedUsername;
m_loggedInUser->show();
m_loggedInUser->setText( tr( "Logged in as %1" ).arg( m_verifiedUsername ) );
m_ui->loginButton->setText( tr( "Log Out" ) );
m_ui->loginButton->setEnabled( true );
emit sizeHintChanged();
}
void
SpotifyAccountConfig::showLoggedOut()
{
m_ui->passwordEdit->show();
m_ui->passwordLabel->show();
m_ui->usernameEdit->show();
m_ui->usernameLabel->show();
if ( m_loggedInUser )
m_loggedInUser->hide();
m_ui->loginButton->setText( tr( "Log In" ) );
m_ui->loginButton->setEnabled( true );
emit sizeHintChanged();
}
void
SpotifyAccountConfig::resetLoginButton()
{
if ( !m_isLoggedIn )
{
m_ui->loginButton->setText( tr( "Log In" ) );
m_ui->loginButton->setEnabled( true );
}
}

View File

@@ -1,97 +0,0 @@
/* === 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 "accounts/AccountConfigWidget.h"
#include <QWidget>
#include <QVariantMap>
#include <QTimer>
class QLabel;
class AnimatedSpinner;
class QShowEvent;
namespace Ui
{
class SpotifyConfig;
}
namespace Tomahawk
{
namespace Accounts
{
class SpotifyAccount;
struct SpotifyPlaylistInfo;
class SpotifyAccountConfig : public AccountConfigWidget
{
Q_OBJECT
public:
explicit SpotifyAccountConfig( SpotifyAccount* account );
QString username() const;
QString password() const;
bool highQuality() const;
bool deleteOnUnsync() const;
bool loveSync() const;
bool persitentPrivacy() const;
void setPlaylists( const QList< SpotifyPlaylistInfo* >& playlists );
void loadFromConfig();
void saveSettings();
void loginResponse( bool success, const QString& msg, const QString& username );
bool loggedInManually() const { return m_loggedInManually; }
signals:
void login( const QString& username, const QString& pw );
void logout();
void updatePrivacy( bool );
void sizeHintChanged();
protected:
void showEvent( QShowEvent* event );
private slots:
void doLogin();
void resetLoginButton();
void selectAllPlaylists();
void showStarredPlaylist( bool );
private:
void showLoggedIn();
void showLoggedOut();
Ui::SpotifyConfig* m_ui;
QLabel* m_loggedInUser;
QString m_verifiedUsername;
SpotifyAccount* m_account;
AnimatedSpinner* m_playlistsLoading;
bool m_loggedInManually, m_isLoggedIn;
};
}
}
#endif // SPOTIFYACCOUNTCONFIG_H

View File

@@ -1,209 +0,0 @@
<?xml version="1.0" encoding="UTF-8"?>
<ui version="4.0">
<class>SpotifyConfig</class>
<widget class="QWidget" name="SpotifyConfig">
<property name="geometry">
<rect>
<x>0</x>
<y>0</y>
<width>406</width>
<height>534</height>
</rect>
</property>
<property name="sizePolicy">
<sizepolicy hsizetype="Fixed" vsizetype="Fixed">
<horstretch>0</horstretch>
<verstretch>0</verstretch>
</sizepolicy>
</property>
<property name="windowTitle">
<string notr="true" extracomment="not translatable because not shown to the user">Form</string>
</property>
<layout class="QVBoxLayout" name="verticalLayout">
<item>
<layout class="QHBoxLayout" name="horizontalLayout_2">
<item>
<widget class="QLabel" name="icon">
<property name="sizePolicy">
<sizepolicy hsizetype="Fixed" vsizetype="Fixed">
<horstretch>0</horstretch>
<verstretch>0</verstretch>
</sizepolicy>
</property>
<property name="minimumSize">
<size>
<width>64</width>
<height>64</height>
</size>
</property>
<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">
<font>
<pointsize>16</pointsize>
<weight>75</weight>
<bold>true</bold>
</font>
</property>
<property name="text">
<string>Configure your Spotify account</string>
</property>
<property name="textFormat">
<enum>Qt::PlainText</enum>
</property>
<property name="alignment">
<set>Qt::AlignCenter</set>
</property>
</widget>
</item>
</layout>
</item>
<item>
<layout class="QHBoxLayout" name="horizontalLayout_4">
<item>
<layout class="QFormLayout" name="formLayout">
<property name="fieldGrowthPolicy">
<enum>QFormLayout::ExpandingFieldsGrow</enum>
</property>
<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>
<widget class="QLabel" name="label">
<property name="text">
<string>Right click on any Tomahawk playlist to sync it to Spotify.</string>
</property>
<property name="margin">
<number>0</number>
</property>
</widget>
</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="QCheckBox" name="selectAllCheckbox">
<property name="text">
<string>Select All</string>
</property>
</widget>
</item>
<item>
<widget class="QListWidget" name="playlistList"/>
</item>
<item>
<widget class="QCheckBox" name="loveSync">
<property name="layoutDirection">
<enum>Qt::LeftToRight</enum>
</property>
<property name="text">
<string>Sync Starred tracks to Loved tracks</string>
</property>
<property name="checked">
<bool>false</bool>
</property>
</widget>
</item>
<item>
<widget class="QCheckBox" name="streamingCheckbox">
<property name="layoutDirection">
<enum>Qt::LeftToRight</enum>
</property>
<property name="text">
<string>High Quality Streams</string>
</property>
<property name="checked">
<bool>false</bool>
</property>
</widget>
</item>
<item>
<widget class="QCheckBox" name="deleteOnUnsync">
<property name="text">
<string>Delete Tomahawk playlist when removing synchronization</string>
</property>
</widget>
</item>
<item>
<widget class="QCheckBox" name="persitentPrivacy">
<property name="toolTip">
<string>Use this to force Spotify to never announce listening data to Social Networks</string>
</property>
<property name="text">
<string>Always run in Private Mode</string>
</property>
</widget>
</item>
</layout>
</widget>
<resources>
<include location="../../../../resources.qrc"/>
</resources>
<connections/>
</ui>

View File

@@ -1,296 +0,0 @@
/* === 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 "SpotifyInfoPlugin.h"
#include "SpotifyAccount.h"
#include "utils/Closure.h"
#include "utils/Json.h"
#include "utils/Logger.h"
#include "utils/NetworkAccessManager.h"
using namespace Tomahawk;
using namespace Tomahawk::InfoSystem;
SpotifyInfoPlugin::SpotifyInfoPlugin( Accounts::SpotifyAccount* account )
: InfoPlugin()
, m_account( QPointer< Accounts::SpotifyAccount >( account ) )
{
m_supportedGetTypes << InfoAlbumSongs;
if ( !m_account.isNull() )
{
m_supportedPushTypes << InfoLove << InfoUnLove;
}
}
SpotifyInfoPlugin::~SpotifyInfoPlugin()
{
}
void
SpotifyInfoPlugin::pushInfo( Tomahawk::InfoSystem::InfoPushData pushData )
{
if ( m_account.isNull() || !m_account.data()->loggedIn() )
return;
switch ( pushData.type )
{
case InfoLove:
case InfoUnLove:
sendLoveSong(pushData.type, pushData.infoPair.second);
break;
default:
return;
}
}
void
SpotifyInfoPlugin::sendLoveSong( const InfoType type, QVariant input )
{
if ( m_account.isNull() || !m_account.data()->loggedIn() )
return;
if( !m_account.data()->loveSync() )
return;
if ( !input.toMap().contains( "trackinfo" ) || !input.toMap()[ "trackinfo" ].canConvert< Tomahawk::InfoSystem::InfoStringHash >() )
{
tLog( LOGVERBOSE ) << "SpotifyInfoPlugin::sendLoveSong cannot convert input!";
return;
}
InfoStringHash hash = input.toMap()[ "trackinfo" ].value< Tomahawk::InfoSystem::InfoStringHash >();
if ( !hash.contains( "title" ) || !hash.contains( "artist" ) || !hash.contains( "album" ) )
return;
if ( type == Tomahawk::InfoSystem::InfoLove )
{
m_account.data()->starTrack( hash["artist"], hash["title"], true );
}
else if ( type == Tomahawk::InfoSystem::InfoUnLove )
{
m_account.data()->starTrack( hash["artist"], hash["title"], false );
}
}
void
SpotifyInfoPlugin::getInfo( InfoRequestData requestData )
{
switch ( requestData.type )
{
case InfoAlbumSongs:
{
if ( !requestData.input.canConvert< Tomahawk::InfoSystem::InfoStringHash >() )
{
dataError( requestData );
return;
}
InfoStringHash hash = requestData.input.value< Tomahawk::InfoSystem::InfoStringHash >();
if ( !hash.contains( "album" ) )
{
dataError( requestData );
return;
}
Tomahawk::InfoSystem::InfoStringHash criteria;
criteria[ "album" ] = hash[ "album" ];
if ( hash.contains( "artist" ) )
criteria["artist"] = hash["artist"];
emit getCachedInfo( criteria, Q_INT64_C(2419200000), requestData );
return;
}
default:
dataError( requestData );
}
}
void
SpotifyInfoPlugin::notInCacheSlot( InfoStringHash criteria, InfoRequestData requestData )
{
switch ( requestData.type )
{
case InfoAlbumSongs:
{
const QString album = criteria[ "album" ];
const QString artist = criteria[ "artist" ];
// Use always Spotify webservice, faster and more stable
QUrl lookupUrl( "http://ws.spotify.com/search/1/album.json" );
TomahawkUtils::urlAddQueryItem( lookupUrl, "q", QString( "%1 %2" ).arg( artist ).arg( album ) );
QNetworkReply * reply = Tomahawk::Utils::nam()->get( QNetworkRequest( lookupUrl ) );
NewClosure( reply, SIGNAL( finished() ), this, SLOT( albumIdLookupFinished( QNetworkReply*, Tomahawk::InfoSystem::InfoRequestData ) ), reply, requestData );
break;
}
default:
{
Q_ASSERT( false );
break;
}
}
}
void
SpotifyInfoPlugin::albumListingResult( const QString& msgType, const QVariantMap& msg, const QVariant& extraData )
{
Q_UNUSED( msgType );
Q_ASSERT( msg.contains( "qid" ) );
Q_ASSERT( extraData.canConvert< InfoRequestData >() );
const InfoRequestData requestData = extraData.value< InfoRequestData >();
QVariantList tracks = msg.value( "tracks" ).toList();
QStringList trackNameList;
foreach ( const QVariant track, tracks )
{
const QVariantMap trackData = track.toMap();
if ( trackData.contains( "track" ) && !trackData[ "track" ].toString().isEmpty() )
trackNameList << trackData[ "track" ].toString();
}
tLog( LOGVERBOSE ) << Q_FUNC_INFO << "Successfully got album listing from spotify resolver";
trackListResult( trackNameList, requestData );
}
void
SpotifyInfoPlugin::albumIdLookupFinished( QNetworkReply* reply, const InfoRequestData& requestData )
{
Q_ASSERT( reply );
reply->deleteLater();
if ( reply->error() == QNetworkReply::NoError )
{
const QVariantMap response = TomahawkUtils::parseJson( reply->readAll() ).toMap();
if ( !response.contains( "albums" ) )
{
dataError( requestData );
return;
}
const QVariantList albums = response.value( "albums" ).toList();
if ( albums.isEmpty() )
{
dataError( requestData );
return;
}
const QVariantMap album = albums.first().toMap();
const QString id = album.value( "href" ).toString();
if ( id.isEmpty() || !id.contains( "spotify:album" ) )
{
tLog( LOGVERBOSE ) << "Empty or malformed spotify album ID from json:" << id << response;
dataError( requestData );
return;
}
tLog( LOGVERBOSE ) << "Doing spotify album lookup via webservice with ID:" << id;
QUrl lookupUrl( QString( "http://spotikea.tomahawk-player.org/browse/%1" ).arg( id ) );
QNetworkReply * reply = Tomahawk::Utils::nam()->get( QNetworkRequest( lookupUrl ) );
NewClosure( reply, SIGNAL( finished() ), this, SLOT( albumContentsLookupFinished( QNetworkReply*, Tomahawk::InfoSystem::InfoRequestData ) ), reply, requestData );
}
else
{
tLog( LOGVERBOSE ) << "Network Error retrieving ID from spotify metadata service:" << reply->error() << reply->errorString() << reply->url();
}
}
void
SpotifyInfoPlugin::albumContentsLookupFinished( QNetworkReply* reply, const InfoRequestData& requestData )
{
Q_ASSERT( reply );
reply->deleteLater();
if ( reply->error() == QNetworkReply::NoError )
{
const QVariantMap response = TomahawkUtils::parseJson( reply->readAll() ).toMap();
if ( !response.contains( "album" ) )
{
dataError( requestData );
return;
}
const QVariantMap album = response.value( "album" ).toMap();
if ( !album.contains( "result" ) || album.value( "result" ).toList().isEmpty() )
{
dataError( requestData );
return;
}
const QVariantList albumTracks = album.value( "result" ).toList();
QStringList trackNameList;
foreach ( const QVariant& track, albumTracks )
{
const QVariantMap trackMap = track.toMap();
if ( trackMap.contains( "title" ) )
trackNameList << trackMap.value( "title" ).toString();
}
tLog( LOGVERBOSE ) << Q_FUNC_INFO << "Successfully got album listing from spotikea service!";
if ( trackNameList.isEmpty() )
dataError( requestData );
else
trackListResult( trackNameList, requestData );
}
else
{
tLog( LOGVERBOSE ) << "Network Error retrieving ID from spotify metadata service:" << reply->error() << reply->errorString() << reply->url();
}
}
void
SpotifyInfoPlugin::dataError( InfoRequestData requestData )
{
emit info( requestData, QVariant() );
}
void
SpotifyInfoPlugin::trackListResult( const QStringList& trackNameList, const InfoRequestData& requestData )
{
QVariantMap returnedData;
returnedData["tracks"] = trackNameList;
emit info( requestData, returnedData );
Tomahawk::InfoSystem::InfoStringHash criteria;
criteria["artist"] = requestData.input.value< InfoStringHash>()["artist"];
criteria["album"] = requestData.input.value< InfoStringHash>()["album"];
emit updateCache( criteria, Q_INT64_C(0), requestData.type, returnedData );
}

View File

@@ -1,75 +0,0 @@
/* === 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 SPOTIFYINFOPLUGIN_H
#define SPOTIFYINFOPLUGIN_H
#include "infosystem/InfoSystem.h"
#include "DllMacro.h"
#include <QPointer>
class QNetworkReply;
namespace Tomahawk
{
namespace Accounts
{
class SpotifyAccount;
}
namespace InfoSystem
{
class DLLEXPORT SpotifyInfoPlugin : public InfoPlugin
{
Q_OBJECT
public:
explicit SpotifyInfoPlugin( Accounts::SpotifyAccount* account );
virtual ~SpotifyInfoPlugin();
const QString friendlyName() const { return "Spotify"; };
public slots:
void albumListingResult( const QString& msgType, const QVariantMap& msg, const QVariant& extraData );
protected slots:
virtual void init() {}
virtual void getInfo( Tomahawk::InfoSystem::InfoRequestData requestData );
virtual void notInCacheSlot( Tomahawk::InfoSystem::InfoStringHash criteria, Tomahawk::InfoSystem::InfoRequestData requestData );
virtual void pushInfo( Tomahawk::InfoSystem::InfoPushData );
private slots:
void albumIdLookupFinished( QNetworkReply* reply, const Tomahawk::InfoSystem::InfoRequestData& requestData );
void albumContentsLookupFinished( QNetworkReply* reply, const Tomahawk::InfoSystem::InfoRequestData& requestData );
private:
void dataError( InfoRequestData );
void trackListResult( const QStringList& trackNameList, const Tomahawk::InfoSystem::InfoRequestData& requestData );
void sendLoveSong( const InfoType type, QVariant input );
QPointer< Tomahawk::Accounts::SpotifyAccount > m_account;
};
}
}
#endif // SPOTIFYINFOPLUGIN_H

View File

@@ -1,835 +0,0 @@
/* === This file is part of Tomahawk Player - <http://tomahawk-player.org> ===
*
* Copyright 2010-2012, Leo Franchi <lfranchi@kde.org>
* Copyright 2012, Hugo Lindström <hugolm84@gmail.com>
* Copyright 2013, Christian Muehlhaeuser <muesli@tomahawk-player.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 "utils/TomahawkUtils.h"
#include "utils/Logger.h"
#include "SpotifyAccount.h"
#include "Track.h"
#include <QApplication>
#include <QMessageBox>
using namespace Tomahawk;
using namespace Accounts;
QPixmap* SpotifyPlaylistUpdater::s_typePixmap = 0;
Tomahawk::PlaylistUpdaterInterface*
SpotifyUpdaterFactory::create( const Tomahawk::playlist_ptr& pl, const QVariantHash &settings )
{
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... ignoring for now!";
return 0;
}
// Register the updater with the account
const QString spotifyId = settings.value( "spotifyId" ).toString();
const QString latestRev = settings.value( "latestrev" ).toString();
const bool sync = settings.value( "sync" ).toBool();
const bool canSubscribe = settings.value( "canSubscribe" ).toBool();
const bool isSubscribed = settings.value( "subscribed" ).toBool();
const bool isOwner = settings.value( "isOwner" ).toBool();
const bool isCollaborative = settings.value( "collaborative" ).toBool();
const bool subscribers = settings.value( "subscribers" ).toInt();
Q_ASSERT( !spotifyId.isEmpty() );
SpotifyPlaylistUpdater* updater = new SpotifyPlaylistUpdater( m_account.data(), latestRev, spotifyId, pl );
updater->setSync( sync );
updater->setCanSubscribe( canSubscribe );
updater->setSubscribedStatus( isSubscribed );
updater->setOwner( isOwner );
updater->setCollaborative( isCollaborative );
updater->setSubscribers( subscribers );
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 )
, m_subscribed( false )
, m_canSubscribe( false )
, m_isOwner( false )
, m_collaborative( false )
, m_subscribers( 0 )
{
init();
}
void
SpotifyPlaylistUpdater::init()
{
connect( playlist().data(), SIGNAL( tracksInserted( QList<Tomahawk::plentry_ptr>, int ) ),
SLOT( tomahawkTracksInserted( QList<Tomahawk::plentry_ptr>, int ) ) );
connect( playlist().data(), SIGNAL( tracksRemoved( QList<Tomahawk::query_ptr> ) ),
SLOT( tomahawkTracksRemoved( QList<Tomahawk::query_ptr> ) ) );
connect( playlist().data(), SIGNAL( tracksMoved( QList<Tomahawk::plentry_ptr>, int ) ),
SLOT( tomahawkTracksMoved( QList<Tomahawk::plentry_ptr>, int ) ) );
connect( playlist().data(), SIGNAL( renamed( QString, QString ) ),
SLOT( tomahawkPlaylistRenamed( QString, QString ) ) );
connect( playlist().data(), SIGNAL( revisionLoaded( Tomahawk::PlaylistRevision ) ),
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
saveToSettings();
}
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::remove( bool askToDeletePlaylist )
{
if ( !askToDeletePlaylist )
m_sync = false;
PlaylistUpdaterInterface::remove();
}
void
SpotifyPlaylistUpdater::unsyncOrDelete( bool toDelete )
{
if ( QThread::currentThread() != QApplication::instance()->thread() )
QMetaObject::invokeMethod( const_cast<SpotifyPlaylistUpdater*>(this), "unsyncOrDelete", Qt::BlockingQueuedConnection, Q_ARG( bool, toDelete ) );
else
{
if ( m_subscribed )
{
m_spotify.data()->setSubscribedForPlaylist( playlist(), false );
}
else if ( m_sync && toDelete )
{
// 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()
{
QVariantHash s = settings();
s[ "latestrev" ] = m_latestRev;
s[ "sync" ] = m_sync;
s[ "canSubscribe" ] = m_canSubscribe;
s[ "subscribed" ] = m_subscribed;
s[ "spotifyId" ] = m_spotifyId;
s[ "isOwner" ] = m_isOwner;
s[ "collaborative" ] = m_collaborative;
saveSettings( s );
}
QString
SpotifyPlaylistUpdater::type() const
{
return "spotify";
}
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;
}
void
SpotifyPlaylistUpdater::setSync( bool sync )
{
if ( m_sync == sync )
return;
m_sync = sync;
saveToSettings();
emit changed();
}
bool
SpotifyPlaylistUpdater::sync() const
{
return m_sync;
}
void
SpotifyPlaylistUpdater::setOwner( bool owner )
{
if ( m_isOwner == owner )
return;
m_isOwner = owner;
saveToSettings();
emit changed();
}
bool
SpotifyPlaylistUpdater::owner() const
{
return m_isOwner;
}
void
SpotifyPlaylistUpdater::setCollaborative( bool collab )
{
if ( m_collaborative == collab )
return;
m_collaborative = collab;
saveToSettings();
emit changed();
}
bool
SpotifyPlaylistUpdater::collaborative() const
{
return m_collaborative;
}
void
SpotifyPlaylistUpdater::setSubscribedStatus( bool subscribed )
{
if ( m_subscribed == subscribed )
return;
m_subscribed = subscribed;
setSync( subscribed );
saveToSettings();
emit changed();
}
void
SpotifyPlaylistUpdater::setSubscribed( bool subscribed )
{
if ( !m_spotify.isNull() )
m_spotify.data()->setSubscribedForPlaylist( playlist(), subscribed );
// Spotify account will in turn call setSUbscribedStatus
}
bool
SpotifyPlaylistUpdater::subscribed() const
{
return m_subscribed;
}
void
SpotifyPlaylistUpdater::setCanSubscribe( bool canSubscribe )
{
if ( m_canSubscribe == canSubscribe )
return;
m_canSubscribe = canSubscribe;
saveToSettings();
emit changed();
}
bool
SpotifyPlaylistUpdater::canSubscribe() const
{
return m_canSubscribe;
}
void
SpotifyPlaylistUpdater::setSubscribers( int numSubscribers )
{
if ( m_subscribers == numSubscribers )
return;
m_subscribers = numSubscribers;
saveToSettings();
emit changed();
}
PlaylistDeleteQuestions
SpotifyPlaylistUpdater::deleteQuestions() const
{
// 1234 is our magic key
if ( m_sync && !m_subscribed )
return Tomahawk::PlaylistDeleteQuestions() << qMakePair<QString, int>( tr( "Delete associated Spotify playlist?" ), 1234 );
else
return Tomahawk::PlaylistDeleteQuestions();
}
void
SpotifyPlaylistUpdater::setQuestionResults( const QMap< int, bool > results )
{
const bool toDelete = results.value( 1234, false );
unsyncOrDelete( toDelete );
}
void
SpotifyPlaylistUpdater::spotifyTracksAdded( const QVariantList& tracks, const QString& startPosId, const QString& newRev, const QString& oldRev )
{
if ( !playlist()->loaded() )
{
playlist()->loadRevision();
}
if ( playlist()->busy() || !playlist()->loaded() )
{
// 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 );
}
void
SpotifyPlaylistUpdater::spotifyTracksRemoved( const QVariantList& trackIds, const QString& newRev, const QString& oldRev )
{
if ( !playlist()->loaded() )
{
playlist()->loadRevision();
}
if ( playlist()->busy() || !playlist()->loaded() )
{
// 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;
}
tDebug() << 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() )
{
tDebug() << 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();
tDebug() << "We were asked to delete:" << trackIds.size() << "tracks from the playlist, and we deleted:" << sizeDiff;
if ( trackIds.size() != ( playlist()->entries().size() - entries.size() ) )
tDebug() << Q_FUNC_INFO << "Failed to delete all the tracks we were asked for! Didn't find some indexes!";
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;
// TODO check return value
m_spotify.data()->sendMessage( msg, this, "onPlaylistRename" );
}
void
SpotifyPlaylistUpdater::spotifyTracksMoved( const QVariantList& tracks, const QString& newStartPos, const QString& newRev, const QString& oldRev )
{
if ( !playlist()->loaded() )
{
playlist()->loadRevision();
}
if ( playlist()->busy() || !playlist()->loaded() )
{
// 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() )
{
++iter;
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()->queryTrack()->toString();
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 QVariant& )
{
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()->queryTrack()->toString() << 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 );
// Update with latest rev when/if we use it
// saveToSettings();
}
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 QVariant& )
{
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() << Q_FUNC_INFO << ple->query()->queryTrack()->toString();
}
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 QVariant& )
{
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->queryTrack()->track();
m[ "artist" ] = query->queryTrack()->artist();
m[ "album" ] = query->queryTrack()->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 ( q.isNull() )
continue;
if ( trackMap.contains( "id" ) )
{
q->setResultHint( trackMap.value( "id" ).toString() );
q->setProperty( "annotation", trackMap.value( "id" ) );
}
queries << q;
}
return queries;
}

View File

@@ -1,144 +0,0 @@
/* === This file is part of Tomahawk Player - <http://tomahawk-player.org> ===
*
* Copyright 2010-2012, Leo Franchi <lfranchi@kde.org>
* Copyright 2012, Hugo Lindström <hugolm84@gmail.com>
* Copyright 2013, Christian Muehlhaeuser <muesli@tomahawk-player.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 "DllMacro.h"
#include "Typedefs.h"
#include <QQueue>
#include <QVariant>
namespace Tomahawk {
namespace Accounts {
class SpotifyAccount;
}
}
class DLLEXPORT 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() {}
virtual QWidget* configurationWidget() const { return 0; }
virtual QPixmap typeIcon() const;
bool sync() const;
void setSync( bool sync );
bool subscribed() const;
// actually change the subscribed value in spotify
void setSubscribed( bool subscribed );
// Just set the subscribed flag
void setSubscribedStatus( bool subscribed );
bool canSubscribe() const;
void setCanSubscribe( bool canSub );
void setSubscribers( int numSubscribers );
int subscribers() const { return m_subscribers; }
// Collaborative actions
void setOwner( bool owner );
bool owner() const;
bool collaborative() const;
void setCollaborative( bool collaborative );
QString spotifyId() const { return m_spotifyId; }
virtual bool hasCustomDeleter() const { return true; }
virtual Tomahawk::PlaylistDeleteQuestions deleteQuestions() const;
virtual void setQuestionResults( const QMap< int, bool > results );
void remove( bool askToDeletePlaylist = true );
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& );
private slots:
// SpotifyResolver message handlers, all take msgtype, msg as argument
void onTracksInsertedReturn( const QString& msgType, const QVariantMap& msg, const QVariant& extraData );
void onTracksRemovedReturn( const QString& msgType, const QVariantMap& msg, const QVariant& extraData );
void onTracksMovedReturn( const QString& msgType, const QVariantMap& msg, const QVariant& extraData );
void unsyncOrDelete( bool toDelete );
void playlistRevisionLoaded();
private:
void init();
void saveToSettings();
/// 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 );
QPointer<Tomahawk::Accounts::SpotifyAccount> m_spotify;
QString m_latestRev, m_spotifyId;
QList< Tomahawk::plentry_ptr > m_waitingForIds;
bool m_blockUpdatesForNextRevision;
bool m_sync;
bool m_subscribed;
bool m_canSubscribe;
bool m_isOwner;
bool m_collaborative;
int m_subscribers;
QQueue<_detail::Closure*> m_queuedOps;
static QPixmap* s_typePixmap;
};
class DLLEXPORT SpotifyUpdaterFactory : public Tomahawk::PlaylistUpdaterFactory
{
public:
SpotifyUpdaterFactory() {}
virtual Tomahawk::PlaylistUpdaterInterface* create( const Tomahawk::playlist_ptr& pl, const QVariantHash& settings );
virtual QString type() const { return "spotify"; }
private:
QPointer<Tomahawk::Accounts::SpotifyAccount> m_account;
};
#endif // SPOTIFYPLAYLISTUPDATER_H

View File

@@ -18,11 +18,120 @@
#include "ExternalResolver.h"
#include "PlaylistEntry.h"
#include "accounts/AccountConfigWidget.h"
#include "utils/Logger.h"
#include "PlaylistEntry.h"
#include <QBoxLayout>
#include <QBuffer>
#include <QDir>
#include <QMetaProperty>
#include <QUiLoader>
Tomahawk::ExternalResolver::ErrorState
Tomahawk::ExternalResolver::error() const
{
return NoError;
}
QVariant
Tomahawk::ExternalResolver::configMsgFromWidget( QWidget* w )
{
if( !w )
return QVariant();
// generate a qvariantmap of all the widgets in the hierarchy, and for each one include the list of properties and values
QVariantMap widgetMap;
addChildProperties( w, widgetMap );
// qDebug() << "Generated widget variant:" << widgetMap;
return widgetMap;
}
void
Tomahawk::ExternalResolver::addChildProperties( QObject* widget, QVariantMap& m )
{
// recursively add all properties of this widget to the map, then repeat on all children.
// bare QWidgets are boring---so skip them! They have no input that the user can set.
if( !widget || !widget->isWidgetType() )
return;
if( qstrcmp( widget->metaObject()->className(), "QWidget" ) != 0 )
{
// qDebug() << "Adding properties for this:" << widget->metaObject()->className();
// add this widget's properties
QVariantMap props;
for( int i = 0; i < widget->metaObject()->propertyCount(); i++ )
{
QString prop = widget->metaObject()->property( i ).name();
QVariant val = widget->property( prop.toLatin1() );
// clean up for QJson....
if( val.canConvert< QPixmap >() || val.canConvert< QImage >() || val.canConvert< QIcon >() )
continue;
props[ prop ] = val.toString();
// qDebug() << QString( "%1: %2" ).arg( prop ).arg( props[ prop ].toString() );
}
m[ widget->objectName() ] = props;
}
// and recurse
foreach( QObject* child, widget->children() )
addChildProperties( child, m );
}
AccountConfigWidget*
Tomahawk::ExternalResolver::widgetFromData( QByteArray& data, QWidget* parent )
{
if( data.isEmpty() )
return 0;
AccountConfigWidget* configWidget = new AccountConfigWidget( parent );
QUiLoader l;
QBuffer b( &data );
QWidget* w = l.load( &b, configWidget );
// HACK: proper way would be to create a designer plugin for this widget type
configWidget->setLayout( new QBoxLayout( QBoxLayout::TopToBottom ) );
configWidget->layout()->addWidget( w );
#ifdef Q_OS_MAC
w->setContentsMargins( 12, 12, 12, 12 );
#else
w->setContentsMargins( 6, 6, 6, 6 );
#endif
return configWidget;
}
QByteArray
Tomahawk::ExternalResolver::fixDataImagePaths( const QByteArray& data, bool compressed, const QVariantMap& images )
{
// with a list of images and image data, write each to a temp file, replace the path in the .ui file with the temp file path
QString uiFile = QString::fromUtf8( data );
foreach( const QString& filename, images.keys() )
{
if( !uiFile.contains( filename ) ) // make sure the image is used
continue;
QString fullPath = QDir::tempPath() + "/" + filename;
QFile imgF( fullPath );
if( !imgF.open( QIODevice::WriteOnly ) )
{
qWarning() << "Failed to write to temporary image in UI file:" << filename << fullPath;
continue;
}
QByteArray data = images[ filename ].toByteArray();
// qDebug() << "expanding data:" << data << compressed;
data = compressed ? qUncompress( QByteArray::fromBase64( data ) ) : QByteArray::fromBase64( data );
imgF.write( data );
imgF.close();
// replace the path to the image with the real path
uiFile.replace( filename, fullPath );
}
return uiFile.toUtf8();
}

View File

@@ -105,6 +105,8 @@ public:
virtual void enqueue( const QSharedPointer< ScriptCommand >& req )
{ m_commandQueue->enqueue( req ); }
virtual AccountConfigWidget* configUI() const = 0;
public slots:
virtual void start() = 0;
virtual void stop() = 0;
@@ -132,8 +134,15 @@ protected:
// UrlLookup
virtual void lookupUrl( const QString& url ) = 0;
AccountConfigWidget* widgetFromData( QByteArray& data, QWidget* parent = 0 );
QVariant configMsgFromWidget( QWidget* w );
QByteArray fixDataImagePaths( const QByteArray& data, bool compressed, const QVariantMap& images );
private:
QString m_filePath;
void addChildProperties( QObject* parent, QVariantMap& m );
};
Q_DECLARE_OPERATORS_FOR_FLAGS( ExternalResolver::Capabilities )

View File

@@ -1,139 +0,0 @@
/* === This file is part of Tomahawk Player - <http://tomahawk-player.org> ===
*
* Copyright 2010-2011, Christian Muehlhaeuser <muesli@tomahawk-player.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 "ExternalResolverGui.h"
#include "Source.h"
#include "utils/Logger.h"
#include "accounts/AccountConfigWidget.h"
#include <QMetaProperty>
#include <QBuffer>
#include <QDir>
#include <QIcon>
#include <QWidget>
#include <QUiLoader>
#include <QBoxLayout>
Tomahawk::ExternalResolverGui::ExternalResolverGui(const QString& filePath)
: Tomahawk::ExternalResolver(filePath)
{
}
QVariant
Tomahawk::ExternalResolverGui::configMsgFromWidget( QWidget* w )
{
if( !w )
return QVariant();
// generate a qvariantmap of all the widgets in the hierarchy, and for each one include the list of properties and values
QVariantMap widgetMap;
addChildProperties( w, widgetMap );
// qDebug() << "Generated widget variant:" << widgetMap;
return widgetMap;
}
void
Tomahawk::ExternalResolverGui::addChildProperties( QObject* widget, QVariantMap& m )
{
// recursively add all properties of this widget to the map, then repeat on all children.
// bare QWidgets are boring---so skip them! They have no input that the user can set.
if( !widget || !widget->isWidgetType() )
return;
if( qstrcmp( widget->metaObject()->className(), "QWidget" ) != 0 )
{
// qDebug() << "Adding properties for this:" << widget->metaObject()->className();
// add this widget's properties
QVariantMap props;
for( int i = 0; i < widget->metaObject()->propertyCount(); i++ )
{
QString prop = widget->metaObject()->property( i ).name();
QVariant val = widget->property( prop.toLatin1() );
// clean up for QJson....
if( val.canConvert< QPixmap >() || val.canConvert< QImage >() || val.canConvert< QIcon >() )
continue;
props[ prop ] = val.toString();
// qDebug() << QString( "%1: %2" ).arg( prop ).arg( props[ prop ].toString() );
}
m[ widget->objectName() ] = props;
}
// and recurse
foreach( QObject* child, widget->children() )
addChildProperties( child, m );
}
AccountConfigWidget*
Tomahawk::ExternalResolverGui::widgetFromData( QByteArray& data, QWidget* parent )
{
if( data.isEmpty() )
return 0;
AccountConfigWidget* configWidget = new AccountConfigWidget( parent );
QUiLoader l;
QBuffer b( &data );
QWidget* w = l.load( &b, configWidget );
// HACK: proper way would be to create a designer plugin for this widget type
configWidget->setLayout( new QBoxLayout( QBoxLayout::TopToBottom ) );
configWidget->layout()->addWidget( w );
#ifdef Q_OS_MAC
w->setContentsMargins( 12, 12, 12, 12 );
#else
w->setContentsMargins( 6, 6, 6, 6 );
#endif
return configWidget;
}
QByteArray
Tomahawk::ExternalResolverGui::fixDataImagePaths( const QByteArray& data, bool compressed, const QVariantMap& images )
{
// with a list of images and image data, write each to a temp file, replace the path in the .ui file with the temp file path
QString uiFile = QString::fromUtf8( data );
foreach( const QString& filename, images.keys() )
{
if( !uiFile.contains( filename ) ) // make sure the image is used
continue;
QString fullPath = QDir::tempPath() + "/" + filename;
QFile imgF( fullPath );
if( !imgF.open( QIODevice::WriteOnly ) )
{
qWarning() << "Failed to write to temporary image in UI file:" << filename << fullPath;
continue;
}
QByteArray data = images[ filename ].toByteArray();
// qDebug() << "expanding data:" << data << compressed;
data = compressed ? qUncompress( QByteArray::fromBase64( data ) ) : QByteArray::fromBase64( data );
imgF.write( data );
imgF.close();
// replace the path to the image with the real path
uiFile.replace( filename, fullPath );
}
return uiFile.toUtf8();
}

View File

@@ -1,58 +0,0 @@
/* === This file is part of Tomahawk Player - <http://tomahawk-player.org> ===
*
* Copyright 2010-2011, Christian Muehlhaeuser <muesli@tomahawk-player.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 EXTERNALRESOLVERGUI_H
#define EXTERNALRESOLVERGUI_H
#include "ExternalResolver.h"
#include "DllMacro.h"
#include <QPixmap>
class QWidget;
class AccountConfigWidget;
namespace Tomahawk
{
/**
* Generic resolver object, used to manage a resolver that Tomahawk knows about
*
* You *must* start() a resolver after creating an ExternalResolver in order to use it,
* otherwise it will not do anything.
*/
class DLLEXPORT ExternalResolverGui : public ExternalResolver
{
Q_OBJECT
public:
ExternalResolverGui( const QString& filePath );
virtual AccountConfigWidget* configUI() const = 0;
protected:
AccountConfigWidget* widgetFromData( QByteArray& data, QWidget* parent = 0 );
QVariant configMsgFromWidget( QWidget* w );
QByteArray fixDataImagePaths( const QByteArray& data, bool compressed, const QVariantMap& images );
private:
void addChildProperties( QObject* parent, QVariantMap& m );
};
}; //ns
#endif // RESOLVER_H

View File

@@ -57,7 +57,7 @@
using namespace Tomahawk;
JSResolver::JSResolver( const QString& accountId, const QString& scriptPath, const QStringList& additionalScriptPaths )
: Tomahawk::ExternalResolverGui( scriptPath )
: Tomahawk::ExternalResolver( scriptPath )
, d_ptr( new JSResolverPrivate( this, accountId, scriptPath, additionalScriptPaths ) )
{
Q_D( JSResolver );

View File

@@ -24,7 +24,7 @@
#include "config.h"
#include "DllMacro.h"
#include "ExternalResolverGui.h"
#include "ExternalResolver.h"
#include "Typedefs.h"
namespace Tomahawk
@@ -35,7 +35,7 @@ class JSResolverHelper;
class JSResolverPrivate;
class ScriptEngine;
class DLLEXPORT JSResolver : public Tomahawk::ExternalResolverGui
class DLLEXPORT JSResolver : public Tomahawk::ExternalResolver
{
Q_OBJECT

View File

@@ -37,7 +37,10 @@ class JSResolverPrivate
public:
JSResolverPrivate( JSResolver* q, const QString& pAccountId, const QString& scriptPath, const QStringList& additionalScriptPaths )
: q_ptr ( q )
, engine( nullptr )
, accountId( pAccountId )
, weight( 0 )
, timeout( 0 )
, ready( false )
, stopped( true )
, error( Tomahawk::ExternalResolver::NoError )
@@ -57,7 +60,7 @@ private:
QString name;
QPixmap icon;
unsigned int weight, timeout;
Tomahawk::ExternalResolverGui::Capabilities capabilities;
Tomahawk::ExternalResolver::Capabilities capabilities;
bool ready;
bool stopped;

View File

@@ -20,7 +20,7 @@
#include "ScriptCollection.h"
#include "Source.h"
#include "ExternalResolverGui.h"
#include "ExternalResolver.h"
#include "utils/TomahawkUtilsGui.h"
#include "utils/Logger.h"
#include "resolvers/ScriptCommand_AllArtists.h"

View File

@@ -0,0 +1,30 @@
/* === This file is part of Tomahawk Player - <http://tomahawk-player.org> ===
*
* Copyright 2013, Teo Mrnjavac <teo@kde.org>
* Copyright 2014, Uwe L. Korn <uwelk@xhochy.com>
*
* 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 "ScriptCommand.h"
namespace Tomahawk
{
ScriptCommand::ScriptCommand( QObject* parent )
: QObject( parent )
{
}
}

View File

@@ -1,6 +1,7 @@
/* === This file is part of Tomahawk Player - <http://tomahawk-player.org> ===
*
* Copyright 2013, Teo Mrnjavac <teo@kde.org>
* Copyright 2014, Uwe L. Korn <uwelk@xhochy.com>
*
* Tomahawk is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
@@ -26,12 +27,13 @@ namespace Tomahawk
class ScriptCommand : public QObject
{
Q_OBJECT
public:
explicit ScriptCommand( QObject* parent = 0 ) : QObject( parent ) {}
explicit ScriptCommand( QObject* parent = nullptr );
virtual ~ScriptCommand() {}
signals:
virtual void done() = 0;
void done();
protected:
friend class ScriptCommandQueue;

View File

@@ -42,7 +42,6 @@ public:
signals:
void albums( const QList< Tomahawk::album_ptr >& );
void done();
protected:
virtual void exec();

View File

@@ -41,7 +41,6 @@ public:
signals:
void artists( const QList< Tomahawk::artist_ptr >& );
void done();
protected:
void exec() override;

View File

@@ -40,7 +40,6 @@ public:
signals:
void tracks( const QList< Tomahawk::query_ptr >& );
void done();
protected:
void exec() override;

View File

@@ -45,7 +45,6 @@ public:
signals:
void information( const QString& url, const QSharedPointer<QObject>& variant );
void done();
protected:
void exec() override;

View File

@@ -1,573 +0,0 @@
/* === 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 2013, Teo Mrnjavac <teo@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 "ScriptResolver.h"
#include "accounts/AccountConfigWidget.h"
#include "utils/TomahawkUtilsGui.h"
#include "utils/Json.h"
#include "utils/Logger.h"
#include "utils/NetworkAccessManager.h"
#include "utils/NetworkProxyFactory.h"
#include "Artist.h"
#include "Album.h"
#include "Pipeline.h"
#include "Result.h"
#include "ScriptCollection.h"
#include "SourceList.h"
#include "Track.h"
#include <QtEndian>
#include <QFileInfo>
#include <QNetworkAccessManager>
#include <QNetworkProxy>
#ifdef Q_OS_WIN
#include <shlwapi.h>
#endif
using namespace Tomahawk;
ScriptResolver::ScriptResolver( const QString& exe )
: Tomahawk::ExternalResolverGui( exe )
, m_num_restarts( 0 )
, m_msgsize( 0 )
, m_ready( false )
, m_stopped( true )
, m_configSent( false )
, m_deleting( false )
, m_error( Tomahawk::ExternalResolver::NoError )
{
tLog() << Q_FUNC_INFO << "Created script resolver:" << exe;
connect( &m_proc, SIGNAL( readyReadStandardError() ), SLOT( readStderr() ) );
connect( &m_proc, SIGNAL( readyReadStandardOutput() ), SLOT( readStdout() ) );
connect( &m_proc, SIGNAL( finished( int, QProcess::ExitStatus ) ), SLOT( cmdExited( int, QProcess::ExitStatus ) ) );
startProcess();
if ( !Tomahawk::Utils::nam() )
return;
// set the name to the binary, if we launch properly we'll get the name the resolver reports
m_name = QFileInfo( filePath() ).baseName();
// set the icon, if we launch properly we'll get the icon the resolver reports
m_icon = TomahawkUtils::defaultPixmap( TomahawkUtils::DefaultResolver, TomahawkUtils::Original, QSize( 128, 128 ) );
}
ScriptResolver::~ScriptResolver()
{
disconnect( &m_proc, SIGNAL( finished( int, QProcess::ExitStatus ) ), this, SLOT( cmdExited( int, QProcess::ExitStatus ) ) );
m_deleting = true;
QVariantMap msg;
msg[ "_msgtype" ] = "quit";
sendMessage( msg );
bool finished = m_proc.state() != QProcess::Running || m_proc.waitForFinished( 2500 ); // might call handleMsg
Tomahawk::Pipeline::instance()->removeResolver( this );
if ( !finished || m_proc.state() == QProcess::Running )
{
qDebug() << "External resolver didn't exit after waiting 2s for it to die, killing forcefully";
#ifdef Q_OS_WIN
m_proc.kill();
#else
m_proc.terminate();
#endif
}
if ( !m_configWidget.isNull() )
delete m_configWidget.data();
}
Tomahawk::ExternalResolver*
ScriptResolver::factory( const QString& accountId, const QString& exe, const QStringList& unused )
{
Q_UNUSED( accountId )
Q_UNUSED( unused )
ExternalResolver* res = 0;
const QFileInfo fi( exe );
if ( fi.suffix() != "js" && fi.suffix() != "script" )
{
res = new ScriptResolver( exe );
tLog() << Q_FUNC_INFO << exe << "Loaded.";
}
return res;
}
void
ScriptResolver::start()
{
m_stopped = false;
if ( m_ready )
Tomahawk::Pipeline::instance()->addResolver( this );
else if ( !m_configSent )
sendConfig();
// else, we've sent our config msg so are waiting for the resolver to react
}
void
ScriptResolver::sendConfig()
{
// Send a configutaion message with any information the resolver might need
// For now, only the proxy information is sent
QVariantMap m;
m.insert( "_msgtype", "config" );
m_configSent = true;
tDebug() << "Nam is:" << Tomahawk::Utils::nam();
tDebug() << "Nam proxy is:" << Tomahawk::Utils::nam()->proxyFactory();
Tomahawk::Utils::nam()->proxyFactory()->queryProxy();
Tomahawk::Utils::NetworkProxyFactory* factory = dynamic_cast<Tomahawk::Utils::NetworkProxyFactory*>( Tomahawk::Utils::nam()->proxyFactory() );
QNetworkProxy proxy = factory->proxy();
QString proxyType = ( proxy.type() == QNetworkProxy::Socks5Proxy ? "socks5" : "none" );
m.insert( "proxytype", proxyType );
m.insert( "proxyhost", proxy.hostName() );
m.insert( "proxyport", proxy.port() );
m.insert( "proxyuser", proxy.user() );
m.insert( "proxypass", proxy.password() );
// QJson sucks
QVariantList hosts;
foreach ( const QString& host, factory->noProxyHosts() )
hosts << host;
m.insert( "noproxyhosts", hosts );
bool ok;
QByteArray data = TomahawkUtils::toJson( m, &ok );
Q_ASSERT( ok );
sendMsg( data );
}
void
ScriptResolver::reload()
{
startProcess();
}
bool
ScriptResolver::running() const
{
return !m_stopped;
}
void
ScriptResolver::sendMessage( const QVariantMap& map )
{
bool ok;
QByteArray data = TomahawkUtils::toJson( map, &ok );
Q_ASSERT( ok );
sendMsg( data );
}
void
ScriptResolver::readStderr()
{
tLog() << "SCRIPT_STDERR" << filePath() << m_proc.readAllStandardError();
}
ScriptResolver::ErrorState
ScriptResolver::error() const
{
return m_error;
}
void
ScriptResolver::readStdout()
{
if ( m_msgsize == 0 )
{
if ( m_proc.bytesAvailable() < 4 )
return;
quint32 len_nbo;
m_proc.read( (char*) &len_nbo, 4 );
m_msgsize = qFromBigEndian( len_nbo );
}
if ( m_msgsize > 0 )
{
m_msg.append( m_proc.read( m_msgsize - m_msg.length() ) );
}
if ( m_msgsize == (quint32) m_msg.length() )
{
handleMsg( m_msg );
m_msgsize = 0;
m_msg.clear();
if ( m_proc.bytesAvailable() )
QTimer::singleShot( 0, this, SLOT( readStdout() ) );
}
}
void
ScriptResolver::sendMsg( const QByteArray& msg )
{
// qDebug() << Q_FUNC_INFO << m_ready << msg << msg.length();
if ( !m_proc.isOpen() )
return;
quint32 len;
qToBigEndian( msg.length(), (uchar*) &len );
m_proc.write( (const char*) &len, 4 );
m_proc.write( msg );
}
void
ScriptResolver::handleMsg( const QByteArray& msg )
{
// qDebug() << Q_FUNC_INFO << msg.size() << QString::fromAscii( msg );
// Might be called from waitForFinished() in ~ScriptResolver, no database in that case, abort.
if ( m_deleting )
return;
bool ok;
QVariant v = TomahawkUtils::parseJson( msg, &ok );
if ( !ok || v.type() != QVariant::Map )
{
Q_ASSERT( false );
return;
}
QVariantMap m = v.toMap();
QString msgtype = m.value( "_msgtype" ).toString();
if ( msgtype == "settings" )
{
doSetup( m );
return;
}
else if ( msgtype == "confwidget" )
{
setupConfWidget( m );
return;
}
else if ( msgtype == "results" )
{
const QString qid = m.value( "qid" ).toString();
QList< Tomahawk::result_ptr > results;
const QVariantList reslist = m.value( "results" ).toList();
foreach( const QVariant& rv, reslist )
{
QVariantMap m = rv.toMap();
tDebug( LOGVERBOSE ) << "Found result:" << m;
Tomahawk::track_ptr track = Tomahawk::Track::get( m.value( "artist" ).toString(), m.value( "track" ).toString(), m.value( "album" ).toString(), m.value( "duration" ).toUInt(), QString(), m.value( "albumpos" ).toUInt(), m.value( "discnumber" ).toUInt() );
if ( !track )
continue;
Tomahawk::result_ptr rp = Tomahawk::Result::get( m.value( "url" ).toString(), track );
if ( !rp )
continue;
rp->setBitrate( m.value( "bitrate" ).toUInt() );
rp->setSize( m.value( "size" ).toUInt() );
rp->setRID( uuid() );
rp->setFriendlySource( m_name );
rp->setPurchaseUrl( m.value( "purchaseUrl" ).toString() );
rp->setLinkUrl( m.value( "linkUrl" ).toString() );
//FIXME
if ( m.contains( "year" ) )
{
QVariantMap attr;
attr[ "releaseyear" ] = m.value( "year" );
// rp->track()->setAttributes( attr );
}
rp->setMimetype( m.value( "mimetype" ).toString() );
if ( rp->mimetype().isEmpty() )
{
rp->setMimetype( TomahawkUtils::extensionToMimetype( m.value( "extension" ).toString() ) );
Q_ASSERT( !rp->mimetype().isEmpty() );
}
rp->setResolvedBy( this );
results << rp;
}
Tomahawk::Pipeline::instance()->reportResults( qid, results );
}
else
{
// Unknown message, give up for custom implementations
emit customMessage( msgtype, m );
}
}
void
ScriptResolver::cmdExited( int code, QProcess::ExitStatus status )
{
m_ready = false;
tLog() << Q_FUNC_INFO << "SCRIPT EXITED, code" << code << "status" << status << filePath();
Tomahawk::Pipeline::instance()->removeResolver( this );
m_error = ExternalResolver::FailedToLoad;
emit changed();
if ( m_stopped )
{
tLog() << "*** Script resolver stopped ";
emit terminated();
return;
}
if ( m_num_restarts < 10 )
{
m_num_restarts++;
tLog() << "*** Restart num" << m_num_restarts;
startProcess();
sendConfig();
}
else
{
tLog() << "*** Reached max restarts, not restarting.";
}
}
void
ScriptResolver::resolve( const Tomahawk::query_ptr& query )
{
QVariantMap m;
m.insert( "_msgtype", "rq" );
if ( query->isFullTextQuery() )
{
m.insert( "fulltext", query->fullTextQuery() );
m.insert( "track", query->fullTextQuery() );
m.insert( "qid", query->id() );
}
else
{
m.insert( "artist", query->queryTrack()->artist() );
m.insert( "track", query->queryTrack()->track() );
m.insert( "qid", query->id() );
if ( !query->resultHint().isEmpty() )
m.insert( "resultHint", query->resultHint() );
}
const QByteArray msg = TomahawkUtils::toJson( QVariant( m ) );
sendMsg( msg );
}
void
ScriptResolver::doSetup( const QVariantMap& m )
{
// qDebug() << Q_FUNC_INFO << m;
m_name = m.value( "name" ).toString();
m_weight = m.value( "weight", 0 ).toUInt();
m_timeout = m.value( "timeout", 5 ).toUInt() * 1000;
bool compressed = m.value( "compressed", "false" ).toString() == "true";
bool ok;
int intCap = m.value( "capabilities" ).toInt( &ok );
if ( !ok )
m_capabilities = NullCapability;
else
m_capabilities = static_cast< Capabilities >( intCap );
QByteArray icoData = m.value( "icon" ).toByteArray();
if( compressed )
icoData = qUncompress( QByteArray::fromBase64( icoData ) );
else
icoData = QByteArray::fromBase64( icoData );
QPixmap ico;
ico.loadFromData( icoData );
bool success = false;
if ( !ico.isNull() )
{
m_icon = ico.scaled( m_icon.size(), Qt::IgnoreAspectRatio );
success = true;
}
// see if the resolver sent an icon path to not break the old (unofficial) api.
// TODO: remove this and publish a definitive api
if ( !success )
{
QString iconPath = QFileInfo( filePath() ).path() + "/" + m.value( "icon" ).toString();
success = m_icon.load( iconPath );
}
qDebug() << "SCRIPT" << filePath() << "READY," << "name" << m_name << "weight" << m_weight << "timeout" << m_timeout << "icon received" << success;
m_ready = true;
m_configSent = false;
m_num_restarts = 0;
if ( !m_stopped )
Tomahawk::Pipeline::instance()->addResolver( this );
emit changed();
}
void
ScriptResolver::setupConfWidget( const QVariantMap& m )
{
bool compressed = m.value( "compressed", "false" ).toString() == "true";
qDebug() << "Resolver has a preferences widget! compressed?" << compressed;
QByteArray uiData = m[ "widget" ].toByteArray();
if( compressed )
uiData = qUncompress( QByteArray::fromBase64( uiData ) );
else
uiData = QByteArray::fromBase64( uiData );
if ( m.contains( "images" ) )
uiData = fixDataImagePaths( uiData, compressed, m[ "images" ].toMap() );
m_configWidget = QPointer< AccountConfigWidget >( widgetFromData( uiData, 0 ) );
emit changed();
}
void
ScriptResolver::startProcess()
{
if ( !QFile::exists( filePath() ) )
m_error = Tomahawk::ExternalResolver::FileNotFound;
else
{
m_error = Tomahawk::ExternalResolver::NoError;
}
const QFileInfo fi( filePath() );
QString interpreter;
// have to enclose in quotes if path contains spaces...
const QString runPath = QString( "\"%1\"" ).arg( filePath() );
QFile file( filePath() );
file.setPermissions( file.permissions() | QFile::ExeOwner | QFile::ExeGroup | QFile::ExeOther );
#ifdef Q_OS_WIN
if ( fi.suffix().toLower() != "exe" )
{
DWORD dwSize = MAX_PATH;
wchar_t path[MAX_PATH] = { 0 };
wchar_t *ext = (wchar_t *) ("." + fi.suffix()).utf16();
HRESULT hr = AssocQueryStringW(
(ASSOCF) 0,
ASSOCSTR_EXECUTABLE,
ext,
L"open",
path,
&dwSize
);
if ( ! FAILED( hr ) )
{
interpreter = QString( "\"%1\"" ).arg(QString::fromUtf16((const ushort *) path));
}
}
#endif // Q_OS_WIN
if ( interpreter.isEmpty() )
{
#ifndef Q_OS_WIN
const QFileInfo info( filePath() );
m_proc.setWorkingDirectory( info.absolutePath() );
tLog() << "Setting working dir:" << info.absolutePath();
#endif
m_proc.start( runPath );
}
else
m_proc.start( interpreter, QStringList() << filePath() );
sendConfig();
}
void
ScriptResolver::saveConfig()
{
Q_ASSERT( !m_configWidget.isNull() );
QVariantMap m;
m.insert( "_msgtype", "setpref" );
QVariant widgets = configMsgFromWidget( m_configWidget.data() );
m.insert( "widgets", widgets );
bool ok;
QByteArray data = TomahawkUtils::toJson( m, &ok );
Q_ASSERT( ok );
sendMsg( data );
}
void
ScriptResolver::setIcon( const QPixmap& icon )
{
m_icon = icon;
}
AccountConfigWidget*
ScriptResolver::configUI() const
{
if ( m_configWidget.isNull() )
return 0;
else
return m_configWidget.data();
}
void
ScriptResolver::stop()
{
m_stopped = true;
foreach ( const Tomahawk::collection_ptr& collection, m_collections )
{
emit collectionRemoved( collection );
}
m_collections.clear();
Tomahawk::Pipeline::instance()->removeResolver( this );
}

View File

@@ -1,115 +0,0 @@
/* === 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 2013, Teo Mrnjavac <teo@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 SCRIPTRESOLVER_H
#define SCRIPTRESOLVER_H
#include "Query.h"
#include "Artist.h"
#include "Album.h"
#include "collection/Collection.h"
#include "ExternalResolverGui.h"
#include "DllMacro.h"
#include <QProcess>
class QWidget;
namespace Tomahawk
{
class DLLEXPORT ScriptResolver : public Tomahawk::ExternalResolverGui
{
Q_OBJECT
public:
explicit ScriptResolver( const QString& exe );
virtual ~ScriptResolver();
static ExternalResolver* factory( const QString& accountId, const QString& exe, const QStringList& );
QString name() const Q_DECL_OVERRIDE { return m_name; }
QPixmap icon() const Q_DECL_OVERRIDE { return m_icon; }
unsigned int weight() const Q_DECL_OVERRIDE { return m_weight; }
virtual unsigned int preference() const { return m_preference; }
unsigned int timeout() const Q_DECL_OVERRIDE { return m_timeout; }
Capabilities capabilities() const Q_DECL_OVERRIDE { return m_capabilities; }
void setIcon( const QPixmap& icon ) Q_DECL_OVERRIDE;
AccountConfigWidget* configUI() const Q_DECL_OVERRIDE;
void saveConfig() Q_DECL_OVERRIDE;
ExternalResolver::ErrorState error() const Q_DECL_OVERRIDE;
void reload() Q_DECL_OVERRIDE;
bool running() const Q_DECL_OVERRIDE;
void sendMessage( const QVariantMap& map );
bool canParseUrl( const QString&, UrlType ) Q_DECL_OVERRIDE { return false; }
signals:
void terminated();
void customMessage( const QString& msgType, const QVariantMap& msg );
public slots:
void stop() Q_DECL_OVERRIDE;
void resolve( const Tomahawk::query_ptr& query ) Q_DECL_OVERRIDE;
void start() Q_DECL_OVERRIDE;
// TODO: implement. Or not. Not really an issue while Spotify doesn't do browsable personal cloud storage.
void artists( const Tomahawk::collection_ptr& ) Q_DECL_OVERRIDE {}
void albums( const Tomahawk::collection_ptr&, const Tomahawk::artist_ptr& ) Q_DECL_OVERRIDE {}
void tracks( const Tomahawk::collection_ptr&, const Tomahawk::album_ptr& ) Q_DECL_OVERRIDE {}
void lookupUrl( const QString& ) Q_DECL_OVERRIDE {}
private slots:
void readStderr();
void readStdout();
void cmdExited( int code, QProcess::ExitStatus status );
private:
void sendConfig();
void handleMsg( const QByteArray& msg );
void sendMsg( const QByteArray& msg );
void doSetup( const QVariantMap& m );
void setupConfWidget( const QVariantMap& m );
void startProcess();
QProcess m_proc;
QString m_name;
QPixmap m_icon;
unsigned int m_weight, m_preference, m_timeout, m_num_restarts;
Capabilities m_capabilities;
QPointer< AccountConfigWidget > m_configWidget;
quint32 m_msgsize;
QByteArray m_msg;
bool m_ready, m_stopped, m_configSent, m_deleting;
ExternalResolver::ErrorState m_error;
};
}
#endif // SCRIPTRESOLVER_H

View File

@@ -27,15 +27,13 @@
QHash< SipStatusMessage::SipStatusMessageType, QPixmap > SipStatusMessagePrivate::s_typesPixmaps = QHash< SipStatusMessage::SipStatusMessageType, QPixmap >();
SipStatusMessage::SipStatusMessage( SipStatusMessageType statusMessageType, const QString& contactId, const QString& message )
: d_ptr( new SipStatusMessagePrivate( this, statusMessageType, contactId, message ) )
: d_ptr( new SipStatusMessagePrivate( this, statusMessageType, contactId, message, new QTimer( this ) ) )
{
Q_D( SipStatusMessage );
// make this temporary for now, as soon as i know how: add ack button
d->timer = new QTimer( this );
d->timer->setInterval( 8 * 1000 );
d->timer->setSingleShot( true );
connect( d->timer, SIGNAL( timeout() ), this, SIGNAL( finished() ) );
d->timer->start();

View File

@@ -29,12 +29,15 @@ class QTimer;
class SipStatusMessagePrivate
{
public:
SipStatusMessagePrivate( SipStatusMessage* q, SipStatusMessage::SipStatusMessageType _statusMessageType, const QString& _contactId, const QString& _message )
SipStatusMessagePrivate( SipStatusMessage* q,
SipStatusMessage::SipStatusMessageType _statusMessageType,
const QString& _contactId, const QString& _message,
QTimer* _timer )
: q_ptr ( q )
, contactId( _contactId )
, statusMessageType( _statusMessageType )
, message( _message )
, timer( _timer )
{
}
SipStatusMessage* q_ptr;

View File

@@ -1,498 +0,0 @@
/* === This file is part of Tomahawk Player - <http://tomahawk-player.org> ===
*
* Copyright 2010-2011, Leo Franchi <lfranchi@kde.org>
* Copyright 2010-2011, Hugo Lindström <hugolm84@gmail.com>
*
* 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 "SpotifyParser.h"
#include "jobview/JobStatusView.h"
#include "jobview/JobStatusModel.h"
#include "jobview/ErrorStatusMessage.h"
#include "utils/Json.h"
#include "utils/NetworkReply.h"
#include "utils/TomahawkUtils.h"
#include "utils/Logger.h"
#include "utils/NetworkAccessManager.h"
#include "Query.h"
#include "SourceList.h"
#include "DropJob.h"
#include "DropJobNotifier.h"
#include "ViewManager.h"
#include <QNetworkAccessManager>
using namespace Tomahawk;
QPixmap* SpotifyParser::s_pixmap = 0;
SpotifyParser::SpotifyParser( const QStringList& Urls, bool createNewPlaylist, QObject* parent )
: QObject ( parent )
, m_limit ( 40 )
, m_single( false )
, m_trackMode( true )
, m_collaborative( false )
, m_createNewPlaylist( createNewPlaylist )
, m_browseJob( 0 )
, m_subscribers( 0 )
{
foreach ( const QString& url, Urls )
lookupUrl( url );
}
SpotifyParser::SpotifyParser( const QString& Url, bool createNewPlaylist, QObject* parent )
: QObject ( parent )
, m_limit ( 40 )
, m_single( true )
, m_trackMode( true )
, m_collaborative( false )
, m_createNewPlaylist( createNewPlaylist )
, m_browseJob( 0 )
, m_subscribers( 0 )
{
lookupUrl( Url );
}
SpotifyParser::~SpotifyParser()
{
}
void
SpotifyParser::lookupUrl( const QString& rawLink )
{
tLog() << "Looking up Spotify rawURI:" << rawLink;
QString link = rawLink;
QRegExp isHttp( "(?:((play|open)\\.spotify.com))(.*)" );
// Some spotify apps contain the link to the playlist as url-encoded in their link (e.g. ShareMyPlaylists)
if ( link.contains( "%253A" ) )
{
link = QUrl::fromPercentEncoding( link.toUtf8() );
}
if( link.contains( "%3A" ) )
{
link = QUrl::fromPercentEncoding( link.toUtf8() );
}
if( isHttp.indexIn( link, 0 ) != -1 )
{
link = "spotify"+isHttp.cap( 3 ).replace( "/", ":" );
}
// TODO: Ignoring search and user querys atm
// (spotify:(?:(?:artist|album|track|user:[^:]+:playlist):[a-zA-Z0-9]+|user:[^:]+|search:(?:[-\w$\.+!*'(),<>:\s]+|%[a-fA-F0-9\s]{2})+))
QRegExp rx( "(spotify:(?:(?:artist|album|track|user:[^:]+:playlist):[a-zA-Z0-9]+[^:\?]))" );
if ( rx.indexIn( link, 0 ) != -1 )
{
link = rx.cap( 1 );
}
else
{
tLog() << "Bad SpotifyURI!" << link;
return;
}
if ( link.contains( "track" ) )
{
m_trackMode = true;
lookupTrack( link );
}
else if ( link.contains( "playlist" ) || link.contains( "album" ) || link.contains( "artist" ) )
{
if( !m_createNewPlaylist )
m_trackMode = true;
else
m_trackMode = false;
lookupSpotifyBrowse( link );
}
else
return; // Not valid spotify item
}
void
SpotifyParser::lookupSpotifyBrowse( const QString& link )
{
tLog() << "Parsing Spotify Browse URI:" << link;
// Used in checkBrowseFinished as identifier
m_browseUri = link;
if ( m_browseUri.contains( "playlist" ) &&
Tomahawk::Accounts::SpotifyAccount::instance() != 0 &&
Tomahawk::Accounts::SpotifyAccount::instance()->loggedIn() )
{
// Do a playlist lookup locally
// Running resolver, so do the lookup through that
qDebug() << Q_FUNC_INFO << "Doing playlist lookup through spotify resolver:" << m_browseUri;
QVariantMap message;
message[ "_msgtype" ] = "playlistListing";
message[ "id" ] = m_browseUri;
QMetaObject::invokeMethod( Tomahawk::Accounts::SpotifyAccount::instance(), "sendMessage", Qt::QueuedConnection, Q_ARG( QVariantMap, message ),
Q_ARG( QObject*, this ),
Q_ARG( QString, "playlistListingResult" ) );
return;
}
DropJob::DropType type;
if ( m_browseUri.contains( "spotify:user" ) )
type = DropJob::Playlist;
else if ( m_browseUri.contains( "spotify:artist" ) )
type = DropJob::Artist;
else if ( m_browseUri.contains( "spotify:album" ) )
type = DropJob::Album;
else if ( m_browseUri.contains( "spotify:track" ) )
type = DropJob::Track;
else
return; // Type not supported.
QUrl url;
if ( type != DropJob::Artist )
url = QUrl( QString( SPOTIFY_PLAYLIST_API_URL "/browse/%1" ).arg( m_browseUri ) );
else
url = QUrl( QString( SPOTIFY_PLAYLIST_API_URL "/browse/%1/%2" ).arg( m_browseUri )
.arg ( m_limit ) );
NetworkReply* reply = new NetworkReply( Tomahawk::Utils::nam()->get( QNetworkRequest( url ) ) );
connect( reply, SIGNAL( finished() ), SLOT( spotifyBrowseFinished() ) );
m_browseJob = new DropJobNotifier( pixmap(), "Spotify", type, reply );
JobStatusView::instance()->model()->addJob( m_browseJob );
m_queries.insert( reply );
}
void
SpotifyParser::lookupTrack( const QString& link )
{
if ( !link.contains( "track" ) ) // we only support track links atm
return;
// we need Spotify URIs such as spotify:track:XXXXXX, so if we by chance get a http://open.spotify.com url, convert it
QString uri = link;
if ( link.contains( "open.spotify.com" ) || link.contains( "play.spotify.com" ) )
{
QString hash = link;
hash.replace( "http://open.spotify.com/track/", "" ).replace( "http://play.spotify.com/track/", "" );
uri = QString( "spotify:track:%1" ).arg( hash );
}
QUrl url = QUrl( QString( "http://ws.spotify.com/lookup/1/.json?uri=%1" ).arg( uri ) );
NetworkReply* reply = new NetworkReply( Tomahawk::Utils::nam()->get( QNetworkRequest( url ) ) );
connect( reply, SIGNAL( finished() ), SLOT( spotifyTrackLookupFinished() ) );
DropJobNotifier* j = new DropJobNotifier( pixmap(), QString( "Spotify" ), DropJob::Track, reply );
JobStatusView::instance()->model()->addJob( j );
m_queries.insert( reply );
}
void
SpotifyParser::spotifyBrowseFinished()
{
NetworkReply* r = qobject_cast< NetworkReply* >( sender() );
Q_ASSERT( r );
m_queries.remove( r );
r->deleteLater();
if ( r->reply()->error() == QNetworkReply::NoError )
{
bool ok;
QByteArray jsonData = r->reply()->readAll();
QVariantMap res = TomahawkUtils::parseJson( jsonData, &ok ).toMap();
if ( !ok )
{
tLog() << "Failed to parse json from Spotify browse item:" << jsonData;
checkTrackFinished();
return;
}
QVariantMap resultResponse = res.value( res.value( "type" ).toString() ).toMap();
if ( !resultResponse.isEmpty() )
{
m_title = resultResponse.value( "name" ).toString();
m_single = false;
if ( res.value( "type" ).toString() == "playlist" )
m_creator = resultResponse.value( "creator" ).toString();
// TODO for now only take the first artist
foreach ( QVariant result, resultResponse.value( "result" ).toList() )
{
QVariantMap trackResult = result.toMap();
QString title, artist, album;
title = trackResult.value( "title", QString() ).toString();
artist = trackResult.value( "artist", QString() ).toString();
album = trackResult.value( "album", QString() ).toString();
if ( title.isEmpty() && artist.isEmpty() ) // don't have enough...
{
tLog() << "Didn't get an artist and track name from spotify, not enough to build a query on. Aborting" << title << artist << album;
return;
}
Tomahawk::query_ptr q = Tomahawk::Query::get( artist, title, album, uuid(), m_trackMode );
if ( q.isNull() )
continue;
tLog() << "Setting resulthint to " << trackResult.value( "trackuri" );
q->setResultHint( trackResult.value( "trackuri" ).toString() );
q->setProperty( "annotation", trackResult.value( "trackuri" ).toString() );
m_tracks << q;
}
}
}
else
{
JobStatusView::instance()->model()->addJob( new ErrorStatusMessage( tr( "Error fetching Spotify information from the network!" ) ) );
tLog() << "Error in network request to Spotify for track decoding:" << r->reply()->errorString();
}
if ( m_trackMode )
checkTrackFinished();
else
checkBrowseFinished();
}
void
SpotifyParser::spotifyTrackLookupFinished()
{
NetworkReply* r = qobject_cast< NetworkReply* >( sender() );
Q_ASSERT( r );
m_queries.remove( r );
r->deleteLater();
if ( r->reply()->error() == QNetworkReply::NoError )
{
bool ok;
QByteArray jsonData = r->reply()->readAll();
QVariantMap res = TomahawkUtils::parseJson( jsonData, &ok ).toMap();
if ( !ok )
{
tLog() << "Failed to parse json from Spotify track lookup:" << jsonData;
checkTrackFinished();
return;
}
else if ( !res.contains( "track" ) )
{
tLog() << "No 'track' item in the spotify track lookup result... not doing anything";
checkTrackFinished();
return;
}
// lets parse this baby
QVariantMap t = res.value( "track" ).toMap();
QString title, artist, album;
title = t.value( "name", QString() ).toString();
// TODO for now only take the first artist
if ( t.contains( "artists" ) && t[ "artists" ].canConvert< QVariantList >() && t[ "artists" ].toList().size() > 0 )
artist = t[ "artists" ].toList().first().toMap().value( "name", QString() ).toString();
if ( t.contains( "album" ) && t[ "album" ].canConvert< QVariantMap >() )
album = t[ "album" ].toMap().value( "name", QString() ).toString();
if ( title.isEmpty() && artist.isEmpty() ) // don't have enough...
{
tLog() << "Didn't get an artist and track name from spotify, not enough to build a query on. Aborting" << title << artist << album;
return;
}
Tomahawk::query_ptr q = Tomahawk::Query::get( artist, title, album, uuid(), m_trackMode );
if ( !q.isNull() )
{
q->setResultHint( t.value( "trackuri" ).toString() );
m_tracks << q;
}
}
else
{
tLog() << "Error in network request to Spotify for track decoding:" << r->reply()->errorString();
}
if ( m_trackMode )
checkTrackFinished();
else
checkBrowseFinished();
}
void
SpotifyParser::playlistListingResult( const QString& msgType, const QVariantMap& msg, const QVariant& extraData )
{
Q_UNUSED( extraData );
Q_ASSERT( msgType == "playlistListing" );
m_title = msg.value( "name" ).toString();
m_single = false;
m_creator = msg.value( "creator" ).toString();
m_collaborative = msg.value( "collaborative" ).toBool();
m_subscribers = msg.value( "subscribers" ).toInt();
const QVariantList tracks = msg.value( "tracks" ).toList();
foreach ( const QVariant& blob, tracks )
{
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 ( q.isNull() )
continue;
const QString id = trackMap.value( "id" ).toString();
if( !id.isEmpty() )
{
q->setResultHint( id );
q->setProperty( "annotation", id );
}
m_tracks << q;
}
checkBrowseFinished();
}
void
SpotifyParser::checkBrowseFinished()
{
tDebug() << "Checking for spotify batch playlist job finished" << m_queries.isEmpty() << m_createNewPlaylist;
if ( m_queries.isEmpty() ) // we're done
{
if ( m_browseJob )
m_browseJob->setFinished();
if ( m_createNewPlaylist && !m_tracks.isEmpty() )
{
QString spotifyUsername;
bool spotifyAccountLoggedIn = Accounts::SpotifyAccount::instance() && Accounts::SpotifyAccount::instance()->loggedIn();
if ( spotifyAccountLoggedIn )
{
QVariantMap creds = Accounts::SpotifyAccount::instance()->credentials();
spotifyUsername = creds.value( "username" ).toString();
}
/* if ( spotifyAccountLoggedIn && Accounts::SpotifyAccount::instance()->hasPlaylist( m_browseUri ) )
{
// The playlist is already registered with Tomahawk, so just open it instead of adding another instance.
m_playlist = Accounts::SpotifyAccount::instance()->playlistForURI( m_browseUri );
playlistCreated();
}
else*/
{
m_playlist = Playlist::create( SourceList::instance()->getLocal(),
uuid(),
m_title,
m_info,
spotifyUsername == m_creator ? QString() : m_creator,
false,
m_tracks );
connect( m_playlist.data(), SIGNAL( revisionLoaded( Tomahawk::PlaylistRevision ) ), this, SLOT( playlistCreated() ) );
/* if ( spotifyAccountLoggedIn )
{
SpotifyPlaylistUpdater* updater = new SpotifyPlaylistUpdater(
Accounts::SpotifyAccount::instance(), m_playlist->currentrevision(), m_browseUri, m_playlist );
// If the user isnt dropping a playlist the he owns, its subscribeable
if ( !m_browseUri.contains( spotifyUsername ) )
updater->setCanSubscribe( true );
else
updater->setOwner( true );
updater->setCollaborative( m_collaborative );
updater->setSubscribers( m_subscribers );
// Just register the infos
Accounts::SpotifyAccount::instance()->registerPlaylistInfo( m_title, m_browseUri, m_browseUri, false, false, updater->owner() );
Accounts::SpotifyAccount::instance()->registerUpdaterForPlaylist( m_browseUri, updater );
// On default, set the playlist as subscribed
if( !updater->owner() )
Accounts::SpotifyAccount::instance()->setSubscribedForPlaylist( m_playlist, true );
}*/
}
return;
}
else if ( m_single && !m_tracks.isEmpty() )
emit track( m_tracks.first() );
else if ( !m_single && !m_tracks.isEmpty() )
emit tracks( m_tracks );
deleteLater();
}
}
void
SpotifyParser::checkTrackFinished()
{
tDebug() << "Checking for spotify batch track job finished" << m_queries.isEmpty();
if ( m_queries.isEmpty() ) // we're done
{
if ( m_browseJob )
m_browseJob->setFinished();
if ( m_single && !m_tracks.isEmpty() )
emit track( m_tracks.first() );
else if ( !m_single && !m_tracks.isEmpty() )
emit tracks( m_tracks );
deleteLater();
}
}
void
SpotifyParser::playlistCreated()
{
ViewManager::instance()->show( m_playlist );
deleteLater();
}
QPixmap
SpotifyParser::pixmap() const
{
if ( !s_pixmap )
s_pixmap = new QPixmap( RESPATH "images/spotify-logo.png" );
return *s_pixmap;
}

View File

@@ -1,103 +0,0 @@
/* === This file is part of Tomahawk Player - <http://tomahawk-player.org> ===
*
* Copyright 2010-2011, Leo Franchi <lfranchi@kde.org>
* Copyright 2010-2011, Hugo Lindström <hugolm84@gmail.com>
*
* 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 SPOTIFY_PARSER_H
#define SPOTIFY_PARSER_H
#include "DllMacro.h"
#include "Typedefs.h"
#include "Query.h"
#include "jobview/JobStatusItem.h"
//#include "accounts/spotify/SpotifyPlaylistUpdater.h"
#include "accounts/spotify/SpotifyAccount.h"
#include <QObject>
#include <QSet>
#include <QtCore/QStringList>
#define SPOTIFY_PLAYLIST_API_URL "http://spotikea.tomahawk-player.org"
/**
* Small class to parse spotify links into query_ptrs
*
* Connect to the signals to get the results
*/
class NetworkReply;
class SpotifyAccount;
namespace Tomahawk
{
class DropJobNotifier;
class DLLEXPORT SpotifyParser : public QObject
{
Q_OBJECT
public:
friend class SpotifyJobNotifier;
explicit SpotifyParser( const QString& trackUrl, bool createNewPlaylist = false, QObject* parent = 0 );
explicit SpotifyParser( const QStringList& trackUrls, bool createNewPlaylist = false, QObject* parent = 0 );
virtual ~SpotifyParser();
// if true, emits track(), if false, emits tracks().
// only matters if you're using the QStrin constructor and explicityl dont' want
// the single track signal
void setSingleMode( bool single ) { m_single = single; }
public slots:
void playlistListingResult( const QString& msgType, const QVariantMap& msg, const QVariant& extraData );
signals:
void track( const Tomahawk::query_ptr& track );
void tracks( const QList< Tomahawk::query_ptr > tracks );
void playlist( const Tomahawk::query_ptr& playlist );
private slots:
void spotifyTrackLookupFinished();
void spotifyBrowseFinished();
void playlistCreated();
private:
QPixmap pixmap() const;
void lookupUrl( const QString& url );
void lookupTrack( const QString& track );
void lookupSpotifyBrowse(const QString& link );
void checkTrackFinished();
void checkBrowseFinished();
int m_limit;
bool m_single;
bool m_trackMode;
bool m_collaborative;
bool m_createNewPlaylist;
DropJobNotifier* m_browseJob;
int m_subscribers;
QList< query_ptr > m_tracks;
QSet< NetworkReply* > m_queries;
QString m_title, m_info, m_creator;
Tomahawk::playlist_ptr m_playlist;
QString m_browseUri;
static QPixmap* s_pixmap;
};
}
#endif

View File

@@ -50,8 +50,6 @@
#include "utils/NetworkAccessManager.h"
#include "accounts/lastfm/LastFmAccount.h"
#include "accounts/spotify/SpotifyAccount.h"
//#include "accounts/spotify/SpotifyPlaylistUpdater.h"
#include "accounts/AccountManager.h"
#include "audio/AudioEngine.h"
#include "database/Database.h"
@@ -70,8 +68,6 @@
#include "widgets/SplashWidget.h"
#include "resolvers/JSResolver.h"
#include "resolvers/ScriptResolver.h"
#include "utils/SpotifyParser.h"
#include "AtticaManager.h"
#include "TomahawkWindow.h"
#include "dialogs/SettingsDialog.h"
@@ -228,9 +224,6 @@ TomahawkApp::init()
Pipeline::instance()->addExternalResolverFactory(
std::bind( &JSResolver::factory, std::placeholders::_1,
std::placeholders::_2, std::placeholders::_3 ) );
Pipeline::instance()->addExternalResolverFactory(
std::bind( &ScriptResolver::factory, std::placeholders::_1,
std::placeholders::_2, std::placeholders::_3 ) );
new ActionCollection( this );
connect( ActionCollection::instance()->getAction( "quit" ), SIGNAL( triggered() ), SLOT( quit() ), Qt::UniqueConnection );
@@ -563,10 +556,6 @@ TomahawkApp::initFactoriesForAccountManager()
m_accountManager.data()->addAccountFactory( lastfmFactory );
#endif
Tomahawk::Accounts::SpotifyAccountFactory* spotifyFactory = new Tomahawk::Accounts::SpotifyAccountFactory;
m_accountManager.data()->addAccountFactory( spotifyFactory );
m_accountManager.data()->registerAccountFactoryForFilesystem( spotifyFactory );
Tomahawk::Accounts::AccountManager::instance()->loadFromConfig();
}
@@ -678,7 +667,6 @@ TomahawkApp::onInfoSystemReady()
Tomahawk::EchonestCatalogSynchronizer::instance();
PlaylistUpdaterInterface::registerUpdaterFactory( new XspfUpdaterFactory );
// PlaylistUpdaterInterface::registerUpdaterFactory( new SpotifyUpdaterFactory );
// Following work-around/fix taken from Clementine rev. 13e13ccd9a95 and courtesy of David Sansome
// A bug in Qt means the wheel_scroll_lines setting gets ignored and replaced
@@ -692,10 +680,6 @@ TomahawkApp::onInfoSystemReady()
// Make sure to init GAM in the gui thread
GlobalActionManager::instance();
// check if our spotify playlist api server is up and running, and enable spotify playlist drops if so
QNetworkReply* r = Tomahawk::Utils::nam()->get( QNetworkRequest( QUrl( SPOTIFY_PLAYLIST_API_URL "/pong" ) ) );
connect( r, SIGNAL( finished() ), this, SLOT( spotifyApiCheckFinished() ) );
#ifdef Q_OS_MAC
// Make sure to do this after main window is inited
Tomahawk::enableFullscreen( m_mainwindow );
@@ -773,16 +757,6 @@ TomahawkApp::ipDetectionFailed( QNetworkReply::NetworkError error, QString error
}
void
TomahawkApp::spotifyApiCheckFinished()
{
QNetworkReply* reply = qobject_cast< QNetworkReply* >( sender() );
Q_ASSERT( reply );
DropJob::setCanParseSpotifyPlaylists( !reply->error() );
}
void
TomahawkApp::activate()
{

View File

@@ -110,7 +110,6 @@ private slots:
void onShutdownDelayed();
void spotifyApiCheckFinished();
void onInfoSystemReady();
void onSchemaUpdateStarted();

View File

@@ -36,7 +36,6 @@
#include "accounts/DelegateConfigWrapper.h"
#include "Pipeline.h"
#include "resolvers/Resolver.h"
#include "resolvers/ExternalResolverGui.h"
#include "utils/TomahawkUtilsGui.h"
#include "utils/GuiHelpers.h"
#include "accounts/AccountDelegate.h"
@@ -50,7 +49,6 @@
#include "accounts/ResolverAccount.h"
#include "utils/Logger.h"
#include "accounts/AccountFactoryWrapper.h"
#include "accounts/spotify/SpotifyAccount.h"
#include "thirdparty/Qocoa/qtoolbartabdialog.h"
#include "thirdparty/Qocoa/qbutton.h"
#include "jobview/JobStatusView.h"