mirror of
https://github.com/tomahawk-player/tomahawk.git
synced 2025-09-06 12:10:47 +02:00
Compare commits
21 Commits
things
...
the-super-
Author | SHA1 | Date | |
---|---|---|---|
|
e9237c1703 | ||
|
2d8f249f3c | ||
|
0c0b207497 | ||
|
5854c6531f | ||
|
cf32037a8d | ||
|
21df2a2775 | ||
|
665a3e97a5 | ||
|
b25672006c | ||
|
d6ad1121b2 | ||
|
167d46b685 | ||
|
1c26564231 | ||
|
1e11ae8b5e | ||
|
a5a4faf9d5 | ||
|
d6572f951f | ||
|
266d10988d | ||
|
fee310bd29 | ||
|
1072e3cf2b | ||
|
7022b2ad1e | ||
|
8e60f16821 | ||
|
f9f1f358ec | ||
|
c2bd1cf917 |
@@ -20,7 +20,6 @@ list(APPEND simple_plugins
|
||||
Echonest
|
||||
Charts
|
||||
NewReleases
|
||||
Spotify
|
||||
Hypem
|
||||
MusixMatch
|
||||
MusicBrainz
|
||||
|
@@ -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 )
|
@@ -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
|
@@ -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
|
||||
|
@@ -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 )
|
||||
|
@@ -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 );
|
||||
|
@@ -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)
|
||||
|
@@ -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
|
||||
{
|
||||
|
@@ -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 );
|
||||
|
@@ -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"
|
||||
|
@@ -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"
|
||||
|
@@ -318,72 +318,19 @@ 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() )
|
||||
// TODO: Readd download count
|
||||
QString versionString = index.data( AccountModel::VersionRole ).toString();
|
||||
if ( !versionString.isEmpty() )
|
||||
{
|
||||
// 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;
|
||||
int pkgTop = runningBottom + PADDING;
|
||||
int h = painter->fontMetrics().height();
|
||||
|
||||
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;
|
||||
}
|
||||
QRect pkgRect( runningEdge, pkgTop, h, h );
|
||||
painter->drawPixmap( pkgRect, TomahawkUtils::defaultPixmap( TomahawkUtils::ResolverBundle, TomahawkUtils::Original, pkgRect.size() ) );
|
||||
|
||||
// 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...
|
||||
{
|
||||
QString versionString = index.data( AccountModel::VersionRole ).toString();
|
||||
|
||||
if ( !versionString.isEmpty() )
|
||||
{
|
||||
int runningEdge = textRect.left();
|
||||
int pkgTop = runningBottom + PADDING;
|
||||
int h = painter->fontMetrics().height();
|
||||
|
||||
QRect pkgRect( runningEdge, pkgTop, h, h );
|
||||
painter->drawPixmap( pkgRect, TomahawkUtils::defaultPixmap( TomahawkUtils::ResolverBundle, TomahawkUtils::Original, pkgRect.size() ) );
|
||||
|
||||
QRect textRect( runningEdge + PADDING + h, pkgTop, painter->fontMetrics().width( versionString ), h );
|
||||
painter->drawText( textRect, Qt::AlignLeft, versionString );
|
||||
}
|
||||
QRect textRect( runningEdge + PADDING + h, pkgTop, painter->fontMetrics().width( versionString ), h );
|
||||
painter->drawText( textRect, Qt::AlignLeft, versionString );
|
||||
}
|
||||
|
||||
// Title and description!
|
||||
@@ -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 );
|
||||
|
@@ -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;
|
||||
|
@@ -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;
|
||||
}
|
||||
|
||||
|
@@ -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" )
|
||||
if ( !isPlatformSupported( configuration[ "platform" ] ) )
|
||||
{
|
||||
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 ) )
|
||||
{
|
||||
displayError( tr( "Resolver installation error: platform mismatch." ) );
|
||||
return 0;
|
||||
}
|
||||
displayError( tr( "Resolver installation error: platform mismatch." ) );
|
||||
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
|
||||
|
@@ -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 );
|
||||
|
@@ -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() ) );
|
||||
}
|
||||
|
@@ -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;
|
||||
};
|
||||
|
@@ -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
@@ -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
|
@@ -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 );
|
||||
}
|
||||
}
|
||||
|
@@ -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
|
@@ -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>
|
@@ -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 );
|
||||
}
|
@@ -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
|
@@ -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;
|
||||
}
|
||||
|
@@ -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
|
@@ -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();
|
||||
}
|
||||
|
@@ -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 )
|
||||
|
@@ -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();
|
||||
}
|
@@ -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
|
@@ -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 );
|
||||
|
@@ -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
|
||||
|
||||
|
@@ -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;
|
||||
|
@@ -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"
|
||||
|
30
src/libtomahawk/resolvers/ScriptCommand.cpp
Normal file
30
src/libtomahawk/resolvers/ScriptCommand.cpp
Normal 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 )
|
||||
{
|
||||
}
|
||||
|
||||
}
|
@@ -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;
|
||||
|
@@ -42,7 +42,6 @@ public:
|
||||
|
||||
signals:
|
||||
void albums( const QList< Tomahawk::album_ptr >& );
|
||||
void done();
|
||||
|
||||
protected:
|
||||
virtual void exec();
|
||||
|
@@ -41,7 +41,6 @@ public:
|
||||
|
||||
signals:
|
||||
void artists( const QList< Tomahawk::artist_ptr >& );
|
||||
void done();
|
||||
|
||||
protected:
|
||||
void exec() override;
|
||||
|
@@ -40,7 +40,6 @@ public:
|
||||
|
||||
signals:
|
||||
void tracks( const QList< Tomahawk::query_ptr >& );
|
||||
void done();
|
||||
|
||||
protected:
|
||||
void exec() override;
|
||||
|
@@ -45,7 +45,6 @@ public:
|
||||
|
||||
signals:
|
||||
void information( const QString& url, const QSharedPointer<QObject>& variant );
|
||||
void done();
|
||||
|
||||
protected:
|
||||
void exec() override;
|
||||
|
@@ -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 );
|
||||
}
|
@@ -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
|
@@ -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();
|
||||
|
||||
|
@@ -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;
|
||||
|
@@ -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;
|
||||
}
|
@@ -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
|
@@ -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()
|
||||
{
|
||||
|
@@ -110,7 +110,6 @@ private slots:
|
||||
|
||||
void onShutdownDelayed();
|
||||
|
||||
void spotifyApiCheckFinished();
|
||||
void onInfoSystemReady();
|
||||
|
||||
void onSchemaUpdateStarted();
|
||||
|
@@ -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"
|
||||
|
Reference in New Issue
Block a user