diff --git a/admin/mac/macdeploy.py b/admin/mac/macdeploy.py
index 71bdefb77..6d7987982 100755
--- a/admin/mac/macdeploy.py
+++ b/admin/mac/macdeploy.py
@@ -199,10 +199,10 @@ QT_PLUGINS = [
]
TOMAHAWK_PLUGINS = [
- 'libtomahawk_account_xmpp.dylib',
- 'libtomahawk_account_google.dylib',
- 'libtomahawk_account_twitter.dylib',
- 'libtomahawk_account_zeroconf.dylib',
+ 'libtomahawk_account_xmpp.so',
+ 'libtomahawk_account_google.so',
+ 'libtomahawk_account_twitter.so',
+ 'libtomahawk_account_zeroconf.so',
]
QT_PLUGINS_SEARCH_PATH=[
@@ -495,11 +495,6 @@ for plugin in VLC_PLUGINS:
for plugin in TOMAHAWK_PLUGINS:
FixPlugin(plugin, '../MacOS')
-try:
- FixPlugin('spotify_tomahawkresolver', '../MacOS')
-except:
- print 'Failed to find spotify resolver'
-
try:
FixPlugin('tomahawk_crash_reporter', '../MacOS')
except:
diff --git a/data/misc/tomahawk_pubkey.pem b/data/misc/tomahawk_pubkey.pem
new file mode 100644
index 000000000..5e9606170
--- /dev/null
+++ b/data/misc/tomahawk_pubkey.pem
@@ -0,0 +1,20 @@
+-----BEGIN PUBLIC KEY-----
+MIIDOzCCAi0GByqGSM44BAEwggIgAoIBAQDRltnNbKWFroVCsG1nTSdlTDmo7fjl
+tgOuQ0YB2s0a1bcqgQ5YJRE59pFvF/z2pkHEHdyBA6USd9N7/T9lolwNcJoByJpO
+MobUNs04elqZXliriaAdoSb2g6ZpxiedppbbyNP/BlK6o+zpyn0LVYXDI/OwJFzS
+xjGXM+rBEWdUJnogZxV31gF9W3yD1Quz6icBulT9V/Soo6me9Mc60ooKSYj4Zgqd
+3ln8tG90RFnWfbb0nbrITvR3ll6XXLfn081tjhymcXqHcgvaaqcmpKWL6ZWwX1mH
+3t1pImnif/tSSZPG21KGE3FtuQ/+YFo19apQ6U6l8kaSFxqcDLAYzBy9AhUA/QfN
+8WEIvzOEZ9uSWT7lYy64mUkCggEABsUmcs3kwjrmszIAAmPIowA0DBrxWZL03JBV
+bDKT6tNHZaFFlCufVSjiL1EFZjRARC16OWYaDcElUsZYFMcsNIIa8LyDQaq6+SSm
+quhMO5heeJiYPrutDiJzbJr0+HoY77Ll+Q4/cEkl0UAN4Ovp18WKwaq6GpHAvBnv
+71LunLGAKsVb5joXBQ8In6zQkibJhgiBJwzLK90/j0OTiDaaOwM3PsAegORBVlVE
+TAk4AQmawmF8nBGLzTyKXl83J571ku1Mm2JTl16jMYziKARKXYBmkcP1at0YddVK
+WWpAwRKSxOucVJYfV58JqmjZqst8BBeH6esQKr5dklUvvDMaEwOCAQYAAoIBAQCw
+5mo+8/R3S9cNYg9o8JNJGdSbMhSkurILHh9WNElsIC3RNtPcpijmAnWtXTVDhe6w
+77wLj37tUuFGbsu2qPXtZoup35emf9DDshZ5w5UOclPaZ9HYjlC1H64c6d66Rllk
+fY6FRDv9qVfjT84APbvMDrk6csJ5YHxFPDaqeQaFB0nxFiCMVwjEx+ZSvQNK1jJ2
+o2gtuOvSPVSphsMeJ72DDNxO+SRRVnOmWaxg9rlmFuGle6Z+UJ2FItfmPEvhSBMY
+hzndUbC7Wi4sIpBzbm9O5MiPYMv0VmN+0t1156EiC9uR4f7AKH2S94dnQob/YeY0
+jMH+XxU/wzGUCmsOx1lx
+-----END PUBLIC KEY-----
diff --git a/resources.qrc b/resources.qrc
index d275428d3..bea3686be 100644
--- a/resources.qrc
+++ b/resources.qrc
@@ -141,5 +141,6 @@
data/images/process-stop.png
data/icons/tomahawk-icon-128x128-grayscale.png
data/images/collection.png
+ data/misc/tomahawk_pubkey.pem
diff --git a/src/AccountDelegate.cpp b/src/AccountDelegate.cpp
index 95e5ce418..6f6185708 100644
--- a/src/AccountDelegate.cpp
+++ b/src/AccountDelegate.cpp
@@ -734,6 +734,15 @@ AccountDelegate::doneInstalling ( const QPersistentModelIndex& idx )
}
+void
+AccountDelegate::errorInstalling( const QPersistentModelIndex& idx )
+{
+ // Just hide the loading spinner as we do after a successful install
+ qDebug() << "ERROR INSTALLING index:" << idx;
+ doneInstalling( idx );
+}
+
+
void
AccountDelegate::doUpdateIndex( const QPersistentModelIndex& idx )
{
diff --git a/src/AccountDelegate.h b/src/AccountDelegate.h
index 2028eb09b..82e59f4d2 100644
--- a/src/AccountDelegate.h
+++ b/src/AccountDelegate.h
@@ -43,6 +43,8 @@ public:
public slots:
void startInstalling( const QPersistentModelIndex& idx );
void doneInstalling ( const QPersistentModelIndex& idx );
+ void errorInstalling ( const QPersistentModelIndex& idx );
+
void doUpdateIndex( const QPersistentModelIndex& idx );
diff --git a/src/SettingsDialog.cpp b/src/SettingsDialog.cpp
index 3999121de..c9cc2546c 100644
--- a/src/SettingsDialog.cpp
+++ b/src/SettingsDialog.cpp
@@ -52,6 +52,7 @@
#include
#include "utils/Logger.h"
#include "AccountFactoryWrapper.h"
+#include "accounts/spotify/SpotifyAccount.h"
#include "ui_ProxyDialog.h"
#include "ui_StackedSettingsDialog.h"
@@ -97,7 +98,7 @@ SettingsDialog::SettingsDialog( QWidget *parent )
ui->enableProxyCheckBox->setChecked( useProxy );
ui->proxyButton->setEnabled( useProxy );
-
+
createIcons();
#ifdef Q_WS_X11
ui->listWidget->setFrameShape( QFrame::StyledPanel );
@@ -116,6 +117,13 @@ SettingsDialog::SettingsDialog( QWidget *parent )
m_proxySettings.setSizeGripEnabled( true );
QSizeGrip* p = m_proxySettings.findChild< QSizeGrip* >();
p->setFixedSize( 0, 0 );
+
+ ui->groupBoxNetworkAdvanced->layout()->removeItem( ui->verticalSpacer );
+ ui->groupBoxNetworkAdvanced->layout()->removeItem( ui->verticalSpacer_2 );
+ ui->groupBoxNetworkAdvanced->layout()->removeItem( ui->verticalSpacer_4 );
+ delete ui->verticalSpacer;
+ delete ui->verticalSpacer_2;
+ delete ui->verticalSpacer_4;
#endif
// Accounts
@@ -135,6 +143,7 @@ SettingsDialog::SettingsDialog( QWidget *parent )
connect( m_accountProxy, SIGNAL( startInstalling( QPersistentModelIndex ) ), accountDelegate, SLOT( startInstalling(QPersistentModelIndex) ) );
connect( m_accountProxy, SIGNAL( doneInstalling( QPersistentModelIndex ) ), accountDelegate, SLOT( doneInstalling(QPersistentModelIndex) ) );
+ connect( m_accountProxy, SIGNAL( errorInstalling( QPersistentModelIndex ) ), accountDelegate, SLOT( errorInstalling(QPersistentModelIndex) ) );
connect( m_accountProxy, SIGNAL( scrollTo( QModelIndex ) ), this, SLOT( scrollTo( QModelIndex ) ) );
ui->accountsView->setModel( m_accountProxy );
@@ -255,7 +264,7 @@ SettingsDialog::~SettingsDialog()
}
else
qDebug() << "Settings dialog cancelled, NOT saving prefs.";
-
+
delete ui;
}
@@ -450,15 +459,40 @@ SettingsDialog::installFromFile()
if( !resolver.isEmpty() )
{
+ const QFileInfo resolverAbsoluteFilePath( resolver );
+ TomahawkSettings::instance()->setScriptDefaultPath( resolverAbsoluteFilePath.absolutePath() );
+
+ if ( resolverAbsoluteFilePath.baseName() == "spotify_tomahawkresolver" )
+ {
+ // HACK if this is a spotify resolver, we treat is 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.
+ SpotifyAccount* acct = 0;
+ foreach ( Account* account, AccountManager::instance()->accounts() )
+ {
+ if ( SpotifyAccount* spotify = qobject_cast< SpotifyAccount* >( account ) )
+ {
+ acct = spotify;
+ break;
+ }
+ }
+
+ if ( acct )
+ {
+ acct->setManualResolverPath( resolver );
+ return;
+ }
+ }
+
Account* acct = AccountManager::instance()->accountFromPath( resolver );
AccountManager::instance()->addAccount( acct );
TomahawkSettings::instance()->addAccount( acct->accountId() );
AccountManager::instance()->enableAccount( acct );
-
-
- QFileInfo resolverAbsoluteFilePath( resolver );
- TomahawkSettings::instance()->setScriptDefaultPath( resolverAbsoluteFilePath.absolutePath() );
}
}
diff --git a/src/TomahawkApp.cpp b/src/TomahawkApp.cpp
index 53b09bda9..5938e60c5 100644
--- a/src/TomahawkApp.cpp
+++ b/src/TomahawkApp.cpp
@@ -290,6 +290,16 @@ TomahawkApp::init()
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
+ // with the default value of 3 in QApplicationPrivate::initialize.
+ {
+ QSettings qt_settings(QSettings::UserScope, "Trolltech");
+ qt_settings.beginGroup("Qt");
+ QApplication::setWheelScrollLines(
+ qt_settings.value("wheelScrollLines", QApplication::wheelScrollLines()).toInt());
+ }
+
#ifndef ENABLE_HEADLESS
// Make sure to init GAM in the gui thread
GlobalActionManager::instance();
diff --git a/src/accounts/spotify/SpotifyAccount.cpp b/src/accounts/spotify/SpotifyAccount.cpp
index a15047d44..3a740f551 100644
--- a/src/accounts/spotify/SpotifyAccount.cpp
+++ b/src/accounts/spotify/SpotifyAccount.cpp
@@ -26,6 +26,9 @@
#include "resolvers/ScriptResolver.h"
#include "utils/TomahawkUtils.h"
#include "ActionCollection.h"
+#include "Pipeline.h"
+#include "accounts/AccountManager.h"
+#include "utils/Closure.h"
#ifndef ENABLE_HEADLESS
#include "jobview/JobStatusView.h"
@@ -37,6 +40,7 @@
#include
#include
#include
+#include
using namespace Tomahawk;
using namespace Accounts;
@@ -44,6 +48,14 @@ using namespace Accounts;
static QPixmap* s_icon = 0;
+#ifdef Q_OS_MAC
+static QString s_resolverId = "spotify-osx";
+#elif defined(Q_OS_WIN)
+static QString s_resolverId = "spotify-win";
+#else
+static QString s_resolverId = "spotify-linux";
+#endif
+
Account*
SpotifyAccountFactory::createAccount( const QString& accountId )
{
@@ -51,21 +63,6 @@ SpotifyAccountFactory::createAccount( const QString& accountId )
}
-bool
-SpotifyAccountFactory::acceptsPath( const QString& path ) const
-{
- QFileInfo info( path );
- return info.baseName().startsWith( "spotify_" );
-}
-
-
-Account*
-SpotifyAccountFactory::createFromPath( const QString& path )
-{
- return new SpotifyAccount( generateId( factoryId() ), path );
-}
-
-
QPixmap
SpotifyAccountFactory::icon() const
{
@@ -77,14 +74,8 @@ SpotifyAccountFactory::icon() const
SpotifyAccount::SpotifyAccount( const QString& accountId )
- : ResolverAccount( accountId )
-{
- init();
-}
-
-
-SpotifyAccount::SpotifyAccount( const QString& accountId, const QString& path )
- : ResolverAccount( accountId, path )
+ : CustomAtticaAccount( accountId )
+ , m_preventEnabling( false )
{
init();
}
@@ -99,10 +90,60 @@ SpotifyAccount::~SpotifyAccount()
void
SpotifyAccount::init()
{
+ setAccountFriendlyName( "Spotify" );
+ setAccountServiceName( "spotify" );
+
+ if ( !AtticaManager::instance()->resolversLoaded() )
+ {
+ // If we're still waiting to load, wait for the attica resolvers to come down the pipe
+ connect( AtticaManager::instance(), SIGNAL( resolversLoaded( Attica::Content::List ) ), this, SLOT( init() ), Qt::UniqueConnection );
+ return;
+ }
+
qRegisterMetaType< Tomahawk::Accounts::SpotifyPlaylistInfo* >( "Tomahawk::Accounts::SpotifyPlaylist*" );
- m_spotifyResolver = dynamic_cast< ScriptResolver* >( m_resolver.data() );
+ AtticaManager::instance()->registerCustomAccount( s_resolverId, this );
+ connect( AtticaManager::instance(), SIGNAL( resolverInstalled( QString ) ), this, SLOT( resolverInstalled( QString ) ) );
+
+ const Attica::Content res = AtticaManager::instance()->resolverForId( s_resolverId );
+ const AtticaManager::ResolverState state = AtticaManager::instance()->resolverState( res );
+
+ const QString path = configuration().value( "path" ).toString(); // Manual path override
+ if ( !checkForResolver() && state != AtticaManager::Uninstalled )
+ {
+ // If the user manually deleted the resolver, mark it as uninstalled, so we re-fetch for the user
+ AtticaManager::instance()->uninstallResolver( res );
+ }
+ else if ( state == AtticaManager::Installed || !path.isEmpty() )
+ {
+ hookupResolver();
+ }
+}
+
+
+void
+SpotifyAccount::hookupResolver()
+{
+ // initialize the resolver itself. this is called if the account actually has an installed spotify resolver,
+ // as it might not.
+ // If there is a spotify resolver from attica installed, create the corresponding ExternalResolver* and hook up to it
+ QString path = configuration().value( "path" ).toString();
+ if ( path.isEmpty() )
+ {
+ const Attica::Content res = AtticaManager::instance()->resolverForId( s_resolverId );
+ const AtticaManager::ResolverState state = AtticaManager::instance()->resolverState( res );
+ Q_ASSERT( state == AtticaManager::Installed );
+ Q_UNUSED( state );
+
+ const AtticaManager::Resolver data = AtticaManager::instance()->resolverData( res.id() );
+ path = data.scriptPath;
+ }
+
+ qDebug() << "Starting spotify resolver with path:" << path;
+ m_spotifyResolver = QWeakPointer< ScriptResolver >( qobject_cast< ScriptResolver* >( Pipeline::instance()->addScriptResolver( path, enabled() ) ) );
+
+ connect( m_spotifyResolver.data(), SIGNAL( changed() ), this, SLOT( resolverChanged() ) );
connect( m_spotifyResolver.data(), SIGNAL( customMessage( QString,QVariantMap ) ), this, SLOT( resolverMessage( QString, QVariantMap ) ) );
const bool hasMigrated = configuration().value( "hasMigrated" ).toBool();
@@ -113,6 +154,151 @@ SpotifyAccount::init()
msg[ "_msgtype" ] = "getCredentials";
m_spotifyResolver.data()->sendMessage( msg );
}
+
+}
+
+
+bool SpotifyAccount::checkForResolver()
+{
+#ifdef Q_OS_MAC
+ const QDir path = QCoreApplication::applicationDirPath();
+ QFile file( path.absoluteFilePath( "spotify_tomahawkresolver" ) );
+ return file.exists();
+#endif
+
+ return false;
+}
+
+void
+SpotifyAccount::resolverChanged()
+{
+ emit connectionStateChanged( connectionState() );
+}
+
+
+Attica::Content
+SpotifyAccount::atticaContent() const
+{
+ return AtticaManager::instance()->resolverForId( s_resolverId );
+}
+
+
+void
+SpotifyAccount::authenticate()
+{
+ if ( !AtticaManager::instance()->resolversLoaded() )
+ {
+ // If we're still waiting to load, wait for the attica resolvers to come down the pipe
+ connect( AtticaManager::instance(), SIGNAL( resolversLoaded( Attica::Content::List ) ), this, SLOT( atticaLoaded( Attica::Content::List ) ), Qt::UniqueConnection );
+ return;
+ }
+
+ const Attica::Content res = AtticaManager::instance()->resolverForId( s_resolverId );
+ const AtticaManager::ResolverState state = AtticaManager::instance()->resolverState( res );
+
+ qDebug() << "Spotify account authenticating...";
+ if ( m_spotifyResolver.isNull() && state == AtticaManager::Installed )
+ {
+ // We don;t have the resolver but it has been installed via attica already, so lets just turn it on
+ hookupResolver();
+ }
+ else if ( m_spotifyResolver.isNull() )
+ {
+ qDebug() << "Got null resolver but asked to authenticate, so installing if we have one from attica:" << res.isValid() << res.id();
+ if ( res.isValid() && !res.id().isEmpty() )
+ AtticaManager::instance()->installResolver( res, false );
+ else
+ {
+#ifdef Q_OS_LINUX
+ m_preventEnabling = true;
+#endif
+ }
+ }
+ else if ( !m_spotifyResolver.data()->running() )
+ {
+ m_spotifyResolver.data()->start();
+ }
+
+ emit connectionStateChanged( connectionState() );
+}
+
+
+void
+SpotifyAccount::deauthenticate()
+{
+ if ( !m_spotifyResolver.isNull() && m_spotifyResolver.data()->running() )
+ m_spotifyResolver.data()->stop();
+
+ emit connectionStateChanged( connectionState() );
+}
+
+
+bool
+SpotifyAccount::isAuthenticated() const
+{
+ return !m_spotifyResolver.isNull() && m_spotifyResolver.data()->running();
+}
+
+
+Account::ConnectionState
+SpotifyAccount::connectionState() const
+{
+ return (!m_spotifyResolver.isNull() && m_spotifyResolver.data()->running()) ? Account::Connected : Account::Disconnected;
+}
+
+
+void
+SpotifyAccount::resolverInstalled(const QString& resolverId)
+{
+ if ( resolverId == s_resolverId )
+ {
+ // We requested this install, so we want to launch it
+ hookupResolver();
+ AccountManager::instance()->enableAccount( this );
+ }
+}
+
+
+void
+SpotifyAccount::atticaLoaded( Attica::Content::List )
+{
+ disconnect( AtticaManager::instance(), SIGNAL( resolversLoaded( Attica::Content::List ) ), this, SLOT( atticaLoaded( Attica::Content::List ) ) );
+ authenticate();
+}
+
+
+void
+SpotifyAccount::setManualResolverPath( const QString &resolverPath )
+{
+ Q_ASSERT( !resolverPath.isEmpty() );
+
+ QVariantHash conf = configuration();
+ conf[ "path" ] = resolverPath;
+ setConfiguration( conf );
+ sync();
+
+ m_preventEnabling = false;
+
+ if ( !m_spotifyResolver.isNull() )
+ {
+ // replace
+ NewClosure( m_spotifyResolver.data(), SIGNAL( destroyed() ), this, SLOT( hookupAfterDeletion( bool ) ), true );
+ m_spotifyResolver.data()->deleteLater();
+ }
+ else
+ {
+ hookupResolver();
+ AccountManager::instance()->enableAccount( this );
+ }
+}
+
+
+void
+SpotifyAccount::hookupAfterDeletion( bool autoEnable )
+{
+ hookupResolver();
+ if ( autoEnable )
+ AccountManager::instance()->enableAccount( this );
}
@@ -225,6 +411,7 @@ SpotifyAccount::resolverMessage( const QString &msgType, const QVariantMap &msg
if ( msgType == "credentials" )
{
QVariantHash creds = credentials();
+
creds[ "username" ] = msg.value( "username" );
creds[ "password" ] = msg.value( "password" );
creds[ "highQuality" ] = msg.value( "highQuality" );
@@ -449,6 +636,9 @@ SpotifyAccount::icon() const
QWidget*
SpotifyAccount::configurationWidget()
{
+ if ( m_spotifyResolver.isNull() )
+ return 0;
+
if ( m_configWidget.isNull() )
{
m_configWidget = QWeakPointer< SpotifyAccountConfig >( new SpotifyAccountConfig( this ) );
diff --git a/src/accounts/spotify/SpotifyAccount.h b/src/accounts/spotify/SpotifyAccount.h
index c3ee789d8..af05d0be6 100644
--- a/src/accounts/spotify/SpotifyAccount.h
+++ b/src/accounts/spotify/SpotifyAccount.h
@@ -22,6 +22,7 @@
#include "accounts/ResolverAccount.h"
#include "SourceList.h"
+#include "AtticaManager.h"
#include "Playlist.h"
#include "utils/TomahawkUtils.h"
#include "utils/SmartPointerList.h"
@@ -61,9 +62,6 @@ public:
virtual QString factoryId() const { return "spotifyaccount"; }
virtual QString prettyName() const { return "Spotify"; }
- virtual bool acceptsPath( const QString& path ) const;
- virtual Account* createFromPath( const QString& path );
-
virtual AccountTypes types() const { return AccountTypes( ResolverType ); }
virtual bool allowUserCreation() const { return false; }
virtual QPixmap icon() const;
@@ -71,7 +69,7 @@ public:
};
-class SpotifyAccount : public ResolverAccount
+class SpotifyAccount : public CustomAtticaAccount
{
Q_OBJECT
public:
@@ -83,10 +81,16 @@ public:
virtual QWidget* 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() { return Tomahawk::InfoSystem::InfoPluginPtr(); }
virtual SipPlugin* sipPlugin() { return 0; }
+ virtual bool preventEnabling() const { return m_preventEnabling; }
QString sendMessage( const QVariantMap& msg, QObject* receiver = 0, const QString& slot = QString() );
@@ -95,11 +99,17 @@ public:
bool deleteOnUnsync() const;
+ void setManualResolverPath( const QString& resolverPath );
+
public slots:
void aboutToShow( QAction* action, const Tomahawk::playlist_ptr& playlist );
void syncActionTriggered( bool );
+ void atticaLoaded(Attica::Content::List);
private slots:
+ void resolverChanged();
+ void resolverInstalled( const QString& resolverId );
+
void resolverMessage( const QString& msgType, const QVariantMap& msg );
void login( const QString& username, const QString& password );
@@ -108,8 +118,13 @@ private slots:
void startPlaylistSyncWithPlaylist( const QString& msgType, const QVariantMap& msg );
void playlistCreated( const QString& msgType, const QVariantMap& msg );
-private:
void init();
+ void hookupAfterDeletion( bool autoEnable );
+
+private:
+ bool checkForResolver();
+ void hookupResolver();
+
void loadPlaylists();
void clearUser( bool permanentlyDelete = false );
@@ -134,6 +149,8 @@ private:
QHash< QString, playlist_ptr > m_waitingForCreateReply;
+ bool m_preventEnabling;
+
SmartPointerList< QAction > m_customActions;
friend class ::SpotifyPlaylistUpdater;
};
diff --git a/src/libtomahawk/AtticaManager.cpp b/src/libtomahawk/AtticaManager.cpp
index 0dd491e8f..4dc80d2fc 100644
--- a/src/libtomahawk/AtticaManager.cpp
+++ b/src/libtomahawk/AtticaManager.cpp
@@ -23,9 +23,8 @@
#include "Pipeline.h"
#include
-#include
-#include
+#include
#include
#include
#include
@@ -40,13 +39,78 @@ using namespace Attica;
AtticaManager* AtticaManager::s_instance = 0;
+class BinaryInstallerHelper : public QObject
+{
+ Q_OBJECT
+public:
+ explicit BinaryInstallerHelper( const QString& resolverId, bool createAccount, AtticaManager* manager)
+ : QObject( manager )
+ , m_manager( QWeakPointer< AtticaManager >( manager ) )
+ , m_resolverId( resolverId )
+ , m_createAccount( createAccount )
+ {
+ Q_ASSERT( !m_resolverId.isEmpty() );
+ Q_ASSERT( !m_manager.isNull() );
+
+ setProperty( "resolverid", m_resolverId );
+ }
+
+ virtual ~BinaryInstallerHelper() {}
+
+public slots:
+ void installSucceeded( const QString& path )
+ {
+ qDebug() << Q_FUNC_INFO << "install of binary resolver succeeded, enabling: " << path;
+
+ if ( m_manager.isNull() )
+ return;
+
+ if ( m_createAccount )
+ {
+ Tomahawk::Accounts::Account* acct = Tomahawk::Accounts::AccountManager::instance()->accountFromPath( path );
+
+ Tomahawk::Accounts::AccountManager::instance()->addAccount( acct );
+ TomahawkSettings::instance()->addAccount( acct->accountId() );
+ Tomahawk::Accounts::AccountManager::instance()->enableAccount( acct );
+ }
+
+ m_manager.data()->m_resolverStates[ m_resolverId ].scriptPath = path;
+ m_manager.data()->m_resolverStates[ m_resolverId ].state = AtticaManager::Installed;
+
+ TomahawkSettingsGui::instanceGui()->setAtticaResolverStates( m_manager.data()->m_resolverStates );
+ emit m_manager.data()->resolverInstalled( m_resolverId );
+ emit m_manager.data()->resolverStateChanged( m_resolverId );
+
+ deleteLater();
+ }
+ void installFailed()
+ {
+ qDebug() << Q_FUNC_INFO << "install failed";
+
+ if ( m_manager.isNull() )
+ return;
+
+ m_manager.data()->resolverInstallationFailed( m_resolverId );
+
+ deleteLater();
+ }
+
+private:
+ QString m_resolverId;
+ bool m_createAccount;
+ QWeakPointer m_manager;
+};
+
+
AtticaManager::AtticaManager( QObject* parent )
: QObject( parent )
+ , m_resolverJobsLoaded( 0 )
{
connect( &m_manager, SIGNAL( providerAdded( Attica::Provider ) ), this, SLOT( providerAdded( Attica::Provider ) ) );
// resolvers
- m_manager.addProviderFile( QUrl( "http://bakery.tomahawk-player.org/resolvers/providers.xml" ) );
+ m_manager.addProviderFile( QUrl( "http://bakery.tomahawk-player.org/resolvers/providers.xml" ) );
+// m_manager.addProviderFile( QUrl( "http://lycophron/resolvers/providers.xml" ) );
qRegisterMetaType< Attica::Content >( "Attica::Content" );
}
@@ -215,11 +279,8 @@ AtticaManager::userHasRated( const Content& c ) const
bool
AtticaManager::hasCustomAccountForAttica( const QString &id ) const
{
- // Only last.fm at the moment contains a custom account
- if ( id == "lastfm" )
- return true;
-
- return false;
+ qDebug() << "Got custom account for?" << id << m_customAccounts.keys();
+ return m_customAccounts.keys().contains( id );
}
@@ -250,9 +311,32 @@ AtticaManager::providerAdded( const Provider& provider )
if ( provider.name() == "Tomahawk Resolvers" )
{
m_resolverProvider = provider;
+ m_resolvers.clear();
+
+ m_resolverStates = TomahawkSettingsGui::instanceGui()->atticaResolverStates();
+
+ ListJob* job = m_resolverProvider.requestCategories();
+ connect( job, SIGNAL( finished( Attica::BaseJob* ) ), this, SLOT( categoriesReturned( Attica::BaseJob* ) ) );
+ job->start();
+ }
+}
+
+
+void
+AtticaManager::categoriesReturned( BaseJob* j )
+{
+ ListJob< Category >* job = static_cast< ListJob< Category >* >( j );
+
+ Category::List categories = job->itemList();
+ foreach ( const Category& category, categories )
+ {
+ ListJob< Content >* job = m_resolverProvider.searchContents( Category::List() << category, QString(), Provider::Downloads, 0, 50 );
+
+ if ( category.name() == "Resolver" )
+ connect( job, SIGNAL( finished( Attica::BaseJob* ) ), this, SLOT( resolversList( Attica::BaseJob* ) ) );
+ else if ( category.name() == "BinaryResolver" )
+ connect( job, SIGNAL( finished( Attica::BaseJob* ) ), this, SLOT( binaryResolversList( Attica::BaseJob* ) ) );
- ListJob< Content >* job = m_resolverProvider.searchContents( Category::List(), QString(), Provider::Downloads, 0, 30 );
- connect( job, SIGNAL( finished( Attica::BaseJob* ) ), this, SLOT( resolversList( Attica::BaseJob* ) ) );
job->start();
}
}
@@ -263,8 +347,7 @@ AtticaManager::resolversList( BaseJob* j )
{
ListJob< Content >* job = static_cast< ListJob< Content >* >( j );
- m_resolvers = job->itemList();
- m_resolverStates = TomahawkSettingsGui::instanceGui()->atticaResolverStates();
+ m_resolvers.append( job->itemList() );
// Sanity check. if any resolvers are installed that don't exist on the hd, remove them.
foreach ( const QString& rId, m_resolverStates.keys() )
@@ -272,6 +355,9 @@ AtticaManager::resolversList( BaseJob* j )
if ( m_resolverStates[ rId ].state == Installed ||
m_resolverStates[ rId ].state == NeedsUpgrade )
{
+ if ( m_resolverStates[ rId ].binary )
+ continue;
+
// Guess location on disk
QDir dir( QString( "%1/atticaresolvers/%2" ).arg( TomahawkUtils::appDataDir().absolutePath() ).arg( rId ) );
if ( !dir.exists() )
@@ -303,7 +389,50 @@ AtticaManager::resolversList( BaseJob* j )
syncServerData();
- emit resolversLoaded( m_resolvers );
+ if ( ++m_resolverJobsLoaded == 2 )
+ emit resolversLoaded( m_resolvers );
+}
+
+
+void
+AtticaManager::binaryResolversList( BaseJob* j )
+{
+ ListJob< Content >* job = static_cast< ListJob< Content >* >( j );
+
+ Content::List binaryResolvers = job->itemList();
+
+ // NOTE: No binary support for linux distros
+ QString platform;
+#ifdef Q_OS_MAC
+ platform = "osx";
+#elif Q_OS_WIN
+ platform = "win";
+#endif
+
+ foreach ( const Content& c, binaryResolvers )
+ {
+ if ( !c.attribute( "typeid" ).isEmpty() && c.attribute( "typeid" ) == platform )
+ {
+ // We have a binary resolver for this platform
+ qDebug() << "WE GOT A BINARY RESOLVER:" << c.id() << c.name() << c.attribute( "signature" );
+ m_resolvers.append( c );
+ if ( !m_resolverStates.contains( c.id() ) )
+ {
+ Resolver r;
+ r.binary = true;
+ m_resolverStates.insert( c.id(), r );
+ }
+ else if ( m_resolverStates[ c.id() ].binary != true )
+ { // HACK workaround... why is this not set in the first place sometimes? Migration issue?
+ m_resolverStates[ c.id() ].binary = true;
+ }
+
+
+ }
+ }
+
+ if ( ++m_resolverJobsLoaded == 2 )
+ emit resolversLoaded( m_resolvers );
}
@@ -382,6 +511,7 @@ AtticaManager::installResolver( const Content& resolver, bool autoCreateAccount
connect( job, SIGNAL( finished( Attica::BaseJob* ) ), this, SLOT( resolverDownloadFinished( Attica::BaseJob* ) ) );
job->setProperty( "resolverId", resolver.id() );
job->setProperty( "createAccount", autoCreateAccount );
+ job->setProperty( "binarySignature", resolver.attribute("signature"));
job->start();
}
@@ -418,6 +548,7 @@ AtticaManager::resolverDownloadFinished ( BaseJob* j )
connect( reply, SIGNAL( finished() ), this, SLOT( payloadFetched() ) );
reply->setProperty( "resolverId", job->property( "resolverId" ) );
reply->setProperty( "createAccount", job->property( "createAccount" ) );
+ reply->setProperty( "binarySignature", job->property( "binarySignature" ) );
}
else
{
@@ -432,6 +563,9 @@ AtticaManager::payloadFetched()
QNetworkReply* reply = qobject_cast< QNetworkReply* >( sender() );
Q_ASSERT( reply );
+ bool installedSuccessfully = false;
+ const QString resolverId = reply->property( "resolverId" ).toString();
+
// we got a zip file, save it to a temporary file, then unzip it to our destination data dir
if ( reply->error() == QNetworkReply::NoError )
{
@@ -444,102 +578,64 @@ AtticaManager::payloadFetched()
f.write( reply->readAll() );
f.close();
- QString resolverId = reply->property( "resolverId" ).toString();
- QDir dir( extractPayload( f.fileName(), resolverId ) );
- QString resolverPath = dir.absoluteFilePath( m_resolverStates[ resolverId ].scriptPath );
-
- if ( !resolverPath.isEmpty() )
+ if ( m_resolverStates[ resolverId ].binary )
{
- // update with absolute, not relative, path
- m_resolverStates[ resolverId ].scriptPath = resolverPath;
-
- if ( reply->property( "createAccount" ).toBool() )
+ // First ensure the signature matches. If we can't verify it, abort!
+ const QString signature = reply->property( "binarySignature" ).toString();
+ // Must have a signature for binary resolvers...
+ Q_ASSERT( !signature.isEmpty() );
+ if ( signature.isEmpty() )
+ return;
+ if ( !TomahawkUtils::verifyFile( f.fileName(), signature ) )
{
- // Do the install / add to tomahawk
- Tomahawk::Accounts::Account* resolver = Tomahawk::Accounts::ResolverAccountFactory::createFromPath( resolverPath, "resolveraccount", true );
- Tomahawk::Accounts::AccountManager::instance()->addAccount( resolver );
- TomahawkSettings::instance()->addAccount( resolver->accountId() );
+ qWarning() << "FILE SIGNATURE FAILED FOR BINARY RESOLVER! WARNING! :" << f.fileName() << signature;
}
+ else
+ {
+ TomahawkUtils::extractBinaryResolver( f.fileName(), new BinaryInstallerHelper( resolverId, reply->property( "createAccount" ).toBool(), this ) );
+ // Don't emit failed yet
+ installedSuccessfully = true;
+ }
+ }
+ else
+ {
+ QDir dir( TomahawkUtils::extractScriptPayload( f.fileName(), resolverId ) );
+ QString resolverPath = dir.absoluteFilePath( m_resolverStates[ resolverId ].scriptPath );
- m_resolverStates[ resolverId ].state = Installed;
- TomahawkSettingsGui::instanceGui()->setAtticaResolverStates( m_resolverStates );
- emit resolverInstalled( resolverId );
- emit resolverStateChanged( resolverId );
+ if ( !resolverPath.isEmpty() )
+ {
+ // update with absolute, not relative, path
+ m_resolverStates[ resolverId ].scriptPath = resolverPath;
+
+ if ( reply->property( "createAccount" ).toBool() )
+ {
+ // Do the install / add to tomahawk
+ Tomahawk::Accounts::Account* resolver = Tomahawk::Accounts::ResolverAccountFactory::createFromPath( resolverPath, "resolveraccount", true );
+ Tomahawk::Accounts::AccountManager::instance()->addAccount( resolver );
+ TomahawkSettings::instance()->addAccount( resolver->accountId() );
+ }
+
+ installedSuccessfully = true;
+ }
}
}
else
{
tLog() << "Failed to download attica payload...:" << reply->errorString();
}
-}
-QString
-AtticaManager::extractPayload( const QString& filename, const QString& resolverId ) const
-{
- // uses QuaZip to extract the temporary zip file to the user's tomahawk data/resolvers directory
- QuaZip zipFile( filename );
- if ( !zipFile.open( QuaZip::mdUnzip ) )
+ if ( installedSuccessfully )
{
- tLog() << "Failed to QuaZip open:" << zipFile.getZipError();
- return QString();
+ m_resolverStates[ resolverId ].state = Installed;
+ TomahawkSettingsGui::instanceGui()->setAtticaResolverStates( m_resolverStates );
+ emit resolverInstalled( resolverId );
+ emit resolverStateChanged( resolverId );
}
-
- if ( !zipFile.goToFirstFile() )
+ else
{
- tLog() << "Failed to go to first file in zip archive: " << zipFile.getZipError();
- return QString();
+ emit resolverInstallationFailed( resolverId );
}
-
- QDir resolverDir = TomahawkUtils::appDataDir();
- if ( !resolverDir.mkpath( QString( "atticaresolvers/%1" ).arg( resolverId ) ) )
- {
- tLog() << "Failed to mkdir resolver save dir: " << TomahawkUtils::appDataDir().absoluteFilePath( QString( "atticaresolvers/%1" ).arg( resolverId ) );
- return QString();
- }
- resolverDir.cd( QString( "atticaresolvers/%1" ).arg( resolverId ) );
- tDebug() << "Installing resolver to:" << resolverDir.absolutePath();
-
- QuaZipFile fileInZip( &zipFile );
- do
- {
- QuaZipFileInfo info;
- zipFile.getCurrentFileInfo( &info );
-
- if ( !fileInZip.open( QIODevice::ReadOnly ) )
- {
- tLog() << "Failed to open file inside zip archive:" << info.name << zipFile.getZipName() << "with error:" << zipFile.getZipError();
- continue;
- }
-
- QFile out( resolverDir.absoluteFilePath( fileInZip.getActualFileName() ) );
-
- QStringList parts = fileInZip.getActualFileName().split( "/" );
- if ( parts.size() > 1 )
- {
- QStringList dirs = parts.mid( 0, parts.size() - 1 );
- QString dirPath = dirs.join( "/" ); // QDir translates / to \ internally if necessary
- resolverDir.mkpath( dirPath );
- }
-
- // make dir if there is one needed
- QDir d( fileInZip.getActualFileName() );
-
- tDebug() << "Writing to output file..." << out.fileName();
- if ( !out.open( QIODevice::WriteOnly ) )
- {
- tLog() << "Failed to open resolver extract file:" << out.errorString() << info.name;
- continue;
- }
-
-
- out.write( fileInZip.readAll() );
- out.close();
- fileInZip.close();
-
- } while ( zipFile.goToNextFile() );
-
- return resolverDir.absolutePath();
}
@@ -617,3 +713,5 @@ AtticaManager::doResolverRemove( const QString& id ) const
TomahawkUtils::removeDirectory( resolverDir.absolutePath() );
}
+
+#include "AtticaManager.moc"
diff --git a/src/libtomahawk/AtticaManager.h b/src/libtomahawk/AtticaManager.h
index 27bcacda6..fecb2d27f 100644
--- a/src/libtomahawk/AtticaManager.h
+++ b/src/libtomahawk/AtticaManager.h
@@ -32,6 +32,7 @@
#include
#include
+class BinaryInstallerHelper;
class DLLEXPORT AtticaManager : public QObject
{
@@ -51,13 +52,14 @@ public:
int userRating; // 0-100
ResolverState state;
QPixmap* pixmap;
+ bool binary;
// internal
bool pixmapDirty;
- Resolver( const QString& v, const QString& path, int userR, ResolverState s )
- : version( v ), scriptPath( path ), userRating( userR ), state( s ), pixmap( 0 ), pixmapDirty( false ) {}
- Resolver() : userRating( -1 ), state( Uninstalled ), pixmap( 0 ), pixmapDirty( false ) {}
+ Resolver( const QString& v, const QString& path, int userR, ResolverState s, bool resolver )
+ : version( v ), scriptPath( path ), userRating( userR ), state( s ), pixmap( 0 ), binary( false ), pixmapDirty( false ) {}
+ Resolver() : userRating( -1 ), state( Uninstalled ), pixmap( 0 ), binary( false ), pixmapDirty( false ) {}
};
typedef QHash< QString, AtticaManager::Resolver > StateHash;
@@ -90,7 +92,7 @@ public:
/**
If the resolver coming from libattica has a native custom c++ account
- as well. For example the last.fm account.
+ as well. For example the last.fm & spotify accounts.
*/
bool hasCustomAccountForAttica( const QString& id ) const;
Tomahawk::Accounts::Account* customAccountForAttica( const QString& id ) const;
@@ -108,10 +110,13 @@ signals:
void resolverStateChanged( const QString& resolverId );
void resolverInstalled( const QString& resolverId );
void resolverUninstalled( const QString& resolverId );
+ void resolverInstallationFailed( const QString& resolverId );
private slots:
void providerAdded( const Attica::Provider& );
+ void categoriesReturned( Attica::BaseJob* );
void resolversList( Attica::BaseJob* );
+ void binaryResolversList( Attica::BaseJob* );
void resolverDownloadFinished( Attica::BaseJob* );
void payloadFetched();
@@ -131,9 +136,12 @@ private:
Attica::Content::List m_resolvers;
StateHash m_resolverStates;
+ int m_resolverJobsLoaded;
QMap< QString, Tomahawk::Accounts::Account* > m_customAccounts;
static AtticaManager* s_instance;
+
+ friend class ::BinaryInstallerHelper;
};
class DLLEXPORT CustomAtticaAccount : public Tomahawk::Accounts::Account
diff --git a/src/libtomahawk/CMakeLists.txt b/src/libtomahawk/CMakeLists.txt
index bf6d9f624..501ef3188 100644
--- a/src/libtomahawk/CMakeLists.txt
+++ b/src/libtomahawk/CMakeLists.txt
@@ -370,6 +370,7 @@ IF( APPLE )
SET( libSources ${libSources}
utils/TomahawkUtils_Mac.mm
+ mac/FileHelpers.mm
thirdparty/Qocoa/qsearchfield_mac.mm )
SET_SOURCE_FILES_PROPERTIES(utils/TomahawkUtils_Mac.mm PROPERTIES COMPILE_FLAGS "-fvisibility=default")
@@ -379,10 +380,11 @@ IF( APPLE )
# System
${COREAUDIO_LIBRARY}
${COREFOUNDATION_LIBRARY}
- ${FOUNDATION_LIBRARY}
+ ${FOUNDATION_LIBRARY}
${SCRIPTINGBRIDGE_LIBRARY}
/System/Library/Frameworks/AppKit.framework
+ /System/Library/Frameworks/Security.framework
)
ELSE( APPLE )
SET( libGuiSources ${libGuiSources} thirdparty/Qocoa/qsearchfield.cpp )
diff --git a/src/libtomahawk/TomahawkSettings.cpp b/src/libtomahawk/TomahawkSettings.cpp
index 3f68f5f99..cb563ec7a 100644
--- a/src/libtomahawk/TomahawkSettings.cpp
+++ b/src/libtomahawk/TomahawkSettings.cpp
@@ -127,6 +127,21 @@ TomahawkSettings::TomahawkSettings( QObject* parent )
// insert upgrade code here as required
setValue( "configversion", TOMAHAWK_SETTINGS_VERSION );
}
+
+ // Ensure last.fm and spotify accounts always exist
+ QString spotifyAcct, lastfmAcct;
+ foreach ( const QString& acct, value( "accounts/allaccounts" ).toStringList() )
+ {
+ if ( acct.startsWith( "lastfmaccount_" ) )
+ lastfmAcct = acct;
+ else if ( acct.startsWith( "spotifyaccount_" ) )
+ spotifyAcct = acct;
+ }
+
+ if ( spotifyAcct.isEmpty() )
+ createSpotifyAccount();
+ if ( lastfmAcct.isEmpty() )
+ createLastFmAccount();
}
@@ -142,6 +157,15 @@ TomahawkSettings::doInitialSetup()
// by default we add a local network resolver
addAccount( "sipzeroconf_autocreated" );
+
+ createLastFmAccount();
+ createSpotifyAccount();
+}
+
+
+void
+TomahawkSettings::createLastFmAccount()
+{
// Add a last.fm account for scrobbling and infosystem
const QString accountKey = QString( "lastfmaccount_%1" ).arg( QUuid::createUuid().toString().mid( 1, 8 ) );
addAccount( accountKey );
@@ -151,6 +175,27 @@ TomahawkSettings::doInitialSetup()
setValue( "autoconnect", true );
setValue( "types", QStringList() << "ResolverType" << "StatusPushType" );
endGroup();
+
+ QStringList allAccounts = value( "accounts/allaccounts" ).toStringList();
+ allAccounts << accountKey;
+ setValue( "accounts/allaccounts", allAccounts );
+}
+
+
+void
+TomahawkSettings::createSpotifyAccount()
+{
+ const QString accountKey = QString( "spotifyaccount_%1" ).arg( QUuid::createUuid().toString().mid( 1, 8 ) );
+ beginGroup( "accounts/" + accountKey );
+ setValue( "enabled", false );
+ setValue( "types", QStringList() << "ResolverType" );
+ setValue( "credentials", QVariantHash() );
+ setValue( "configuration", QVariantHash() );
+ endGroup();
+
+ QStringList allAccounts = value( "accounts/allaccounts" ).toStringList();
+ allAccounts << accountKey;
+ setValue( "accounts/allaccounts", allAccounts );
}
@@ -516,6 +561,34 @@ TomahawkSettings::doUpgrade( int oldVersion, int newVersion )
remove( "playlistupdaters" );
}
+ else if ( oldVersion == 11 )
+ {
+ // If the user doesn't have a spotify account, create one, since now it
+ // is like the last.fm account and always exists
+ QStringList allAccounts = value( "accounts/allaccounts" ).toStringList();
+ QString acct;
+ foreach ( const QString& account, allAccounts )
+ {
+ if ( account.startsWith( "spotifyaccount_" ) )
+ {
+ acct = account;
+ break;
+ }
+ }
+
+ if ( !acct.isEmpty() )
+ {
+ beginGroup( "accounts/" + acct );
+ QVariantHash conf = value( "configuration" ).toHash();
+ foreach ( const QString& key, conf.keys() )
+ qDebug() << key << conf[ key ].toString();
+ endGroup();
+ }
+ else
+ {
+ createSpotifyAccount();
+ }
+ }
}
diff --git a/src/libtomahawk/TomahawkSettings.h b/src/libtomahawk/TomahawkSettings.h
index e7c6156e7..717da384d 100644
--- a/src/libtomahawk/TomahawkSettings.h
+++ b/src/libtomahawk/TomahawkSettings.h
@@ -30,7 +30,7 @@
#include "DllMacro.h"
-#define TOMAHAWK_SETTINGS_VERSION 10
+#define TOMAHAWK_SETTINGS_VERSION 12
/**
* Convenience wrapper around QSettings for tomahawk-specific config
@@ -214,6 +214,8 @@ private slots:
private:
void doInitialSetup();
+ void createLastFmAccount();
+ void createSpotifyAccount();
void doUpgrade( int oldVersion, int newVersion );
static TomahawkSettings* s_instance;
diff --git a/src/libtomahawk/TomahawkSettingsGui.cpp b/src/libtomahawk/TomahawkSettingsGui.cpp
index dea0c5236..8aa15b3c2 100644
--- a/src/libtomahawk/TomahawkSettingsGui.cpp
+++ b/src/libtomahawk/TomahawkSettingsGui.cpp
@@ -30,7 +30,7 @@ inline QDataStream& operator<<(QDataStream& out, const AtticaManager::StateHash&
foreach( const QString& key, states.keys() )
{
AtticaManager::Resolver resolver = states[ key ];
- out << key << resolver.version << resolver.scriptPath << (qint32)resolver.state << resolver.userRating;
+ out << key << resolver.version << resolver.scriptPath << (qint32)resolver.state << resolver.userRating << resolver.binary;
}
return out;
}
@@ -38,19 +38,25 @@ inline QDataStream& operator<<(QDataStream& out, const AtticaManager::StateHash&
inline QDataStream& operator>>(QDataStream& in, AtticaManager::StateHash& states)
{
- quint32 count = 0, version = 0;
- in >> version;
+ quint32 count = 0, configVersion = 0;
+ in >> configVersion;
in >> count;
for ( uint i = 0; i < count; i++ )
{
QString key, version, scriptPath;
qint32 state, userRating;
+ bool binary = false;
in >> key;
in >> version;
in >> scriptPath;
in >> state;
in >> userRating;
- states[ key ] = AtticaManager::Resolver( version, scriptPath, userRating, (AtticaManager::ResolverState)state );
+ if ( configVersion > 10 )
+ {
+ // V11 includes 'bool binary' flag
+ in >> binary;
+ }
+ states[ key ] = AtticaManager::Resolver( version, scriptPath, userRating, (AtticaManager::ResolverState)state, binary );
}
return in;
}
diff --git a/src/libtomahawk/accounts/Account.h b/src/libtomahawk/accounts/Account.h
index 98afb3750..48aecefa6 100644
--- a/src/libtomahawk/accounts/Account.h
+++ b/src/libtomahawk/accounts/Account.h
@@ -102,6 +102,10 @@ public:
virtual Tomahawk::InfoSystem::InfoPluginPtr infoPlugin() = 0;
virtual SipPlugin* sipPlugin() = 0;
+ // Some accounts cannot be enabled if authentication fails. Return true after failing to authenticate
+ // if this is the case, and the account will not be enabled
+ virtual bool preventEnabling() const { return false; }
+
AccountTypes types() const;
void setAccountServiceName( const QString &serviceName ) { QMutexLocker locker( &m_mutex ); m_accountServiceName = serviceName; }
diff --git a/src/libtomahawk/accounts/AccountManager.cpp b/src/libtomahawk/accounts/AccountManager.cpp
index 0c9e3d190..1bacf9c30 100644
--- a/src/libtomahawk/accounts/AccountManager.cpp
+++ b/src/libtomahawk/accounts/AccountManager.cpp
@@ -202,6 +202,9 @@ AccountManager::enableAccount( Account* account )
account->authenticate();
+ if ( account->preventEnabling() )
+ return;
+
account->setEnabled( true );
m_enabledAccounts << account;
diff --git a/src/libtomahawk/accounts/AccountModel.cpp b/src/libtomahawk/accounts/AccountModel.cpp
index f58014435..677e9c3c6 100644
--- a/src/libtomahawk/accounts/AccountModel.cpp
+++ b/src/libtomahawk/accounts/AccountModel.cpp
@@ -24,6 +24,10 @@
#include "AtticaManager.h"
#include "ResolverAccount.h"
+#ifndef ENABLE_HEADLESS
+#include
+#endif
+
#include
using namespace Tomahawk;
@@ -35,6 +39,7 @@ AccountModel::AccountModel( QObject* parent )
: QAbstractListModel( parent )
{
connect( AtticaManager::instance(), SIGNAL( resolversLoaded( Attica::Content::List ) ), this, SLOT( loadData() ) );
+ connect( AtticaManager::instance(), SIGNAL( resolverInstallationFailed( QString ) ), this, SLOT( resolverInstallFailed( QString ) ) );
connect( AccountManager::instance(), SIGNAL( added( Tomahawk::Accounts::Account* ) ), this, SLOT( accountAdded( Tomahawk::Accounts::Account* ) ) );
connect( AccountManager::instance(), SIGNAL( removed( Tomahawk::Accounts::Account* ) ), this, SLOT( accountRemoved( Tomahawk::Accounts::Account* ) ) );
@@ -491,6 +496,21 @@ AccountModel::setData( const QModelIndex& index, const QVariant& value, int role
else if( state == Qt::Unchecked )
AccountManager::instance()->disableAccount( acct );
+#if defined(Q_OS_LINUX) && !defined(ENABLE_HEADLESS)
+ if ( acct->preventEnabling() )
+ {
+ // Can't install from attica yet on linux, so show a warning if the user tries to turn it on.
+ // TODO make a prettier display
+ QMessageBox box;
+ box.setWindowTitle( tr( "Manual Install Required" ) );
+ box.setTextFormat( Qt::RichText );
+ box.setIcon( QMessageBox::Information );
+ box.setText( tr( "Unfortunately, automatic installation of this resolver is not yet available on Linux.
"
+ "Please use \"Install from file\" above, by fetching it from your distribution or compiling it yourself. Further instructions can be found here:
http://www.tomahawk-player.org/resolvers/%1" ).arg( acct->accountServiceName() ) );
+ box.setStandardButtons( QMessageBox::Ok );
+ box.exec();
+ }
+#endif
emit dataChanged( index, index );
return true;
@@ -615,7 +635,8 @@ AccountModel::accountStateChanged( Account* account , Account::ConnectionState )
// For each type that this node could be, check the corresponding data
if ( ( n->type == AccountModelNode::UniqueFactoryType && n->accounts.size() && n->accounts.first() == account ) ||
( n->type == AccountModelNode::AtticaType && n->atticaAccount && n->atticaAccount == account ) ||
- ( n->type == AccountModelNode::ManualResolverType && n->resolverAccount && n->resolverAccount == account ) )
+ ( n->type == AccountModelNode::ManualResolverType && n->resolverAccount && n->resolverAccount == account ) ||
+ ( n->type == AccountModelNode::CustomAccountType && n->customAccount && n->customAccount == account ) )
{
const QModelIndex idx = index( i, 0, QModelIndex() );
emit dataChanged( idx, idx );
@@ -689,6 +710,22 @@ AccountModel::accountRemoved( Account* account )
}
+void
+AccountModel::resolverInstallFailed( const QString& resolverId )
+{
+ for ( int i = 0; i < m_accounts.size(); i++ )
+ {
+ if ( m_accounts[ i ]->type == AccountModelNode::AtticaType && m_accounts[ i ]->atticaContent.id() == resolverId )
+ {
+ qDebug() << "Got failed attica install in account mode, emitting signal!";
+ emit errorInstalling( index( i, 0, QModelIndex() ) );
+
+ return;
+ }
+ }
+}
+
+
int
AccountModel::rowCount( const QModelIndex& ) const
{
diff --git a/src/libtomahawk/accounts/AccountModel.h b/src/libtomahawk/accounts/AccountModel.h
index 68fcb89f6..e383b6ec6 100644
--- a/src/libtomahawk/accounts/AccountModel.h
+++ b/src/libtomahawk/accounts/AccountModel.h
@@ -96,6 +96,8 @@ signals:
void startInstalling( const QPersistentModelIndex& idx );
void doneInstalling( const QPersistentModelIndex& idx );
+ void errorInstalling( const QPersistentModelIndex& idx );
+
private slots:
void loadData();
@@ -103,6 +105,7 @@ private slots:
void accountRemoved( Tomahawk::Accounts::Account* );
void accountStateChanged( Account*, Accounts::Account::ConnectionState );
+ void resolverInstallFailed( const QString& resolverId );
private:
QList< AccountModelNode* > m_accounts;
QSet< QString > m_waitingForAtticaInstall;
diff --git a/src/libtomahawk/accounts/AccountModelFilterProxy.cpp b/src/libtomahawk/accounts/AccountModelFilterProxy.cpp
index 1a559faed..fee5516f3 100644
--- a/src/libtomahawk/accounts/AccountModelFilterProxy.cpp
+++ b/src/libtomahawk/accounts/AccountModelFilterProxy.cpp
@@ -38,6 +38,7 @@ AccountModelFilterProxy::setSourceModel( QAbstractItemModel* sourceModel )
connect( sourceModel, SIGNAL( scrollTo( QModelIndex ) ), this, SLOT( onScrollTo( QModelIndex ) ) );
connect( sourceModel, SIGNAL( startInstalling( QPersistentModelIndex ) ), this, SLOT( onStartInstalling( QPersistentModelIndex ) ) );
connect( sourceModel, SIGNAL( doneInstalling( QPersistentModelIndex ) ), this, SLOT( onDoneInstalling( QPersistentModelIndex ) ) );
+ connect( sourceModel, SIGNAL( errorInstalling( QPersistentModelIndex ) ), this, SLOT( onErrorInstalling( QPersistentModelIndex ) ) );
QSortFilterProxyModel::setSourceModel( sourceModel );
}
@@ -86,3 +87,11 @@ AccountModelFilterProxy::onStartInstalling( const QPersistentModelIndex& idx )
{
emit startInstalling( mapFromSource( idx ) );
}
+
+
+void
+AccountModelFilterProxy::onErrorInstalling( const QPersistentModelIndex& idx )
+{
+ emit errorInstalling( mapFromSource( idx ) );
+}
+
diff --git a/src/libtomahawk/accounts/AccountModelFilterProxy.h b/src/libtomahawk/accounts/AccountModelFilterProxy.h
index 75021c6b1..2a8868fd8 100644
--- a/src/libtomahawk/accounts/AccountModelFilterProxy.h
+++ b/src/libtomahawk/accounts/AccountModelFilterProxy.h
@@ -42,6 +42,7 @@ signals:
void startInstalling( const QPersistentModelIndex& idx );
void doneInstalling( const QPersistentModelIndex& idx );
+ void errorInstalling( const QPersistentModelIndex& idx );
protected:
virtual bool filterAcceptsRow ( int sourceRow, const QModelIndex& sourceParent ) const;
@@ -51,6 +52,7 @@ private slots:
void onStartInstalling( const QPersistentModelIndex& idx );
void onDoneInstalling( const QPersistentModelIndex& idx );
+ void onErrorInstalling( const QPersistentModelIndex& idx );
private:
Tomahawk::Accounts::AccountType m_filterType;
diff --git a/src/libtomahawk/accounts/ResolverAccount.h b/src/libtomahawk/accounts/ResolverAccount.h
index 03bd46fc5..bcf167640 100644
--- a/src/libtomahawk/accounts/ResolverAccount.h
+++ b/src/libtomahawk/accounts/ResolverAccount.h
@@ -99,7 +99,7 @@ protected:
* Extends ResolverAccount with what attica additionally provides---e.g. icon
* Assumes certain file layout on disk.
*/
-class AtticaResolverAccount : public ResolverAccount
+class DLLEXPORT AtticaResolverAccount : public ResolverAccount
{
Q_OBJECT
public:
diff --git a/src/libtomahawk/database/DatabaseCollection.cpp b/src/libtomahawk/database/DatabaseCollection.cpp
index cb864cc66..2dca1980d 100644
--- a/src/libtomahawk/database/DatabaseCollection.cpp
+++ b/src/libtomahawk/database/DatabaseCollection.cpp
@@ -1,6 +1,7 @@
/* === This file is part of Tomahawk Player - ===
*
* Copyright 2010-2011, Christian Muehlhaeuser
+ * Copyright 2010-2011, Leo Franchi
*
* Tomahawk is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
diff --git a/src/libtomahawk/database/DatabaseCollection.h b/src/libtomahawk/database/DatabaseCollection.h
index a9e9fae65..de94bbd6c 100644
--- a/src/libtomahawk/database/DatabaseCollection.h
+++ b/src/libtomahawk/database/DatabaseCollection.h
@@ -1,6 +1,7 @@
/* === This file is part of Tomahawk Player - ===
*
* Copyright 2010-2011, Christian Muehlhaeuser
+ * Copyright 2010-2011, Leo Franchi
*
* Tomahawk is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
diff --git a/src/libtomahawk/mac/FileHelpers.h b/src/libtomahawk/mac/FileHelpers.h
new file mode 100644
index 000000000..1a25cd97d
--- /dev/null
+++ b/src/libtomahawk/mac/FileHelpers.h
@@ -0,0 +1,40 @@
+/* === This file is part of Tomahawk Player - ===
+ *
+ * Copyright 2012, Leo Franchi .
+ */
+
+#ifndef MAC_FILE_HELPERS_H
+#define MAC_FILE_HELPERS_H
+
+#import
+#import
+
+// Implement this delegate protocol to get notified about the result of your copy attempt
+@interface NSObject (SUInstallerDelegateInformalProtocol)
+- (void)moveFinished;
+- (void)moveFailedWithError:(NSError *)error;
+@end
+
+@interface FileHelpers : NSObject
+{}
+// Move a file from point A to point B, asking for authentication if necessary
+// Will be asynchronous: Implement the delegate protocol know about the completion
++ (void) moveFile:(NSString *)source to:(NSString*)dest withDelegate:delegate;
+
+
+@end
+
+#endif
diff --git a/src/libtomahawk/mac/FileHelpers.mm b/src/libtomahawk/mac/FileHelpers.mm
new file mode 100644
index 000000000..97ee3c4ed
--- /dev/null
+++ b/src/libtomahawk/mac/FileHelpers.mm
@@ -0,0 +1,226 @@
+/* === This file is part of Tomahawk Player - ===
+ *
+ * Copyright 2012, Leo Franchi .
+ */
+
+#import "FileHelpers.h"
+
+#import
+#import
+#import
+#import
+#import
+#import
+#import
+
+#include
+
+static NSString * const TKCopySourceKey = @"TKInstallerSourcePath";
+static NSString * const TKCopyDestinationKey = @"TKInstallerDestinationPath";
+static NSString * const TKInstallerDelegateKey = @"TKInstallerDelegate";
+static NSString * const TKInstallerResultKey = @"TKInstallerResult";
+static NSString * const TKInstallerErrorKey = @"TKInstallerError";
+
+class CAutoreleasePool
+{
+ NSAutoreleasePool *pool;
+
+public:
+ CAutoreleasePool()
+ {
+ pool = [[NSAutoreleasePool alloc] init];
+ }
+
+ ~CAutoreleasePool()
+ {
+ [pool drain];
+ }
+};
+
+// Authorization code based on generous contribution from Allan Odgaard. Thanks, Allan!
+static BOOL AuthorizationExecuteWithPrivilegesAndWait(AuthorizationRef authorization, const char* executablePath, AuthorizationFlags options, const char* const* arguments)
+{
+ // *** MUST BE SAFE TO CALL ON NON-MAIN THREAD!
+
+ sig_t oldSigChildHandler = signal(SIGCHLD, SIG_DFL);
+ BOOL returnValue = YES;
+
+ if (AuthorizationExecuteWithPrivileges(authorization, executablePath, options, (char* const*)arguments, NULL) == errAuthorizationSuccess)
+ {
+ int status;
+ pid_t pid = wait(&status);
+ if (pid == -1 || !WIFEXITED(status) || WEXITSTATUS(status) != 0)
+ returnValue = NO;
+ }
+ else
+ returnValue = NO;
+
+ signal(SIGCHLD, oldSigChildHandler);
+ return returnValue;
+}
+
+@implementation FileHelpers
+
++ (void) moveFile:(NSString *)source to:(NSString*)dest withDelegate:delegate
+{
+ NSLog(@"FileHelpers moving file from %@ to %@", source, dest);
+
+ NSDictionary *info = [NSDictionary dictionaryWithObjectsAndKeys:source, TKCopySourceKey, dest, TKCopyDestinationKey, delegate, TKInstallerDelegateKey, nil];
+ [NSThread detachNewThreadSelector:@selector(performMoveWithInfo:) toTarget:self withObject:info];
+}
+
+
++ (void)performMoveWithInfo:(NSDictionary *)info
+{
+ // *** GETS CALLED ON NON-MAIN THREAD!
+
+ CAutoreleasePool _p;
+
+ NSString* fromPath = [info objectForKey: TKCopySourceKey];
+ NSString* toPath = [info objectForKey: TKCopyDestinationKey];
+
+ AuthorizationRef auth = NULL;
+ OSStatus authStat = errAuthorizationDenied;
+
+ NSLog(@"FileHelpers moving file from %@ to %@", fromPath, toPath);
+ BOOL haveOld = [[NSFileManager defaultManager] fileExistsAtPath: toPath];
+
+ if (haveOld == YES) { // delete the old file if it's there
+ if (0 != access([[toPath stringByDeletingLastPathComponent] fileSystemRepresentation], W_OK)
+ || 0 != access([[[toPath stringByDeletingLastPathComponent] stringByDeletingLastPathComponent] fileSystemRepresentation], W_OK))
+ {
+ const char* rmParams[] = { [toPath fileSystemRepresentation], NULL };
+ // NSLog( @"WOULD DELETE: %@", [toPath fileSystemRepresentation] );
+
+ while( authStat == errAuthorizationDenied )
+ {
+ authStat = AuthorizationCreate(NULL,
+ kAuthorizationEmptyEnvironment,
+ kAuthorizationFlagDefaults,
+ &auth);
+ }
+ if (authStat == errAuthorizationSuccess)
+ {
+ BOOL res = AuthorizationExecuteWithPrivilegesAndWait( auth, "/bin/rm", kAuthorizationFlagDefaults, rmParams );
+ if (!res)
+ NSLog(@"Could not delete: %@", toPath);
+ } else {
+ qDebug() << "Failed to authenticate to delete file under target to move, aborting";
+ return;
+ }
+ } else {
+ // We can delete it ourselves w/out authenticating
+ NSFileManager *manager = [[[NSFileManager alloc] init] autorelease];
+ NSError* error;
+ BOOL success = [manager removeItemAtPath:toPath error:&error];
+
+ if (!success) {
+ NSLog(@"Failed to delete file (w/out perms) underneath copy!: %@", [[error userInfo] objectForKey: NSLocalizedDescriptionKey]);
+ return;
+ }
+ }
+ }
+
+ FSRef dstRef, dstDirRef;
+ OSStatus err = FSPathMakeRefWithOptions((UInt8 *)[toPath fileSystemRepresentation], kFSPathMakeRefDoNotFollowLeafSymlink, &dstRef, NULL);
+
+ if (err != noErr && err != fnfErr) { // If the file is not found that's fine, we're moving to there after all
+ qDebug() << "GOT AN ERROR DOING FSPathMakeRefWithOptions!!!!! aborting move";
+ return;
+ }
+
+ if (0 != access([[toPath stringByDeletingLastPathComponent] fileSystemRepresentation], W_OK)
+ || 0 != access([[[toPath stringByDeletingLastPathComponent] stringByDeletingLastPathComponent] fileSystemRepresentation], W_OK))
+ {
+ // Not writeable by user, so authenticate
+ if (!auth) {
+ while( authStat == errAuthorizationDenied )
+ {
+ authStat = AuthorizationCreate(NULL,
+ kAuthorizationEmptyEnvironment,
+ kAuthorizationFlagDefaults,
+ &auth);
+ }
+ }
+
+ if (authStat == errAuthorizationSuccess)
+ {
+ // Fix perms before moving so we have them correct when they arrive
+ struct stat dstSB;
+ stat([[toPath stringByDeletingLastPathComponent] fileSystemRepresentation], &dstSB);
+ char uidgid[42];
+ snprintf(uidgid, sizeof(uidgid), "%d:%d",
+ dstSB.st_uid, dstSB.st_gid);
+
+ const char* coParams[] = { "-R", uidgid, [fromPath fileSystemRepresentation], NULL };
+ BOOL res = AuthorizationExecuteWithPrivilegesAndWait( auth, "/usr/sbin/chown", kAuthorizationFlagDefaults, coParams );
+ if( !res )
+ qDebug() << "Failed to set permissions before moving";
+
+ // Do the move
+ const char* mvParams[] = { "-f", [fromPath fileSystemRepresentation], [toPath fileSystemRepresentation], NULL };
+ res = AuthorizationExecuteWithPrivilegesAndWait( auth, "/bin/mv", kAuthorizationFlagDefaults, mvParams );
+ if( !res )
+ NSLog(@"Failed to move source file from %@ to %@ with error %@", fromPath, toPath, res );
+
+ AuthorizationFree(auth, 0);
+ auth = NULL;
+ }
+
+ return;
+ }
+
+ if (auth) {
+ AuthorizationFree(auth, 0);
+ auth = NULL;
+ }
+
+ err = FSPathMakeRef((UInt8 *)[[toPath stringByDeletingLastPathComponent] fileSystemRepresentation], &dstDirRef, NULL);
+
+ if (err != noErr) {
+ qDebug() << "GOT AN ERROR DOING FSPathMakeRef to get dir to copy into!!!!! aborting move";
+ return;
+ }
+
+ NSFileManager *manager = [[[NSFileManager alloc] init] autorelease];
+ NSError* error;
+ BOOL success = [manager moveItemAtPath:fromPath toPath:toPath error:&error];
+ if (!success) {
+ NSLog( @"Failed to do non-authenticated move! Help! %@", [[error userInfo] objectForKey: NSLocalizedDescriptionKey] );
+ }
+ [self notifyDelegate:[NSDictionary dictionaryWithObjectsAndKeys:[NSNumber numberWithBool:success], TKInstallerResultKey, [info objectForKey:TKInstallerDelegateKey], TKInstallerDelegateKey, error, TKInstallerErrorKey, nil]];
+}
+
+
++ (void)notifyDelegate:(NSDictionary *)info
+{
+ // *** GETS CALLED ON NON-MAIN THREAD!
+ BOOL result = [[info objectForKey:TKInstallerResultKey] boolValue];
+ if (result)
+ {
+ if ([[info objectForKey:TKInstallerDelegateKey] respondsToSelector:@selector(moveFinished)])
+ [[info objectForKey:TKInstallerDelegateKey] performSelectorOnMainThread: @selector(moveFinished) withObject:nil waitUntilDone: NO];
+ }
+ else
+ {
+ if ([[info objectForKey:TKInstallerDelegateKey] respondsToSelector:@selector(moveFailedWithError:)])
+ {
+ [[info objectForKey:TKInstallerDelegateKey] performSelectorOnMainThread: @selector(moveFailedWithError) withObject:[NSDictionary dictionaryWithObjectsAndKeys:[info objectForKey:TKInstallerErrorKey], TKInstallerErrorKey, nil] waitUntilDone: NO];
+ }
+ }
+}
+
+@end
diff --git a/src/libtomahawk/utils/Closure.h b/src/libtomahawk/utils/Closure.h
index f071c0e28..591712358 100644
--- a/src/libtomahawk/utils/Closure.h
+++ b/src/libtomahawk/utils/Closure.h
@@ -1,5 +1,6 @@
-/* This file is part of Clementine.
+/* This file is part of Tomabawk.
Copyright 2011, David Sansome
+ Copyright 2012, Leo Franchi
Clementine is free software: you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
diff --git a/src/libtomahawk/utils/TomahawkUtils.cpp b/src/libtomahawk/utils/TomahawkUtils.cpp
index f61bf924d..3099cd170 100644
--- a/src/libtomahawk/utils/TomahawkUtils.cpp
+++ b/src/libtomahawk/utils/TomahawkUtils.cpp
@@ -39,7 +39,10 @@
#include
#include
-#ifdef Q_WS_WIN
+#include
+#include
+
+#ifdef Q_OS_WIN
#include
#include
#endif
@@ -49,6 +52,10 @@
#include
#endif
+#ifdef QCA2_FOUND
+#include
+#endif
+
namespace TomahawkUtils
{
static quint64 s_infosystemRequestId = 0;
@@ -676,4 +683,220 @@ SharedTimeLine::disconnectNotify( const char* signal )
}
+bool
+verifyFile( const QString &filePath, const QString &signature )
+{
+ QCA::Initializer init;
+
+ if( !QCA::isSupported( "sha1" ) )
+ {
+ qWarning() << "SHA1 not supported by QCA, aborting.";
+ return false;
+ }
+
+ // The signature for the resolver.zip was created like so:
+ // openssl dgst -sha1 -binary < "#{tarball}" | openssl dgst -dss1 -sign "#{ARGV[2]}" | openssl enc -base64
+ // which means we need to decode it with QCA's DSA public key signature verification tools
+ // The input data is:
+ // file -> SHA1 binary format -> DSS1/DSA signed -> base64 encoded.
+
+ // Step 1: Load the public key
+ // Public key is in :/data/misc/tomahawk_pubkey.pem
+ QFile f( ":/data/misc/tomahawk_pubkey.pem" );
+ if ( !f.open( QIODevice::ReadOnly ) )
+ {
+ qWarning() << "Unable to read public key from resources!";
+ return false;
+ }
+
+ const QString pubkeyData = QString::fromUtf8( f.readAll() );
+ QCA::ConvertResult conversionResult;
+ QCA::PublicKey publicKey = QCA::PublicKey::fromPEM( pubkeyData, &conversionResult );
+ if ( QCA::ConvertGood != conversionResult)
+ {
+ qWarning() << "Public key reading/loading failed! Tried to load public key:" << pubkeyData;
+ return false;
+ }
+
+ if ( !publicKey.canVerify() )
+ {
+ qWarning() << "Loaded Tomahawk public key but cannot use it to verify! What is up....";
+ return false;
+ }
+
+ // Step 2: Get the SHA1 of the file contents
+ QFile toVerify( filePath );
+ if ( !toVerify.exists() || !toVerify.open( QIODevice::ReadOnly ) )
+ {
+ qWarning() << "Failed to open file we are trying to verify!" << filePath;
+ return false;
+ }
+
+ const QByteArray fileHashData = QCA::Hash( "sha1" ).hash( toVerify.readAll() ).toByteArray();
+ toVerify.close();
+
+ // Step 3: Base64 decode the signature
+ QCA::Base64 decoder( QCA::Decode );
+ const QByteArray decodedSignature = decoder.decode( QCA::SecureArray( signature.trimmed().toUtf8() ) ).toByteArray();
+ if ( decodedSignature.isEmpty() )
+ {
+ qWarning() << "Got empty signature after we tried to decode it from Base64:" << signature.trimmed().toUtf8() << decodedSignature.toBase64();
+ return false;
+ }
+
+ // Step 4: Do the actual verifying!
+ const bool result = publicKey.verifyMessage( fileHashData, decodedSignature, QCA::EMSA1_SHA1, QCA::DERSequence );
+ if ( !result )
+ {
+ qWarning() << "File" << filePath << "FAILED VERIFICATION against our input signature!";
+ return false;
+ }
+
+ qDebug() << "Successfully verified signature of downloaded file:" << filePath;
+
+ return true;
+}
+
+
+QString
+extractScriptPayload( const QString& filename, const QString& resolverId )
+{
+ // uses QuaZip to extract the temporary zip file to the user's tomahawk data/resolvers directory
+ QDir resolverDir = appDataDir();
+ if ( !resolverDir.mkpath( QString( "atticaresolvers/%1" ).arg( resolverId ) ) )
+ {
+ tLog() << "Failed to mkdir resolver save dir: " << TomahawkUtils::appDataDir().absoluteFilePath( QString( "atticaresolvers/%1" ).arg( resolverId ) );
+ return QString();
+ }
+ resolverDir.cd( QString( "atticaresolvers/%1" ).arg( resolverId ) );
+
+
+ if ( !unzipFileInFolder( filename, resolverDir ) )
+ {
+ qWarning() << "Failed to unzip resolver. Ooops.";
+ return QString();
+ }
+
+ return resolverDir.absolutePath();
+}
+
+
+bool
+unzipFileInFolder( const QString &zipFileName, const QDir &folder )
+{
+ Q_ASSERT( !zipFileName.isEmpty() );
+ Q_ASSERT( folder.exists() );
+
+ QuaZip zipFile( zipFileName );
+ if ( !zipFile.open( QuaZip::mdUnzip ) )
+ {
+ qWarning() << "Failed to QuaZip open:" << zipFile.getZipError();
+ return false;
+ }
+
+ if ( !zipFile.goToFirstFile() )
+ {
+ tLog() << "Failed to go to first file in zip archive: " << zipFile.getZipError();
+ return false;
+ }
+
+ tDebug() << "Unzipping files to:" << folder.absolutePath();
+
+ QuaZipFile fileInZip( &zipFile );
+ do
+ {
+ QuaZipFileInfo info;
+ zipFile.getCurrentFileInfo( &info );
+
+ if ( !fileInZip.open( QIODevice::ReadOnly ) )
+ {
+ tLog() << "Failed to open file inside zip archive:" << info.name << zipFile.getZipName() << "with error:" << zipFile.getZipError();
+ continue;
+ }
+
+ QFile out( folder.absoluteFilePath( fileInZip.getActualFileName() ) );
+
+ // make dir if there is one needed
+ QStringList parts = fileInZip.getActualFileName().split( "/" );
+ if ( parts.size() > 1 )
+ {
+ QStringList dirs = parts.mid( 0, parts.size() - 1 );
+ QString dirPath = dirs.join( "/" ); // QDir translates / to \ internally if necessary
+ folder.mkpath( dirPath );
+ }
+
+ tDebug() << "Writing to output file..." << out.fileName();
+ if ( !out.open( QIODevice::WriteOnly ) )
+ {
+ tLog() << "Failed to open zip extract file:" << out.errorString() << info.name;
+ continue;
+ }
+
+
+ out.write( fileInZip.readAll() );
+ out.close();
+ fileInZip.close();
+
+ } while ( zipFile.goToNextFile() );
+
+ return true;
+}
+
+
+void
+extractBinaryResolver( const QString& zipFilename, QObject* receiver )
+{
+#if !defined(Q_OS_MAC) && !defined (Q_OS_WIN)
+ Q_ASSERT( false );
+ qWarning() << "NO SUPPORT YET FOR LINUX BINARY RESOLVERS!";
+ return;
+#endif
+
+#ifdef Q_OS_MAC
+ // Platform-specific handling of resolver payload now. We know it's good
+ // Unzip the file.
+ QFileInfo info( zipFilename );
+ QDir tmpDir = QDir::tempPath();
+ if ( !tmpDir.mkdir( info.baseName() ) )
+ {
+ qWarning() << "Failed to create temporary directory to unzip in:" << tmpDir.absolutePath();
+ return;
+ }
+ tmpDir.cd( info.baseName() );
+ TomahawkUtils::unzipFileInFolder( info.absoluteFilePath(), tmpDir );
+
+ // On OSX it just contains 1 file, the resolver executable itself. For now. We just copy it to
+ // the Tomahawk.app/Contents/MacOS/ folder alongside the Tomahawk executable.
+ const QString dest = QCoreApplication::applicationDirPath();
+ // Find the filename
+ const QDir toList( tmpDir.absolutePath() );
+ const QStringList files = toList.entryList( QStringList(), QDir::Files );
+ Q_ASSERT( files.size() == 1 );
+
+ const QString src = toList.absoluteFilePath( files.first() );
+ qDebug() << "OS X: Copying binary resolver from to:" << src << dest;
+
+ copyWithAuthentication( src, dest, receiver );
+#elif defined(Q_OS_WIN)
+ // We unzip directly to the target location, just like normal attica resolvers
+ Q_ASSERT( receiver );
+ if ( !receiver )
+ return;
+
+ const QString resolverId = receiver->property( "resolverid" ).toString();
+
+ Q_ASSERT( !resolverId.isEmpty() );
+ if ( resolverId.isEmpty() )
+ return;
+
+ const QString resolverDir = extractScriptPayload( zipFilename, resolverId );
+ QMetaObject::invokeMethod(receiver, "installSucceeded", Qt::DirectConnection, Q_ARG( QString, path ) );
+
+#endif
+
+ // No support for binary resolvers on linux! Shouldn't even have been allowed to see/install..
+ Q_ASSERT( false );
+}
+
+
} // ns
diff --git a/src/libtomahawk/utils/TomahawkUtils.h b/src/libtomahawk/utils/TomahawkUtils.h
index b2211a488..27350e3e1 100644
--- a/src/libtomahawk/utils/TomahawkUtils.h
+++ b/src/libtomahawk/utils/TomahawkUtils.h
@@ -115,7 +115,7 @@ namespace TomahawkUtils
QStringList m_noProxyHosts;
QNetworkProxy m_proxy;
};
-
+
DLLEXPORT QString appFriendlyVersion();
@@ -138,7 +138,19 @@ namespace TomahawkUtils
DLLEXPORT QString md5( const QByteArray& data );
DLLEXPORT bool removeDirectory( const QString& dir );
-
+
+ DLLEXPORT bool verifyFile( const QString& filePath, const QString& signature );
+ DLLEXPORT QString extractScriptPayload( const QString& filename, const QString& resolverId );
+ DLLEXPORT bool unzipFileInFolder( const QString& zipFileName, const QDir& folder );
+
+
+ // Extracting may be asynchronous, pass in a receiver object with the following slots:
+ // extractSucceeded( const QString& path ) and extractFailed() to be notified/
+ DLLEXPORT void extractBinaryResolver( const QString& zipFilename, QObject* receiver );
+
+ // Used by the above, not exported
+ void copyWithAuthentication( const QString& srcFile, const QDir dest, QObject* receiver );
+
/**
* This helper is designed to help "update" an existing playlist with a newer revision of itself.
* To avoid re-loading the whole playlist and re-resolving tracks that are the same in the old playlist,
diff --git a/src/libtomahawk/utils/TomahawkUtils_Mac.h b/src/libtomahawk/utils/TomahawkUtils_Mac.h
new file mode 100644
index 000000000..635db4117
--- /dev/null
+++ b/src/libtomahawk/utils/TomahawkUtils_Mac.h
@@ -0,0 +1,37 @@
+/* === This file is part of Tomahawk Player - ===
+ *
+ * Copyright 2012, Leo Franchi .
+ */
+
+#ifndef TOMAHAWKUTILS_MAC_H
+#define TOMAHAWKUTILS_MAC_H
+
+#include
+
+#import "mac/FileHelpers.h"
+
+@interface MoveDelegate : NSObject
+{
+ QObject* receiver;
+ QString path;
+}
+- (void)setReceiver:(QObject*)receiver;
+- (void)setMoveTo:(QString)path;
+- (void)moveFinished;
+- (void)moveFailedWithError:(NSError *)error;
+@end
+
+#endif // TOMAHAWKUTILS_MAC_H
diff --git a/src/libtomahawk/utils/TomahawkUtils_Mac.mm b/src/libtomahawk/utils/TomahawkUtils_Mac.mm
index aeac94866..fc5c3d08c 100644
--- a/src/libtomahawk/utils/TomahawkUtils_Mac.mm
+++ b/src/libtomahawk/utils/TomahawkUtils_Mac.mm
@@ -1,6 +1,75 @@
+/* === This file is part of Tomahawk Player - ===
+ *
+ * Copyright 2012, Leo Franchi .
+ */
+
#include "TomahawkUtils.h"
+#include "TomahawkUtils_Mac.h"
+#include "mac/FileHelpers.h"
+
+#include
+#include
+
#import
+#import
+
+@implementation MoveDelegate
+
+
+-(void) setReceiver:(QObject*) object
+{
+ receiver = object;
+}
+
+-(void) setMoveTo:(QString) p
+{
+ path = p;
+}
+
+- (void)moveFinished
+{
+ // HACK since I can't figure out how to get QuaZip to maintain executable permissions after unzip (nor find the info)
+ // we set the binary to executable here
+
+ NSLog(@"Move succeeded!, handling result");
+
+ NSFileManager *manager = [[[NSFileManager alloc] init] autorelease];
+ NSError* error;
+ NSDictionary* attrs = [NSDictionary dictionaryWithObjectsAndKeys:[NSNumber numberWithInt:0755], NSFilePosixPermissions, nil];
+
+ NSString* target = [[NSString alloc] initWithBytes:path.toUtf8() length:path.length() encoding: NSUTF8StringEncoding];
+ NSLog(@"Changing permissions to executable for: %@", target);
+ BOOL success = [manager setAttributes:attrs ofItemAtPath:target error:&error];
+ if (!success) {
+ NSLog( @"Failed to do chmod +x of moved resolver! %@", [[error userInfo] objectForKey: NSLocalizedDescriptionKey] );
+ }
+
+ if ( receiver )
+ QMetaObject::invokeMethod(receiver, "installSucceeded", Qt::DirectConnection, Q_ARG(QString, path));
+
+}
+
+- (void)moveFailedWithError:(NSError *)error
+{
+ NSLog(@"Move failed, handling result");
+ if ( receiver )
+ QMetaObject::invokeMethod(receiver, "installFailed", Qt::DirectConnection);
+}
+@end
namespace TomahawkUtils
{
@@ -10,4 +79,31 @@ bringToFront() {
[NSApp activateIgnoringOtherApps:YES];
}
+void
+copyWithAuthentication( const QString& srcFile, const QDir dest, QObject* receiver )
+{
+ /**
+ On OS X, we have to do the following:
+ 1) Authenticate to be able to have write access to the /Applications folder
+ 2) Copy file to dest
+ 5) Call result slots on receiver object
+ */
+
+ MoveDelegate* del = [[MoveDelegate alloc] init];
+ [del setReceiver: receiver];
+
+ // Get the filename + path to save for later
+ QFileInfo srcInfo( srcFile );
+ const QString resultingPath = dest.absoluteFilePath( srcInfo.fileName() );
+ [del setMoveTo: resultingPath];
+
+ const QFileInfo info( srcFile );
+ const QString destPath = dest.absoluteFilePath( info.fileName() );
+
+ NSString* src = [[NSString alloc] initWithBytes: srcFile.toUtf8() length: srcFile.length() encoding: NSUTF8StringEncoding];
+ NSString* destStr = [[NSString alloc] initWithBytes: destPath.toUtf8() length: destPath.length() encoding: NSUTF8StringEncoding];
+ [FileHelpers moveFile:src to:destStr withDelegate:del];
+}
+
+
}
diff --git a/src/main.cpp b/src/main.cpp
index fe0332d1f..2c786bf55 100644
--- a/src/main.cpp
+++ b/src/main.cpp
@@ -121,7 +121,7 @@ int WINAPI WinMain (HINSTANCE hInstance, HINSTANCE hPrevInstance,
int
main( int argc, char *argv[] )
{
- #ifdef Q_WS_MAC
+#ifdef Q_WS_MAC
// Do Mac specific startup to get media keys working.
// This must go before QApplication initialisation.
Tomahawk::macMain();