1
0
mirror of https://github.com/tomahawk-player/tomahawk.git synced 2025-08-11 00:24:12 +02:00

Add a last.fm account w. resolver and infotype. Needs merging w/ attica still

This commit is contained in:
Leo Franchi
2012-02-20 00:58:38 -05:00
parent 3e81405086
commit 47e8f4ffc6
28 changed files with 758 additions and 346 deletions

BIN
data/images/lastfm-icon.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 3.4 KiB

View File

@@ -133,6 +133,7 @@
<file>data/images/headphones-bigger.png</file>
<file>data/images/no-album-no-case.png</file>
<file>data/images/rdio.png</file>
<file>data/images/lastfm-icon.png</file>
<file>data/sql/dbmigrate-27_to_28.sql</file>
</qresource>
</RCC>

View File

@@ -75,10 +75,10 @@ AccountDelegate::AccountDelegate( QObject* parent )
QSize
AccountDelegate::sizeHint( const QStyleOptionViewItem& option, const QModelIndex& index ) const
AccountDelegate::sizeHint( const QStyleOptionViewItem&, const QModelIndex& index ) const
{
AccountModel::RowType rowType = static_cast< AccountModel::RowType >( index.data( AccountModel::RowTypeRole ).toInt() );
if ( rowType == AccountModel::TopLevelAccount || rowType == AccountModel::UniqueFactory )
if ( rowType == AccountModel::TopLevelAccount || rowType == AccountModel::UniqueFactory || rowType == AccountModel::CustomAccount )
return QSize( 200, TOPLEVEL_ACCOUNT_HEIGHT );
else if ( rowType == AccountModel::TopLevelFactory )
{
@@ -204,7 +204,6 @@ AccountDelegate::paint ( QPainter* painter, const QStyleOptionViewItem& option,
rightEdge = drawAccountList( painter, opt, accts, rightEdge );
painter->restore();
int centeredUnderAccounts = oldRightEdge - (oldRightEdge - rightEdge)/2 - (btnWidth/2);
btnRect = QRect( opt.rect.right() - PADDING - btnWidth, opt.rect.bottom() - installMetrics.height() - 3*PADDING, btnWidth, installMetrics.height() + 2*PADDING );
}
@@ -303,7 +302,6 @@ AccountDelegate::paint ( QPainter* painter, const QStyleOptionViewItem& option,
{
// rating stars
const int rating = index.data( AccountModel::RatingRole ).toInt();
const int ratingWidth = 5 * ( m_ratingStarPositive.width() + PADDING_BETWEEN_STARS );
// int runningEdge = opt.rect.right() - 2*PADDING - ratingWidth;
int runningEdge = textRect.left();
@@ -414,7 +412,8 @@ AccountDelegate::editorEvent( QEvent* event, QAbstractItemModel* model, const QS
m_configPressed = index;
const AccountModel::RowType rowType = static_cast< AccountModel::RowType >( index.data( AccountModel::RowTypeRole ).toInt() );
if ( rowType == AccountModel::TopLevelAccount )
if ( rowType == AccountModel::TopLevelAccount ||
rowType == AccountModel::CustomAccount )
{
Account* acct = qobject_cast< Account* >( index.data( AccountModel::AccountData ).value< QObject* >() );
Q_ASSERT( acct ); // Should not be showing a config wrench if there is no account!
@@ -445,8 +444,6 @@ AccountDelegate::editorEvent( QEvent* event, QAbstractItemModel* model, const QS
m_configPressed = QModelIndex();
const AccountModel::ItemState state = static_cast< AccountModel::ItemState >( index.data( AccountModel::StateRole ).toInt() );
if ( checkRectForIndex( option, index ).contains( me->pos() ) )
{
// Check box for this row

View File

@@ -308,6 +308,8 @@ set( libSources
accounts/AccountModel.cpp
accounts/AccountModelFilterProxy.cpp
accounts/ResolverAccount.cpp
accounts/LastFmAccount.cpp
accounts/LastFmConfig.cpp
sip/SipPlugin.cpp
sip/SipHandler.cpp
@@ -450,6 +452,8 @@ set( libHeaders
accounts/AccountModel.h
accounts/AccountModelFilterProxy.h
accounts/ResolverAccount.h
accounts/LastFmAccount.h
accounts/LastFmConfig.h
EchonestCatalogSynchronizer.h
@@ -582,6 +586,7 @@ set( libUI ${libUI}
playlist/queueview.ui
context/ContextWidget.ui
infobar/infobar.ui
accounts/LastFmConfig.ui
)
include_directories( . ${CMAKE_CURRENT_BINARY_DIR} ${CMAKE_CURRENT_BINARY_DIR}/.. ..

View File

@@ -35,6 +35,7 @@ accountTypeToString( AccountType type )
case ResolverType:
return QObject::tr( "Music Finders" );
case InfoType:
case StatusPushType:
return QObject::tr( "Status Updaters" );
}
@@ -174,6 +175,8 @@ Account::setTypes( AccountTypes types )
m_types << "SipType";
if ( types & ResolverType )
m_types << "ResolverType";
if ( types & StatusPushType )
m_types << "StatusPushType";
syncConfig();
}
@@ -189,6 +192,8 @@ Account::types() const
types |= SipType;
if ( m_types.contains( "ResolverType" ) )
types |= ResolverType;
if ( m_types.contains( "StatusPushTypeType" ) )
types |= StatusPushType;
return types;
}

View File

@@ -50,7 +50,8 @@ enum AccountType
InfoType = 0x01,
SipType = 0x02,
ResolverType = 0x04
ResolverType = 0x04,
StatusPushType = 0x08
};
DLLEXPORT QString accountTypeToString( AccountType type );
@@ -88,7 +89,7 @@ public:
virtual QWidget* configurationWidget() = 0;
virtual void saveConfig() {} // called when the widget has been edited. save values from config widget, call sync() to write to disk account generic settings
QVariantHash credentials() { QMutexLocker locker( &m_mutex ); return m_credentials; }
QVariantHash credentials() const { QMutexLocker locker( &m_mutex ); return m_credentials; }
QVariantMap acl() const { QMutexLocker locker( &m_mutex ); return m_acl; }
virtual QWidget* aclWidget() = 0;

View File

@@ -21,6 +21,7 @@
#include "config.h"
#include "sourcelist.h"
#include "ResolverAccount.h"
#include "LastFmAccount.h"
#include <QtCore/QLibrary>
#include <QtCore/QDir>
@@ -58,6 +59,9 @@ AccountManager::AccountManager( QObject *parent )
// We include the resolver factory manually, not in a plugin
ResolverAccountFactory* f = new ResolverAccountFactory();
m_accountFactories[ f->factoryId() ] = f;
LastFmAccountFactory* l = new LastFmAccountFactory();
m_accountFactories[ l->factoryId() ] = l;
}
@@ -291,16 +295,14 @@ AccountManager::addAccount( Account* account )
if ( account->types() & Accounts::SipType )
m_accountsByAccountType[ Accounts::SipType ].append( account );
if ( account->types() & Accounts::InfoType )
{
m_accountsByAccountType[ Accounts::InfoType ].append( account );
if ( account->infoPlugin() )
{
InfoSystem::InfoSystem::instance()->addInfoPlugin( account->infoPlugin() );
}
}
if ( account->types() & Accounts::ResolverType )
m_accountsByAccountType[ Accounts::ResolverType ].append( account );
if ( account->types() & Accounts::StatusPushType )
m_accountsByAccountType[ Accounts::StatusPushType ].append( account );
if ( account->infoPlugin() )
InfoSystem::InfoSystem::instance()->addInfoPlugin( account->infoPlugin() );
emit added( account );
}

View File

@@ -59,6 +59,13 @@ AccountModel::loadData()
qDebug() << "Creating factory node:" << fac->prettyName();
m_accounts << new AccountModelNode( fac );
// remove the accounts we are dealing with
foreach ( Account* acct, allAccounts )
{
if ( AccountManager::instance()->factoryForAccount( acct ) == fac )
allAccounts.removeAll( acct );
}
}
// add all attica resolvers (installed or uninstalled)
@@ -67,15 +74,26 @@ AccountModel::loadData()
{
qDebug() << "Loading ATTICA ACCOUNT with content:" << content.id() << content.name();
m_accounts << new AccountModelNode( content );
foreach ( Account* acct, AccountManager::instance()->accounts( Accounts::ResolverType ) )
{
if ( AtticaResolverAccount* resolver = qobject_cast< AtticaResolverAccount* >( acct ) )
{
if ( resolver->atticaId() == content.id() )
{
allAccounts.removeAll( acct );
}
}
}
}
// Add all non-attica manually installed resolvers
// All other accounts we haven't dealt with yet
foreach ( Account* acct, allAccounts )
{
if ( qobject_cast< ResolverAccount* >( acct ) && !qobject_cast< AtticaResolverAccount* >( acct ) )
{
m_accounts << new AccountModelNode( qobject_cast< ResolverAccount* >( acct ) );
}
else
m_accounts << new AccountModelNode( acct );
}
endResetModel();
@@ -280,6 +298,41 @@ AccountModel::data( const QModelIndex& index, int role ) const
}
}
}
case AccountModelNode::CustomAccountType:
{
Q_ASSERT( node->customAccount );
Q_ASSERT( node->factory );
Account* account = node->customAccount;
switch ( role )
{
case Qt::DisplayRole:
return account->accountFriendlyName();
case Qt::DecorationRole:
return account->icon();
case StateRole:
return ShippedWithTomahawk;
case Qt::ToolTipRole:
case DescriptionRole:
return node->factory->description();
case CanRateRole:
return false;
case RowTypeRole:
return CustomAccount;
case AccountData:
return QVariant::fromValue< QObject* >( account );
case HasConfig:
return account->configurationWidget() != 0;
case AccountTypeRole:
return QVariant::fromValue< AccountTypes >( account->types() );
case Qt::CheckStateRole:
return account->enabled();
case ConnectionStateRole:
return account->connectionState();
default:
return QVariant();
}
}
}
return QVariant();
@@ -345,6 +398,9 @@ AccountModel::setData( const QModelIndex& index, const QVariant& value, int role
case AccountModelNode::ManualResolverType:
acct = node->resolverAccount;
break;
case AccountModelNode::CustomAccountType:
acct = node->customAccount;
break;
default:
;
};

