mirror of
https://github.com/tomahawk-player/tomahawk.git
synced 2025-03-13 20:39:57 +01:00
Rdio parser for tracks, playlists, artists, albums. WIP
This commit is contained in:
parent
cce80ff535
commit
4350fad789
@ -111,7 +111,7 @@
|
||||
<file>data/sql/dbmigrate-23_to_24.sql</file>
|
||||
<file>data/sql/dbmigrate-24_to_25.sql</file>
|
||||
<file>data/sql/dbmigrate-25_to_26.sql</file>
|
||||
<file>data/sql/dbmigrate-26_to_27.sql</file>
|
||||
<file>data/sql/dbmigrate-26_to_27.sql</file>
|
||||
<file>data/js/tomahawk.js</file>
|
||||
<file>data/images/avatar_frame.png</file>
|
||||
<file>data/images/drop-all-songs.png</file>
|
||||
@ -119,7 +119,7 @@
|
||||
<file>data/images/drop-top-songs.png</file>
|
||||
<file>data/images/drop-song.png</file>
|
||||
<file>data/images/drop-album.png</file>
|
||||
<file>data/images/spotify-logo.png</file>
|
||||
<file>data/images/spotify-logo.png</file>
|
||||
<file>data/images/itunes.png</file>
|
||||
<file>data/images/uploading.png</file>
|
||||
<file>data/images/downloading.png</file>
|
||||
@ -127,6 +127,7 @@
|
||||
<file>data/images/headphones-off.png</file>
|
||||
<file>data/images/headphones-sidebar.png</file>
|
||||
<file>data/images/headphones-bigger.png</file>
|
||||
<file>data/images/no-album-no-case.png</file>
|
||||
<file>data/images/no-album-no-case.png</file>
|
||||
<file>data/images/rdio.png</file>
|
||||
</qresource>
|
||||
</RCC>
|
||||
|
@ -112,7 +112,7 @@ DropJob::acceptsMimeData( const QMimeData* data, DropJob::DropTypes acceptedType
|
||||
if ( url.contains( "spotify" ) && url.contains( "track" ) )
|
||||
return true;
|
||||
|
||||
if ( url.contains( "rdio.com" ) && url.contains( "track" ) )
|
||||
if ( url.contains( "rdio.com" ) && ( url.contains( "track" ) || /*url.contains( "artist" ) ||*/ url.contains( "album" ) || url.contains( "playlists" ) ) )
|
||||
return true;
|
||||
}
|
||||
|
||||
@ -153,6 +153,9 @@ DropJob::isDropType( DropJob::DropType desired, const QMimeData* data )
|
||||
if ( url.contains( "spotify" ) && url.contains( "playlist" ) && s_canParseSpotifyPlaylists )
|
||||
return true;
|
||||
|
||||
if ( url.contains( "rdio.com" ) && url.contains( "people" ) && url.contains( "playlist" ) )
|
||||
return true;
|
||||
|
||||
// we don't know about these.. gotta say yes for now
|
||||
if ( url.contains( "bit.ly" ) ||
|
||||
url.contains( "j.mp" ) ||
|
||||
@ -450,6 +453,30 @@ DropJob::handleSpotifyUrls( const QString& urlsRaw )
|
||||
m_queryCount++;
|
||||
}
|
||||
|
||||
void
|
||||
DropJob::handleRdioUrls( const QString& urlsRaw )
|
||||
{
|
||||
QStringList urls = urlsRaw.split( QRegExp( "\\s+" ), QString::SkipEmptyParts );
|
||||
qDebug() << "Got Rdio urls!!" << urls;
|
||||
|
||||
if ( dropAction() == Default )
|
||||
setDropAction( Create );
|
||||
|
||||
RdioParser* rdio = new RdioParser( this );
|
||||
rdio->setCreatePlaylist( dropAction() == Create );
|
||||
rdio->parse( urls );
|
||||
|
||||
/// 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( rdio, SIGNAL( tracks( QList<Tomahawk::query_ptr> ) ), this, SLOT( onTracksAdded( QList< Tomahawk::query_ptr > ) ) );
|
||||
}
|
||||
|
||||
m_queryCount++;
|
||||
}
|
||||
|
||||
|
||||
void
|
||||
DropJob::handleAllUrls( const QString& urls )
|
||||
{
|
||||
@ -459,6 +486,8 @@ DropJob::handleAllUrls( const QString& urls )
|
||||
&& ( urls.contains( "playlist" ) || urls.contains( "artist" ) || urls.contains( "album" ) || urls.contains( "track" ) )
|
||||
&& s_canParseSpotifyPlaylists )
|
||||
handleSpotifyUrls( urls );
|
||||
else if ( urls.contains( "rdio.com" ) )
|
||||
handleRdioUrls( urls );
|
||||
else
|
||||
handleTrackUrls ( urls );
|
||||
}
|
||||
|
@ -102,7 +102,9 @@ public:
|
||||
void setGetWholeAlbums( bool getWholeAlbums );
|
||||
void tracksFromMimeData( const QMimeData* data, bool allowDuplicates = false, bool onlyLocal = false, bool top10 = false );
|
||||
void handleXspfs( const QString& files );
|
||||
|
||||
void handleSpotifyUrls( const QString& urls );
|
||||
void handleRdioUrls( const QString& urls );
|
||||
|
||||
static bool canParseSpotifyPlaylists() { return s_canParseSpotifyPlaylists; }
|
||||
static void setCanParseSpotifyPlaylists( bool parseable ) { s_canParseSpotifyPlaylists = parseable; }
|
||||
|
@ -1,6 +1,7 @@
|
||||
/* === 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
|
||||
@ -18,15 +19,39 @@
|
||||
|
||||
#include "rdioparser.h"
|
||||
|
||||
#include "shortenedlinkparser.h"
|
||||
#include "config.h"
|
||||
#include "utils/tomahawkutils.h"
|
||||
#include "utils/logger.h"
|
||||
#include "dropjob.h"
|
||||
#include "jobview/JobStatusView.h"
|
||||
#include "jobview/JobStatusModel.h"
|
||||
#include "dropjobnotifier.h"
|
||||
#include "viewmanager.h"
|
||||
#include "sourcelist.h"
|
||||
|
||||
#include <qjson/parser.h>
|
||||
#include <QDateTime>
|
||||
#include <QtNetwork/QNetworkAccessManager>
|
||||
#include <QtNetwork/QNetworkReply>
|
||||
#include <QUrl>
|
||||
#include <QStringList>
|
||||
#include "shortenedlinkparser.h"
|
||||
|
||||
#include <QtCore/QCryptographicHash>
|
||||
|
||||
using namespace Tomahawk;
|
||||
|
||||
QPixmap* RdioParser::s_pixmap = 0;
|
||||
|
||||
#ifdef QCA2_FOUND
|
||||
QCA::Initializer RdioParser::m_qcaInit = QCA::Initializer();
|
||||
#endif
|
||||
|
||||
RdioParser::RdioParser( QObject* parent )
|
||||
: QObject( parent )
|
||||
, m_count( 0 )
|
||||
, m_browseJob( 0 )
|
||||
, m_createPlaylist( false )
|
||||
{
|
||||
}
|
||||
|
||||
@ -63,50 +88,273 @@ RdioParser::parseUrl( const QString& url )
|
||||
return;
|
||||
}
|
||||
|
||||
query_ptr query;
|
||||
m_count++;
|
||||
|
||||
if ( url.contains( "artist" ) && url.contains( "album" ) )
|
||||
if ( url.contains( "artist" ) && url.contains( "album" ) && url.contains( "track" ) )
|
||||
parseTrack( url );
|
||||
else
|
||||
{
|
||||
// this is a "full" url, no redirection needed
|
||||
QString realUrl = QUrl::fromUserInput( url ).toString().replace( "_", " " );
|
||||
|
||||
QString artist, trk, album;
|
||||
QString matchStr = "/%1/([^/]*)/";
|
||||
QRegExp r( QString( matchStr ).arg( "artist" ) );
|
||||
|
||||
int loc = r.indexIn( realUrl );
|
||||
if ( loc >= 0 )
|
||||
artist = r.cap( 1 );
|
||||
|
||||
r = QRegExp( QString( matchStr ).arg( "album" ) );
|
||||
loc = r.indexIn( realUrl );
|
||||
if ( loc >= 0 )
|
||||
album = r.cap( 1 );
|
||||
|
||||
r = QRegExp( QString( matchStr ).arg( "track" ) );
|
||||
loc = r.indexIn( realUrl );
|
||||
if ( loc >= 0 )
|
||||
trk = r.cap( 1 );
|
||||
|
||||
if ( !trk.isEmpty() && !artist.isEmpty() )
|
||||
DropJob::DropType type = DropJob::None;
|
||||
if ( url.contains( "artist" ) && url.contains( "album" ) )
|
||||
type = DropJob::Album;
|
||||
else if ( url.contains( "artist" ) )
|
||||
type = DropJob::Artist;
|
||||
else if ( url.contains( "people" ) && url.contains( "playlist" ) )
|
||||
type = DropJob::Playlist;
|
||||
else
|
||||
{
|
||||
query = Query::get( artist, trk, album, uuid(), true );
|
||||
tLog() << "Got Rdio URL I can't parse!" << url;
|
||||
return;
|
||||
}
|
||||
|
||||
// artist, album, or playlist link requre fetching
|
||||
fetchObjectsFromUrl( url, type );
|
||||
}
|
||||
|
||||
if ( m_multi )
|
||||
{
|
||||
if ( !query.isNull() )
|
||||
m_queries << query;
|
||||
|
||||
if ( m_count == m_total )
|
||||
emit tracks( m_queries );
|
||||
}
|
||||
if ( !m_multi && !query.isNull() )
|
||||
emit track( query );
|
||||
}
|
||||
|
||||
void
|
||||
RdioParser::fetchObjectsFromUrl( const QString& url, DropJob::DropType type )
|
||||
{
|
||||
QList< QPair< QByteArray, QByteArray > > params;
|
||||
params.append( QPair<QByteArray, QByteArray>( "extras", "tracks" ) );
|
||||
|
||||
QString cleanedUrl = url;
|
||||
cleanedUrl.replace("#/", "");
|
||||
|
||||
QByteArray data;
|
||||
QNetworkRequest request = generateRequest( "getObjectFromUrl", cleanedUrl, params, &data );
|
||||
|
||||
request.setHeader( QNetworkRequest::ContentTypeHeader, QLatin1String( "application/x-www-form-urlencoded" ) );
|
||||
QNetworkReply* reply = TomahawkUtils::nam()->post( request, data );
|
||||
connect( reply, SIGNAL( finished() ), this, SLOT( rdioReturned() ) );
|
||||
|
||||
m_browseJob = new DropJobNotifier( pixmap(), QString( "Rdio" ), type, reply );
|
||||
JobStatusView::instance()->model()->addJob( m_browseJob );
|
||||
|
||||
m_reqQueries.insert( reply );
|
||||
}
|
||||
|
||||
void
|
||||
RdioParser::rdioReturned()
|
||||
{
|
||||
|
||||
QNetworkReply* r = qobject_cast< QNetworkReply* >( sender() );
|
||||
Q_ASSERT( r );
|
||||
m_reqQueries.remove( r );
|
||||
m_count++;
|
||||
r->deleteLater();
|
||||
|
||||
if ( r->error() == QNetworkReply::NoError )
|
||||
{
|
||||
QJson::Parser p;
|
||||
bool ok;
|
||||
QVariantMap res = p.parse( r, &ok ).toMap();
|
||||
QVariantMap result = res.value( "result" ).toMap();
|
||||
|
||||
if ( !ok || result.isEmpty() )
|
||||
{
|
||||
tLog() << "Failed to parse json from Rdio browse item :" << p.errorString() << "On line" << p.errorLine() << "With data:" << res;
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
QVariantList tracks = result.value( "tracks" ).toList();
|
||||
if ( tracks.isEmpty() )
|
||||
{
|
||||
tLog() << "Got no tracks in result, ignoring!" << result;
|
||||
return;
|
||||
}
|
||||
|
||||
// Playlists will have these
|
||||
m_title = result[ "name" ].toString();
|
||||
m_creator = result[ "owner" ].toString();
|
||||
|
||||
foreach( QVariant track, tracks )
|
||||
{
|
||||
QVariantMap rdioResult = track.toMap();
|
||||
QString title, artist, album;
|
||||
|
||||
title = rdioResult.value( "name", QString() ).toString();
|
||||
artist = rdioResult.value( "artist", QString() ).toString();
|
||||
album = rdioResult.value( "album", QString() ).toString();
|
||||
|
||||
if ( title.isEmpty() && artist.isEmpty() ) // don't have enough...
|
||||
{
|
||||
tLog() << "Didn't get an artist and track name from Rdio, not enough to build a query on. Aborting" << title << artist << album;
|
||||
return;
|
||||
}
|
||||
|
||||
Tomahawk::query_ptr q = Tomahawk::Query::get( artist, title, album, uuid(), !m_createPlaylist );
|
||||
m_tracks << q;
|
||||
}
|
||||
|
||||
} else
|
||||
{
|
||||
tLog() << "Error in network request to Rdio for track decoding:" << r->errorString();
|
||||
}
|
||||
|
||||
|
||||
checkFinished();
|
||||
|
||||
|
||||
}
|
||||
|
||||
void
|
||||
RdioParser::parseTrack( const QString& origUrl )
|
||||
{
|
||||
QString url = origUrl;
|
||||
QString artist, trk, album, playlist;
|
||||
QString realUrl = url.replace( "_", " " );
|
||||
QString matchStr = "/%1/([^/]*)/";
|
||||
QString matchPlStr = "/%1/(?:[^/]*)/([^/]*)/";
|
||||
|
||||
QRegExp r( QString( matchStr ).arg( "artist" ) );
|
||||
|
||||
int loc = r.indexIn( realUrl );
|
||||
if ( loc >= 0 )
|
||||
artist = r.cap( 1 );
|
||||
|
||||
r = QRegExp( QString( matchStr ).arg( "album" ) );
|
||||
loc = r.indexIn( realUrl );
|
||||
if ( loc >= 0 )
|
||||
album = r.cap( 1 );
|
||||
|
||||
r = QRegExp( QString( matchStr ).arg( "track" ) );
|
||||
loc = r.indexIn( realUrl );
|
||||
if ( loc >= 0 )
|
||||
trk = r.cap( 1 );
|
||||
|
||||
r = QRegExp( QString( matchPlStr ).arg( "playlists" ) );
|
||||
loc = r.indexIn( realUrl );
|
||||
if ( loc >= 0 )
|
||||
playlist = r.cap( 1 );
|
||||
|
||||
if ( trk.isEmpty() || artist.isEmpty() )
|
||||
{
|
||||
tLog() << "Parsed Rdio track url but it's missing artist or track!" << url;
|
||||
return;
|
||||
}
|
||||
|
||||
query_ptr q = Query::get( artist, trk, album, uuid(), !m_createPlaylist );
|
||||
m_count++;
|
||||
m_queries << q;
|
||||
|
||||
checkFinished();
|
||||
}
|
||||
|
||||
|
||||
QNetworkRequest
|
||||
RdioParser::generateRequest( const QString& method, const QString& url, const QList< QPair< QByteArray, QByteArray > >& extraParams, QByteArray* data )
|
||||
{
|
||||
QUrl fetchUrl( "http://api.rdio.com/1/" );
|
||||
QUrl toSignUrl = fetchUrl;
|
||||
|
||||
QPair<QByteArray, QByteArray> param;
|
||||
foreach( param, extraParams )
|
||||
{
|
||||
toSignUrl.addEncodedQueryItem( param.first, param.second );
|
||||
}
|
||||
toSignUrl.addQueryItem( "method", method );
|
||||
toSignUrl.addEncodedQueryItem("oauth_consumer_key", "gk8zmyzj5xztt8aj48csaart" );
|
||||
QString nonce;
|
||||
for ( int i = 0; i < 8; i++ )
|
||||
nonce += QString::number( qrand() % 10 );
|
||||
toSignUrl.addQueryItem("oauth_nonce", nonce );
|
||||
toSignUrl.addEncodedQueryItem("oauth_signature_method", "HMAC-SHA1");
|
||||
toSignUrl.addQueryItem("oauth_timestamp", QString::number(QDateTime::currentMSecsSinceEpoch() / 1000 ) );
|
||||
toSignUrl.addEncodedQueryItem("oauth_version", "1.0");
|
||||
toSignUrl.addEncodedQueryItem( "url", QUrl::toPercentEncoding( url ) );
|
||||
int size = toSignUrl.encodedQueryItems().size();
|
||||
for( int i = 0; i < size; i++ ) {
|
||||
const QPair< QByteArray, QByteArray > item = toSignUrl.encodedQueryItems().at( i );
|
||||
data->append( item.first + "=" + item.second + "&" );
|
||||
}
|
||||
data->truncate( data->size() - 1 ); // remove extra &
|
||||
|
||||
QByteArray toSign = "POST&" + QUrl::toPercentEncoding( fetchUrl.toEncoded() ) + '&' + QUrl::toPercentEncoding( *data );
|
||||
qDebug() << "Rdio" << toSign;
|
||||
|
||||
toSignUrl.addEncodedQueryItem( "oauth_signature", QUrl::toPercentEncoding( hmacSha1("yt35kakDyW&", toSign ) ) );
|
||||
|
||||
data->clear();
|
||||
size = toSignUrl.encodedQueryItems().size();
|
||||
for( int i = 0; i < size; i++ ) {
|
||||
const QPair< QByteArray, QByteArray > item = toSignUrl.encodedQueryItems().at( i );
|
||||
data->append( item.first + "=" + item.second + "&" );
|
||||
}
|
||||
data->truncate( data->size() - 1 ); // remove extra &
|
||||
|
||||
qDebug() << "POST data:" << *data;
|
||||
|
||||
QNetworkRequest request = QNetworkRequest( fetchUrl );
|
||||
request.setHeader( QNetworkRequest::ContentTypeHeader, QLatin1String( "application/x-www-form-urlencoded" ) );
|
||||
|
||||
return request;
|
||||
}
|
||||
|
||||
|
||||
QByteArray
|
||||
RdioParser::hmacSha1(QByteArray key, QByteArray baseString)
|
||||
{
|
||||
#ifdef QCA2_FOUND
|
||||
QCA::MessageAuthenticationCode hmacsha1( "hmac(sha1)", QCA::SecureArray() );
|
||||
QCA::SymmetricKey keyObject( key );
|
||||
hmacsha1.setup( keyObject );
|
||||
|
||||
hmacsha1.update( QCA::SecureArray( baseString ) );
|
||||
QCA::SecureArray resultArray = hmacsha1.final();
|
||||
|
||||
QByteArray result = resultArray.toByteArray().toBase64();
|
||||
return result;
|
||||
#else
|
||||
tLog() << "Tomahawk compiled without QCA support, cannot generate HMAC signature";
|
||||
return QByteArray();
|
||||
#endif
|
||||
}
|
||||
|
||||
void
|
||||
RdioParser::checkFinished()
|
||||
{
|
||||
tDebug() << "Checking for Rdio batch playlist job finished" << m_reqQueries.isEmpty();
|
||||
if ( m_reqQueries.isEmpty() ) // we're done
|
||||
{
|
||||
if ( m_browseJob )
|
||||
m_browseJob->setFinished();
|
||||
|
||||
if ( m_tracks.isEmpty() )
|
||||
return;
|
||||
|
||||
if( m_createPlaylist )
|
||||
{
|
||||
m_playlist = Playlist::create( SourceList::instance()->getLocal(),
|
||||
uuid(),
|
||||
m_title,
|
||||
"",
|
||||
m_creator,
|
||||
false,
|
||||
m_tracks );
|
||||
|
||||
connect( m_playlist.data(), SIGNAL( revisionLoaded( Tomahawk::PlaylistRevision ) ), this, SLOT( playlistCreated() ) );
|
||||
return;
|
||||
}
|
||||
else
|
||||
{
|
||||
if ( !m_multi )
|
||||
emit track( m_tracks.first() );
|
||||
else if ( m_multi && m_count == m_total )
|
||||
emit tracks( m_tracks );
|
||||
}
|
||||
|
||||
deleteLater();
|
||||
}
|
||||
}
|
||||
|
||||
void
|
||||
RdioParser::playlistCreated( Tomahawk::PlaylistRevision )
|
||||
{
|
||||
ViewManager::instance()->show( m_playlist );
|
||||
}
|
||||
|
||||
|
||||
void
|
||||
RdioParser::expandedLinks( const QStringList& urls )
|
||||
{
|
||||
@ -117,3 +365,12 @@ RdioParser::expandedLinks( const QStringList& urls )
|
||||
}
|
||||
}
|
||||
|
||||
QPixmap
|
||||
RdioParser::pixmap() const
|
||||
{
|
||||
if ( !s_pixmap )
|
||||
s_pixmap = new QPixmap( RESPATH "images/rdio.png" );
|
||||
|
||||
return *s_pixmap;
|
||||
}
|
||||
|
||||
|
@ -1,6 +1,7 @@
|
||||
/* === 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
|
||||
@ -18,16 +19,27 @@
|
||||
|
||||
#ifndef RDIOPARSER_H
|
||||
#define RDIOPARSER_H
|
||||
#include "jobview/JobStatusItem.h"
|
||||
#include "query.h"
|
||||
#include "config.h"
|
||||
#include "dropjob.h"
|
||||
#include "typedefs.h"
|
||||
#include "playlist.h"
|
||||
|
||||
#include <QtCore/QObject>
|
||||
#include <QStringList>
|
||||
#include <QSet>
|
||||
|
||||
#include "query.h"
|
||||
#ifdef QCA2_FOUND
|
||||
#include <QtCrypto>
|
||||
#include <QNetworkRequest>
|
||||
#endif
|
||||
|
||||
class QNetworkReply;
|
||||
namespace Tomahawk
|
||||
{
|
||||
|
||||
class DropJobNotifier;
|
||||
/**
|
||||
* Small class to parse spotify links into query_ptrs
|
||||
*
|
||||
@ -38,25 +50,51 @@ class RdioParser : public QObject
|
||||
{
|
||||
Q_OBJECT
|
||||
public:
|
||||
|
||||
explicit RdioParser( QObject* parent = 0 );
|
||||
virtual ~RdioParser();
|
||||
|
||||
void parse( const QString& url );
|
||||
void parse( const QStringList& urls );
|
||||
|
||||
void setCreatePlaylist( bool createPlaylist ) { m_createPlaylist = createPlaylist; }
|
||||
|
||||
signals:
|
||||
void track( const Tomahawk::query_ptr& track );
|
||||
void tracks( const QList< Tomahawk::query_ptr > tracks );
|
||||
|
||||
private slots:
|
||||
void expandedLinks( const QStringList& );
|
||||
void rdioReturned();
|
||||
|
||||
void playlistCreated( Tomahawk::PlaylistRevision );
|
||||
private:
|
||||
void parseTrack( const QString& url );
|
||||
void fetchObjectsFromUrl( const QString& url, DropJob::DropType type );
|
||||
|
||||
QByteArray hmacSha1(QByteArray key, QByteArray baseString);
|
||||
QNetworkRequest generateRequest( const QString& method, const QString& url, const QList< QPair< QByteArray, QByteArray > >& extraParams, QByteArray* postData );
|
||||
QPixmap pixmap() const;
|
||||
void checkFinished();
|
||||
void parseUrl( const QString& url );
|
||||
|
||||
bool m_multi;
|
||||
int m_count, m_total;
|
||||
QList< query_ptr > m_queries;
|
||||
QSet< QNetworkReply* > m_reqQueries;
|
||||
DropJobNotifier* m_browseJob;
|
||||
|
||||
QString m_title, m_creator;
|
||||
playlist_ptr m_playlist;
|
||||
|
||||
static QPixmap* s_pixmap;
|
||||
|
||||
bool m_createPlaylist;
|
||||
QList< query_ptr > m_tracks;
|
||||
|
||||
#ifdef QCA2_FOUND
|
||||
static QCA::Initializer m_qcaInit;
|
||||
#endif
|
||||
};
|
||||
|
||||
}
|
||||
|
Loading…
x
Reference in New Issue
Block a user