View File

@@ -70,7 +70,8 @@ public:
enum RowType {
TopLevelFactory,
TopLevelAccount,
UniqueFactory
UniqueFactory,
CustomAccount
};
enum ItemState {

View File

@@ -36,13 +36,14 @@ namespace Accounts {
* 1) AccountFactory* for all factories that have child accounts. Also a list of children
* 2) Attica::Content for AtticaResolverAccounts (with associated AtticaResolverAccount*) (all synchrotron resolvers)
* 3) ResolverAccount* for manually added resolvers (from file).
* 4) Account* for custom accounts. These may be hybrid infosystem/resolver/sip plugins or other special accounts
*
* These are the top-level items in tree.
*
* Top level nodes all look the same to the user. The only difference is that services that have login (and thus
* can have multiple logins at once) allow a user to create multiple children with specific login information.
* All other top level accounts (Account*, Attica::Content, ResolverAccount*) behave the same to the user, they can
* simply click "Install" or toggle on/off.
* simply toggle on/off.
*
*/
@@ -51,7 +52,8 @@ struct AccountModelNode {
FactoryType,
UniqueFactoryType,
AtticaType,
ManualResolverType
ManualResolverType,
CustomAccountType
};
AccountModelNode* parent;
NodeType type;
@@ -67,6 +69,9 @@ struct AccountModelNode {
/// 3.
ResolverAccount* resolverAccount;
/// 4.
Account* customAccount;
// Construct in one of four ways. Then access the corresponding members
explicit AccountModelNode( AccountFactory* fac ) : type( FactoryType )
{
@@ -114,11 +119,19 @@ struct AccountModelNode {
resolverAccount = ra;
}
explicit AccountModelNode( Account* account ) : type( CustomAccountType )
{
init();
customAccount = account;
factory = AccountManager::instance()->factoryForAccount( account );
}
void init()
{
factory = 0;
atticaAccount = 0;
resolverAccount = 0;
customAccount = 0;
}
};

View File

@@ -0,0 +1,194 @@
/* === This file is part of Tomahawk Player - <http://tomahawk-player.org> ===
*
* Copyright 2010-2012, Leo Franchi <lfranchi@kde.org>
*
* Tomahawk is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* Tomahawk is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with Tomahawk. If not, see <http://www.gnu.org/licenses/>.
*/
#include "LastFmAccount.h"
#include "LastFmConfig.h"
#include "infosystem/infosystem.h"
#include "infosystem/infoplugins/generic/lastfmplugin.h"
#include "utils/tomahawkutils.h"
#include "resolvers/qtscriptresolver.h"
using namespace Tomahawk;
using namespace InfoSystem;
using namespace Accounts;
LastFmAccountFactory::LastFmAccountFactory()
{
m_icon.load( RESPATH "images/lastfm-icon.png" );
}
Account*
LastFmAccountFactory::createAccount( const QString& accountId )
{
return new LastFmAccount( accountId.isEmpty() ? generateId( factoryId() ) : accountId );
}
QPixmap
LastFmAccountFactory::icon() const
{
return m_icon;
}
LastFmAccount::LastFmAccount( const QString& accountId )
: Account( accountId )
{
m_infoPlugin = new LastFmPlugin( this );
setAccountFriendlyName( "Last.Fm" );
m_icon.load( RESPATH "images/lastfm-icon.png" );
}
LastFmAccount::~LastFmAccount()
{
delete m_infoPlugin;
delete m_resolver.data();
}
void
LastFmAccount::authenticate()
{
}
void
LastFmAccount::deauthenticate()
{
}
QWidget*
LastFmAccount::configurationWidget()
{
if ( m_configWidget.isNull() )
m_configWidget = QWeakPointer<LastFmConfig>( new LastFmConfig( this ) );
return m_configWidget.data();
}
Account::ConnectionState
LastFmAccount::connectionState() const
{
return m_authenticated ? Account::Connected : Account::Disconnected;
}
QPixmap
LastFmAccount::icon() const
{
return m_icon;
}
InfoPlugin*
LastFmAccount::infoPlugin()
{
return m_infoPlugin;
}
bool
LastFmAccount::isAuthenticated() const
{
return m_authenticated;
}
void
LastFmAccount::saveConfig()
{
if ( !m_configWidget.isNull() )
{
setUsername( m_configWidget.data()->username() );
setPassword( m_configWidget.data()->password() );
setScrobble( m_configWidget.data()->scrobble() );
}
m_infoPlugin->settingsChanged();
}
QString
LastFmAccount::password() const
{
return credentials().value( "password" ).toString();
}
void
LastFmAccount::setPassword( const QString& password )
{
QVariantHash creds;
creds[ "password" ] = password;
setCredentials( creds );
}
QString
LastFmAccount::sessionKey() const
{
return credentials().value( "sessionkey" ).toString();
}
void
LastFmAccount::setSessionKey( const QString& sessionkey )
{
QVariantHash creds;
creds[ "sessionkey" ] = sessionkey;
setCredentials( creds );
}
QString
LastFmAccount::username() const
{
return credentials().value( "username" ).toString();
}
void
LastFmAccount::setUsername( const QString& username )
{
QVariantHash creds;
creds[ "username" ] = username;
setCredentials( creds );
}
bool
LastFmAccount::scrobble() const
{
return configuration().value( "scrobble" ).toBool();
}
void
LastFmAccount::setScrobble( bool scrobble )
{
QVariantHash conf;
conf[ "scrobble" ] = scrobble;
setConfiguration( conf );
}

View File

@@ -0,0 +1,103 @@
/* === This file is part of Tomahawk Player - <http://tomahawk-player.org> ===
*
* Copyright 2010-2012, Leo Franchi <lfranchi@kde.org>
*
* Tomahawk is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* Tomahawk is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with Tomahawk. If not, see <http://www.gnu.org/licenses/>.
*/
#ifndef LASTFMACCOUNT_H
#define LASTFMACCOUNT_H
#include "accounts/Account.h"
#include <QObject>
class QtScriptResolver;
namespace Tomahawk {
namespace InfoSystem {
class LastFmPlugin;
}
namespace Accounts {
class LastFmConfig;
class LastFmAccountFactory : public AccountFactory
{
Q_OBJECT
public:
LastFmAccountFactory();
virtual Account* createAccount(const QString& accountId = QString());
virtual QString description() const { return tr( "Scrobble your tracks to last.fm, and find freely downloadable tracks to play" ); }
virtual QString factoryId() const { return "lastfmaccount"; }
virtual QString prettyName() const { return "Last.fm"; }
virtual AccountTypes types() const { return AccountTypes( InfoType | StatusPushType ); }
virtual bool allowUserCreation() const { return false; }
virtual QPixmap icon() const;
virtual bool isUnique() const { return true; }
private:
QPixmap m_icon;
};
/**
* 3.Last.Fm account is special. It is both an attica resolver *and* a InfoPlugin. We always want the infoplugin,
* but the user can install the attica resolver on-demand. So we take care of both there.
*
*/
class LastFmAccount : public Account
{
Q_OBJECT
public:
explicit LastFmAccount( const QString& accountId );
~LastFmAccount();
virtual void deauthenticate();
virtual void authenticate();
virtual SipPlugin* sipPlugin() { return 0; }
virtual Tomahawk::InfoSystem::InfoPlugin* infoPlugin();
virtual bool isAuthenticated() const;
virtual ConnectionState connectionState() const;
virtual QPixmap icon() const;
virtual QWidget* aclWidget() { return 0; }
virtual QWidget* configurationWidget();
virtual void saveConfig();
QString username() const;
void setUsername( const QString& );
QString password() const;
void setPassword( const QString& );
QString sessionKey() const;
void setSessionKey( const QString& );
bool scrobble() const;
void setScrobble( bool scrobble );
private:
bool m_authenticated;
QWeakPointer<QtScriptResolver> m_resolver;
Tomahawk::InfoSystem::LastFmPlugin* m_infoPlugin;
QWeakPointer<LastFmConfig> m_configWidget;
QPixmap m_icon;
};
}
}
#endif // LASTFMACCOUNT_H

View File

@@ -0,0 +1,134 @@
/* === This file is part of Tomahawk Player - <http://tomahawk-player.org> ===
*
* Copyright 2010-2011, Leo Franchi <lfranchi@kde.org>
*
* Tomahawk is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* 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 "LastFmConfig.h"
#include "LastFmAccount.h"
#include <utils/tomahawkutils.h>
#include "ui_LastFmConfig.h"
#include "lastfm/ws.h"
#include "lastfm/XmlQuery"
using namespace Tomahawk::Accounts;
LastFmConfig::LastFmConfig( LastFmAccount* account )
: QWidget( 0 )
, m_account( account )
{
m_ui = new Ui_LastFmConfig;
m_ui->setupUi( this );
m_ui->username->setText( m_account->username() );
m_ui->password->setText( m_account->password() );
m_ui->enable->setChecked( m_account->scrobble() );
connect( m_ui->testLogin, SIGNAL( clicked( bool ) ), this, SLOT( testLogin( bool ) ) );
// #ifdef Q_WS_MAC // FIXME
// m_ui->testLogin->setVisible( false );
// #endif
}
QString
LastFmConfig::password() const
{
return m_ui->password->text();
}
bool
LastFmConfig::scrobble() const
{
return m_ui->enable->isChecked();
}
QString
LastFmConfig::username() const
{
return m_ui->username->text().trimmed();
}
void
LastFmConfig::testLogin(bool )
{
m_ui->testLogin->setEnabled( false );
m_ui->testLogin->setText( "Testing..." );
QString authToken = TomahawkUtils::md5( ( m_ui->username->text().toLower() + TomahawkUtils::md5( m_ui->password->text().toUtf8() ) ).toUtf8() );
// now authenticate w/ last.fm and get our session key
QMap<QString, QString> query;
query[ "method" ] = "auth.getMobileSession";
query[ "username" ] = m_ui->username->text().toLower();
query[ "authToken" ] = authToken;
// ensure they have up-to-date settings
lastfm::setNetworkAccessManager( TomahawkUtils::nam() );
QNetworkReply* authJob = lastfm::ws::post( query );
connect( authJob, SIGNAL( finished() ), SLOT( onLastFmFinished() ) );
}
void
LastFmConfig::onLastFmFinished()
{
QNetworkReply* authJob = dynamic_cast<QNetworkReply*>( sender() );
if( !authJob )
{
qDebug() << Q_FUNC_INFO << "No auth job returned!";
return;
}
if( authJob->error() == QNetworkReply::NoError )
{
lastfm::XmlQuery lfm = lastfm::XmlQuery( authJob->readAll() );
if( lfm.children( "error" ).size() > 0 )
{
qDebug() << "ERROR from last.fm:" << lfm.text();
m_ui->testLogin->setText( tr( "Failed" ) );
m_ui->testLogin->setEnabled( true );
}
else
{
m_ui->testLogin->setText( tr( "Success" ) );
m_ui->testLogin->setEnabled( false );
}
}
else
{
switch( authJob->error() )
{
case QNetworkReply::ContentOperationNotPermittedError:
case QNetworkReply::AuthenticationRequiredError:
m_ui->testLogin->setText( tr( "Failed" ) );
m_ui->testLogin->setEnabled( true );
break;
default:
qDebug() << "Couldn't get last.fm auth result";
m_ui->testLogin->setText( tr( "Could not contact server" ) );
m_ui->testLogin->setEnabled( true );
return;
}
}
}

View File

@@ -0,0 +1,53 @@
/* === This file is part of Tomahawk Player - <http://tomahawk-player.org> ===
*
* Copyright 2010-2011, Leo Franchi <lfranchi@kde.org>
*
* Tomahawk is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* 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 LASTFMCONFIG_H
#define LASTFMCONFIG_H
#include <QWidget>
class Ui_LastFmConfig;
namespace Tomahawk {
namespace Accounts {
class LastFmAccount;
class LastFmConfig : public QWidget
{
Q_OBJECT
public:
explicit LastFmConfig( LastFmAccount* account );
QString username() const;
QString password() const;
bool scrobble() const;
public slots:
void testLogin( bool );
void onLastFmFinished();
private:
LastFmAccount* m_account;
Ui_LastFmConfig* m_ui;
};
}
}
#endif // LASTFMCONFIG_H

View File

@@ -0,0 +1,85 @@
<?xml version="1.0" encoding="UTF-8"?>
<ui version="4.0">
<class>LastFmConfig</class>
<widget class="QWidget" name="LastFmConfig">
<property name="geometry">
<rect>
<x>0</x>
<y>0</y>
<width>400</width>
<height>178</height>
</rect>
</property>
<property name="windowTitle">
<string>Form</string>
</property>
<layout class="QVBoxLayout" name="verticalLayout">
<item>
<widget class="QLabel" name="label">
<property name="text">
<string/>
</property>
<property name="pixmap">
<pixmap resource="../../../resources.qrc">:/data/images/lastfm-icon.png</pixmap>
</property>
<property name="alignment">
<set>Qt::AlignCenter</set>
</property>
</widget>
</item>
<item>
<layout class="QVBoxLayout" name="verticalLayout_2">
<item>
<widget class="QCheckBox" name="enable">
<property name="layoutDirection">
<enum>Qt::LeftToRight</enum>
</property>
<property name="text">
<string>Scrobble tracks to Last.fm</string>
</property>
</widget>
</item>
<item>
<layout class="QGridLayout" name="gridLayout_3">
<item row="0" column="0">
<widget class="QLabel" name="label_3">
<property name="text">
<string>Username:</string>
</property>
</widget>
</item>
<item row="0" column="1">
<widget class="QLineEdit" name="username"/>
</item>
<item row="1" column="0">
<widget class="QLabel" name="label_4">
<property name="text">
<string>Password:</string>
</property>
</widget>
</item>
<item row="1" column="1">
<widget class="QLineEdit" name="password">
<property name="echoMode">
<enum>QLineEdit::Password</enum>
</property>
</widget>
</item>
</layout>
</item>
<item>
<widget class="QPushButton" name="testLogin">
<property name="text">
<string>Test Login</string>
</property>
</widget>
</item>
</layout>
</item>
</layout>
</widget>
<resources>
<include location="../../../resources.qrc"/>
</resources>
<connections/>
</ui>

View File

@@ -26,34 +26,37 @@
#include "album.h"
#include "typedefs.h"
#include "audio/audioengine.h"
#include "tomahawksettings.h"
#include "utils/tomahawkutils.h"
#include "utils/logger.h"
#include "accounts/LastFmAccount.h"
#include <lastfm/ws.h>
#include <lastfm/XmlQuery>
#include <qjson/parser.h>
using namespace Tomahawk::Accounts;
using namespace Tomahawk::InfoSystem;
LastFmPlugin::LastFmPlugin()
LastFmPlugin::LastFmPlugin( LastFmAccount* account )
: InfoPlugin()
, m_account( account )
, m_scrobbler( 0 )
{
m_supportedGetTypes << InfoAlbumCoverArt << InfoArtistImages << InfoArtistSimilars << InfoArtistSongs << InfoChart << InfoChartCapabilities;
m_supportedPushTypes << InfoSubmitScrobble << InfoSubmitNowPlaying << InfoLove << InfoUnLove;
// Flush session key cache
TomahawkSettings::instance()->setLastFmSessionKey( QByteArray() );
// TODO WHY FLUSH
// m_account->setSessionKey( QByteArray() );
lastfm::ws::ApiKey = "7194b85b6d1f424fe1668173a78c0c4a";
lastfm::ws::SharedSecret = "ba80f1df6d27ae63e9cb1d33ccf2052f";
lastfm::ws::Username = TomahawkSettings::instance()->lastFmUsername();
lastfm::ws::Username = m_account->username();
lastfm::setNetworkAccessManager( TomahawkUtils::nam() );
m_pw = TomahawkSettings::instance()->lastFmPassword();
m_pw = m_account->password();
//HACK work around a bug in liblastfm---it doesn't create its config dir, so when it
// tries to write the track cache, it fails silently. until we have a fixed version, do this
@@ -69,9 +72,6 @@ LastFmPlugin::LastFmPlugin()
m_badUrls << QUrl( "http://cdn.last.fm/flatness/catalogue/noimage" );
connect( TomahawkSettings::instance(), SIGNAL( changed() ),
SLOT( settingsChanged() ), Qt::QueuedConnection );
QTimer::singleShot( 0, this, SLOT( settingsChanged() ) );
}
@@ -693,23 +693,23 @@ LastFmPlugin::artistImagesReturned()
void
LastFmPlugin::settingsChanged()
{
if ( !m_scrobbler && TomahawkSettings::instance()->scrobblingEnabled() )
if ( !m_scrobbler && m_account->enabled() )
{ // can simply create the scrobbler
lastfm::ws::Username = TomahawkSettings::instance()->lastFmUsername();
m_pw = TomahawkSettings::instance()->lastFmPassword();
lastfm::ws::Username = m_account->username();
m_pw = m_account->password();
createScrobbler();
}
else if ( m_scrobbler && !TomahawkSettings::instance()->scrobblingEnabled() )
else if ( m_scrobbler && !m_account->enabled() )
{
delete m_scrobbler;
m_scrobbler = 0;
}
else if ( TomahawkSettings::instance()->lastFmUsername() != lastfm::ws::Username ||
TomahawkSettings::instance()->lastFmPassword() != m_pw )
else if ( m_account->username() != lastfm::ws::Username ||
m_account->password() != m_pw )
{
lastfm::ws::Username = TomahawkSettings::instance()->lastFmUsername();
m_pw = TomahawkSettings::instance()->lastFmPassword();
lastfm::ws::Username = m_account->username();
m_pw = m_account->password();
// credentials have changed, have to re-create scrobbler for them to take effect
if ( m_scrobbler )
{
@@ -739,16 +739,16 @@ LastFmPlugin::onAuthenticated()
if ( lfm.children( "error" ).size() > 0 )
{
tLog() << "Error from authenticating with Last.fm service:" << lfm.text();
TomahawkSettings::instance()->setLastFmSessionKey( QByteArray() );
m_account->setSessionKey( QByteArray() );
}
else
{
lastfm::ws::SessionKey = lfm[ "session" ][ "key" ].text();
TomahawkSettings::instance()->setLastFmSessionKey( lastfm::ws::SessionKey.toLatin1() );
m_account->setSessionKey( lastfm::ws::SessionKey.toLatin1() );
// qDebug() << "Got session key from last.fm";
if ( TomahawkSettings::instance()->scrobblingEnabled() )
if ( m_account->enabled() )
m_scrobbler = new lastfm::Audioscrobbler( "thk" );
}
}
@@ -764,7 +764,7 @@ LastFmPlugin::onAuthenticated()
void
LastFmPlugin::createScrobbler()
{
if ( TomahawkSettings::instance()->lastFmSessionKey().isEmpty() ) // no session key, so get one
if ( m_account->sessionKey().isEmpty() ) // no session key, so get one
{
qDebug() << "LastFmPlugin::createScrobbler Session key is empty";
QString authToken = TomahawkUtils::md5( ( lastfm::ws::Username.toLower() + TomahawkUtils::md5( m_pw.toUtf8() ) ).toUtf8() );
@@ -780,7 +780,7 @@ LastFmPlugin::createScrobbler()
else
{
qDebug() << "LastFmPlugin::createScrobbler Already have session key";
lastfm::ws::SessionKey = TomahawkSettings::instance()->lastFmSessionKey();
lastfm::ws::SessionKey = m_account->sessionKey();
m_scrobbler = new lastfm::Audioscrobbler( "thk" );
}

View File

@@ -32,6 +32,11 @@ class QNetworkReply;
namespace Tomahawk
{
namespace Accounts
{
class LastFmAccount;
}
namespace InfoSystem
{
@@ -40,7 +45,7 @@ class LastFmPlugin : public InfoPlugin
Q_OBJECT
public:
LastFmPlugin();
LastFmPlugin( Accounts::LastFmAccount* account );
virtual ~LastFmPlugin();
public slots:
@@ -74,6 +79,7 @@ private:
void dataError( Tomahawk::InfoSystem::InfoRequestData requestData );
Accounts::LastFmAccount* m_account;
QList<lastfm::Track> parseTrackList( QNetworkReply * reply );
lastfm::MutableTrack m_track;
@@ -88,3 +94,5 @@ private:
}
#endif // LASTFMPLUGIN_H
class A;

View File

@@ -46,6 +46,9 @@ InfoSystem* InfoSystem::s_instance = 0;
InfoSystem*
InfoSystem::instance()
{
if ( !s_instance )
s_instance = new InfoSystem( 0 );
return s_instance;
}
@@ -101,6 +104,9 @@ void
InfoSystem::init()
{
tDebug() << Q_FUNC_INFO;
if ( m_inited )
return;
if ( !m_infoSystemCacheThreadController->cache() || !m_infoSystemWorkerThreadController->worker() )
{
QTimer::singleShot( 0, this, SLOT( init() ) );
@@ -199,6 +205,12 @@ InfoSystem::pushInfo( const QString &caller, const InfoTypeMap &input )
void
InfoSystem::addInfoPlugin( InfoPlugin* plugin )
{
// Init is not complete (waiting for worker th read to start and create worker object) so keep trying till then
if ( !m_inited || !m_infoSystemWorkerThreadController->worker() )
{
QMetaObject::invokeMethod( this, "addInfoPlugin", Qt::QueuedConnection, Q_ARG( Tomahawk::InfoSystem::InfoPlugin*, plugin ) );
return;
}
QMetaObject::invokeMethod( m_infoSystemWorkerThreadController->worker(), "addInfoPlugin", Qt::QueuedConnection, Q_ARG( Tomahawk::InfoSystem::InfoPlugin*, plugin ) );
}

View File

@@ -242,8 +242,9 @@ public:
bool pushInfo( const QString &caller, const InfoType type, const QVariant &input );
bool pushInfo( const QString &caller, const InfoTypeMap &input );
public slots:
// InfoSystem takes ownership of InfoPlugins
void addInfoPlugin( InfoPlugin* plugin );
void addInfoPlugin( Tomahawk::InfoSystem::InfoPlugin* plugin );
signals:
void info( Tomahawk::InfoSystem::InfoRequestData requestData, QVariant output );

View File

@@ -62,7 +62,8 @@ public slots:
void infoSlot( Tomahawk::InfoSystem::InfoRequestData requestData, QVariant output );
void addInfoPlugin( InfoPlugin* plugin );
void addInfoPlugin( Tomahawk::InfoSystem::InfoPlugin* plugin );
private slots:
void checkTimeoutsTimerFired();

View File

@@ -164,6 +164,9 @@ Pipeline::removeScriptResolver( const QString& scriptPath )
QWeakPointer< ExternalResolver > r;
foreach ( QWeakPointer< ExternalResolver > res, m_scriptResolvers )
{
if ( res.isNull() )
continue;
if ( res.data()->filePath() == scriptPath )
r = res;
}

View File

@@ -296,6 +296,28 @@ TomahawkSettings::doUpgrade( int oldVersion, int newVersion )
}
// Add a Last.Fm account since we now moved the infoplugin into the account
const QString accountKey = QString( "lastfmaccount_%1" ).arg( QUuid::createUuid().toString().mid( 1, 8 ) );
accounts << accountKey;
const QString lfmUsername = value( "lastfm/username" ).toString();
const QString lfmPassword = value( "lastfm/password" ).toString();
const bool scrobble = value( "lastfm/enablescrobbling", false ).toBool();
beginGroup( "accounts/" + accountKey );
// setValue( "enabled", false );
setValue( "autoconnect", true );
setValue( "types", QStringList() << "ResolverType" << "StatusPushType" );
QVariantHash credentials;
credentials[ "username" ] = lfmUsername;
credentials[ "password" ] = lfmPassword;
credentials[ "session" ] = value( "lastfm/session" ).toString();
setValue( "credentials", credentials );
QVariantHash configuration;
configuration[ "scrobble" ] = scrobble;
setValue( "configuration", configuration );
endGroup();
remove( "lastfm" );
remove( "script/resolvers" );
remove( "script/loadedresolvers" );
@@ -892,62 +914,6 @@ TomahawkSettings::setExternalPort(int externalPort)
}
QString
TomahawkSettings::lastFmPassword() const
{
return value( "lastfm/password" ).toString();
}
void
TomahawkSettings::setLastFmPassword( const QString& password )
{
setValue( "lastfm/password", password );
}
QByteArray
TomahawkSettings::lastFmSessionKey() const
{
return value( "lastfm/session" ).toByteArray();
}
void
TomahawkSettings::setLastFmSessionKey( const QByteArray& key )
{
setValue( "lastfm/session", key );
}
QString
TomahawkSettings::lastFmUsername() const
{
return value( "lastfm/username" ).toString();
}
void
TomahawkSettings::setLastFmUsername( const QString& username )
{
setValue( "lastfm/username", username );
}
bool
TomahawkSettings::scrobblingEnabled() const
{
return value( "lastfm/enablescrobbling", false ).toBool();
}
void
TomahawkSettings::setScrobblingEnabled( bool enable )
{
setValue( "lastfm/enablescrobbling", enable );
}
QString
TomahawkSettings::xmppBotServer() const
{

View File

@@ -165,19 +165,6 @@ public:
QStringList aclEntries() const;
void setAclEntries( const QStringList &entries );
/// Last.fm settings
bool scrobblingEnabled() const; /// false by default
void setScrobblingEnabled( bool enable );
QString lastFmUsername() const;
void setLastFmUsername( const QString& username );
QString lastFmPassword() const;
void setLastFmPassword( const QString& password );
QByteArray lastFmSessionKey() const;
void setLastFmSessionKey( const QByteArray& key );
/// XMPP Component Settings
QString xmppBotServer() const;
void setXmppBotServer( const QString &server );

View File

@@ -87,11 +87,6 @@ public slots:
}
signals:
void repeatModeChanged( Tomahawk::PlaylistInterface::RepeatMode mode );
void shuffleModeChanged( bool enabled );
void trackCountChanged( unsigned int tracks );
void sourceTrackCountChanged( unsigned int tracks );
void nextTrackReady();
private slots:

View File

@@ -30,11 +30,6 @@
#include <QVBoxLayout>
#include <QSizeGrip>
#ifdef LIBLASTFM_FOUND
#include <lastfm/ws.h>
#include <lastfm/XmlQuery>
#endif
#include "AtticaManager.h"
#include "tomahawkapp.h"
#include "tomahawksettings.h"
@@ -168,21 +163,11 @@ SettingsDialog::SettingsDialog( QWidget *parent )
}
// NOW PLAYING
#ifdef Q_WS_MAC
ui->checkBoxEnableAdium->setChecked( s->nowPlayingEnabled() );
#else
ui->checkBoxEnableAdium->hide();
#endif
// LAST FM
ui->checkBoxEnableLastfm->setChecked( s->scrobblingEnabled() );
ui->lineEditLastfmUsername->setText( s->lastFmUsername() );
ui->lineEditLastfmPassword->setText(s->lastFmPassword() );
connect( ui->pushButtonTestLastfmLogin, SIGNAL( clicked( bool) ), SLOT( testLastFmLogin() ) );
#ifdef Q_WS_MAC // FIXME
ui->pushButtonTestLastfmLogin->setVisible( false );
#endif
// #ifdef Q_WS_MAC
// ui->checkBoxEnableAdium->setChecked( s->nowPlayingEnabled() );
// #else
// ui->checkBoxEnableAdium->hide();
// #endif
connect( ui->proxyButton, SIGNAL( clicked() ), SLOT( showProxySettings() ) );
connect( ui->checkBoxStaticPreferred, SIGNAL( toggled(bool) ), SLOT( toggleUpnp(bool) ) );
@@ -217,11 +202,7 @@ SettingsDialog::~SettingsDialog()
s->setScannerTime( ui->scannerTimeSpinBox->value() );
s->setEnableEchonestCatalogs( ui->enableEchonestCatalog->isChecked() );
s->setNowPlayingEnabled( ui->checkBoxEnableAdium->isChecked() );
s->setScrobblingEnabled( ui->checkBoxEnableLastfm->isChecked() );
s->setLastFmUsername( ui->lineEditLastfmUsername->text() );
s->setLastFmPassword( ui->lineEditLastfmPassword->text() );
// s->setNowPlayingEnabled( ui->checkBoxEnableAdium->isChecked() );
s->applyChanges();
s->sync();
@@ -265,13 +246,6 @@ SettingsDialog::createIcons()
musicButton->setFlags( Qt::ItemIsSelectable | Qt::ItemIsEnabled );
maxlen = qMax( fm.width( musicButton->text() ), maxlen );
QListWidgetItem *lastfmButton = new QListWidgetItem( ui->listWidget );
lastfmButton->setIcon( QIcon( RESPATH "images/lastfm-settings.png" ) );
lastfmButton->setText( tr( "Last.fm" ) );
lastfmButton->setTextAlignment( Qt::AlignHCenter );
lastfmButton->setFlags( Qt::ItemIsSelectable | Qt::ItemIsEnabled );
maxlen = qMax( fm.width( lastfmButton->text() ), maxlen );
QListWidgetItem *advancedButton = new QListWidgetItem( ui->listWidget );
advancedButton->setIcon( QIcon( RESPATH "images/advanced-settings.png" ) );
advancedButton->setText( tr( "Advanced" ) );
@@ -282,7 +256,6 @@ SettingsDialog::createIcons()
maxlen += 15; // padding
accountsButton->setSizeHint( QSize( maxlen, 60 ) );
musicButton->setSizeHint( QSize( maxlen, 60 ) );
lastfmButton->setSizeHint( QSize( maxlen, 60 ) );
advancedButton->setSizeHint( QSize( maxlen, 60 ) );
#ifndef Q_WS_MAC
@@ -362,78 +335,6 @@ SettingsDialog::updateScanOptionsView()
}
void
SettingsDialog::testLastFmLogin()
{
#ifdef LIBLASTFM_FOUND
ui->pushButtonTestLastfmLogin->setEnabled( false );
ui->pushButtonTestLastfmLogin->setText( "Testing..." );
QString authToken = TomahawkUtils::md5( ( ui->lineEditLastfmUsername->text().toLower() + TomahawkUtils::md5( ui->lineEditLastfmPassword->text().toUtf8() ) ).toUtf8() );
// now authenticate w/ last.fm and get our session key
QMap<QString, QString> query;
query[ "method" ] = "auth.getMobileSession";
query[ "username" ] = ui->lineEditLastfmUsername->text().toLower();
query[ "authToken" ] = authToken;
// ensure they have up-to-date settings
lastfm::setNetworkAccessManager( TomahawkUtils::nam() );
QNetworkReply* authJob = lastfm::ws::post( query );
connect( authJob, SIGNAL( finished() ), SLOT( onLastFmFinished() ) );
#endif
}
void
SettingsDialog::onLastFmFinished()
{
#ifdef LIBLASTFM_FOUND
QNetworkReply* authJob = dynamic_cast<QNetworkReply*>( sender() );
if( !authJob )
{
qDebug() << Q_FUNC_INFO << "No auth job returned!";
return;
}
if( authJob->error() == QNetworkReply::NoError )
{
lastfm::XmlQuery lfm = lastfm::XmlQuery( authJob->readAll() );
if( lfm.children( "error" ).size() > 0 )
{
qDebug() << "ERROR from last.fm:" << lfm.text();
ui->pushButtonTestLastfmLogin->setText( tr( "Failed" ) );
ui->pushButtonTestLastfmLogin->setEnabled( true );
}
else
{
ui->pushButtonTestLastfmLogin->setText( tr( "Success" ) );
ui->pushButtonTestLastfmLogin->setEnabled( false );
}
}
else
{
switch( authJob->error() )
{
case QNetworkReply::ContentOperationNotPermittedError:
case QNetworkReply::AuthenticationRequiredError:
ui->pushButtonTestLastfmLogin->setText( tr( "Failed" ) );
ui->pushButtonTestLastfmLogin->setEnabled( true );
break;
default:
qDebug() << "Couldn't get last.fm auth result";
ui->pushButtonTestLastfmLogin->setText( tr( "Could not contact server" ) );
ui->pushButtonTestLastfmLogin->setEnabled( true );
return;
}
}
#endif
}
void
SettingsDialog::accountsFilterChanged( int )
{

View File

@@ -86,9 +86,6 @@ private slots:
void toggleUpnp( bool preferStaticEnabled );
void showProxySettings();
void testLastFmLogin();
void onLastFmFinished();
void accountsFilterChanged( int );
void createAccountFromFactory( Tomahawk::Accounts::AccountFactory* );

View File

@@ -85,7 +85,7 @@
<item>
<widget class="QStackedWidget" name="stackedWidget">
<property name="currentIndex">
<number>0</number>
<number>2</number>
</property>
<widget class="QWidget" name="accountsPage">
<layout class="QVBoxLayout" name="verticalLayout_11">
@@ -98,6 +98,9 @@
<string>Internet Sources</string>
</property>
<layout class="QVBoxLayout" name="verticalLayout_8">
<property name="margin">
<number>2</number>
</property>
<item>
<layout class="QHBoxLayout" name="horizontalLayout_2">
<item>
@@ -221,119 +224,6 @@
</item>
</layout>
</widget>
<widget class="QWidget" name="lastfmPage">
<layout class="QVBoxLayout" name="verticalLayout_7">
<property name="margin">
<number>0</number>
</property>
<item>
<widget class="QGroupBox" name="groupBox">
<property name="title">
<string>Now Playing Information</string>
</property>
<layout class="QVBoxLayout" name="verticalLayout_15">
<item>
<widget class="QLabel" name="label_6">
<property name="text">
<string>Applications to update with currently playing track:</string>
</property>
</widget>
</item>
<item>
<layout class="QHBoxLayout" name="horizontalLayout_3">
<item>
<layout class="QVBoxLayout" name="verticalLayout_6">
<item>
<widget class="QCheckBox" name="checkBoxEnableAdium">
<property name="text">
<string>Adium</string>
</property>
</widget>
</item>
<item>
<spacer name="verticalSpacer_2">
<property name="orientation">
<enum>Qt::Vertical</enum>
</property>
<property name="sizeHint" stdset="0">
<size>
<width>20</width>
<height>40</height>
</size>
</property>
</spacer>
</item>
</layout>
</item>
<item>
<layout class="QVBoxLayout" name="verticalLayout_2">
<item>
<widget class="QCheckBox" name="checkBoxEnableLastfm">
<property name="layoutDirection">
<enum>Qt::LeftToRight</enum>
</property>
<property name="text">
<string>Scrobble tracks to Last.fm</string>
</property>
</widget>
</item>
<item>
<layout class="QGridLayout" name="gridLayout_3">
<item row="0" column="0">
<widget class="QLabel" name="label_3">
<property name="text">
<string>Username:</string>
</property>
</widget>
</item>
<item row="0" column="1">
<widget class="QLineEdit" name="lineEditLastfmUsername"/>
</item>
<item row="1" column="0">
<widget class="QLabel" name="label_4">
<property name="text">
<string>Password:</string>
</property>
</widget>
</item>
<item row="1" column="1">
<widget class="QLineEdit" name="lineEditLastfmPassword">
<property name="echoMode">
<enum>QLineEdit::Password</enum>
</property>
</widget>
</item>
</layout>
</item>
<item>
<widget class="QPushButton" name="pushButtonTestLastfmLogin">
<property name="text">
<string>Test Login</string>
</property>
</widget>
</item>
<item>
<spacer name="verticalSpacer">
<property name="orientation">
<enum>Qt::Vertical</enum>
</property>
<property name="sizeHint" stdset="0">
<size>
<width>20</width>
<height>40</height>
</size>
</property>
</spacer>
</item>
</layout>
</item>
</layout>
</item>
</layout>
</widget>
</item>
</layout>
</widget>
<widget class="QWidget" name="advancedPage">
<layout class="QVBoxLayout" name="verticalLayout_3">
<property name="margin">
@@ -582,4 +472,4 @@
</hints>
</connection>
</connections>
</ui>
</ui>

View File

@@ -219,13 +219,13 @@ TomahawkApp::init()
connect( m_shortcutHandler.data(), SIGNAL( mute() ), m_audioEngine.data(), SLOT( mute() ) );
}
tDebug() << "Init InfoSystem.";
m_infoSystem = QWeakPointer<Tomahawk::InfoSystem::InfoSystem>( Tomahawk::InfoSystem::InfoSystem::instance() );
tDebug() << "Init AccountManager.";
m_accountManager = QWeakPointer< Tomahawk::Accounts::AccountManager >( new Tomahawk::Accounts::AccountManager( this ) );
Tomahawk::Accounts::AccountManager::instance()->loadFromConfig();
tDebug() << "Init InfoSystem.";
m_infoSystem = QWeakPointer<Tomahawk::InfoSystem::InfoSystem>( new Tomahawk::InfoSystem::InfoSystem( this ) );
Echonest::Config::instance()->setNetworkAccessManager( TomahawkUtils::nam() );
#ifndef ENABLE_HEADLESS
EchonestGenerator::setupCatalogs();
@@ -422,6 +422,7 @@ TomahawkApp::registerMetaTypes()
qRegisterMetaType< Tomahawk::InfoSystem::InfoType >( "Tomahawk::InfoSystem::InfoType" );
qRegisterMetaType< Tomahawk::InfoSystem::InfoRequestData >( "Tomahawk::InfoSystem::InfoRequestData" );
qRegisterMetaType< Tomahawk::InfoSystem::InfoSystemCache* >( "Tomahawk::InfoSystem::InfoSystemCache*" );
qRegisterMetaType< Tomahawk::InfoSystem::InfoPlugin* >( "Tomahawk::InfoSystem::InfoPlugin*" );
qRegisterMetaType< QList< Tomahawk::InfoSystem::InfoStringHash > >("QList< Tomahawk::InfoSystem::InfoStringHash > ");
qRegisterMetaTypeStreamOperators< QList< Tomahawk::InfoSystem::InfoStringHash > >("QList< Tomahawk::InfoSystem::InfoStringHash > ");