1
0
mirror of https://github.com/tomahawk-player/tomahawk.git synced 2025-03-23 01:09:42 +01:00

Merge branch 'binaryghns'

This commit is contained in:
Leo Franchi 2012-05-18 16:34:54 -04:00
commit b972558171
32 changed files with 1316 additions and 154 deletions

View File

@ -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:

View File

@ -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-----

View File

@ -141,5 +141,6 @@
<file>data/images/process-stop.png</file>
<file>data/icons/tomahawk-icon-128x128-grayscale.png</file>
<file>data/images/collection.png</file>
<file>data/misc/tomahawk_pubkey.pem</file>
</qresource>
</RCC>

View File

@ -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 )
{

View File

@ -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 );

View File

@ -52,6 +52,7 @@
#include <accounts/ResolverAccount.h>
#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() );
}
}

View File

@ -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();

View File

@ -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 <QAction>
#include <QHBoxLayout>
#include <QLabel>
#include <QMessageBox>
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 ) );

View File

@ -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;
};

View File

@ -23,9 +23,8 @@
#include "Pipeline.h"
#include <attica/downloaditem.h>
#include <quazip.h>
#include <quazipfile.h>
#include <QCoreApplication>
#include <QNetworkReply>
#include <QTemporaryFile>
#include <QDir>
@ -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<AtticaManager> 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<Category>* 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"

View File

@ -32,6 +32,7 @@
#include <attica/providermanager.h>
#include <attica/content.h>
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

View File

@ -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 )

View File

@ -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();
}
}
}

View File

@ -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;

View File

@ -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;
}

View File

@ -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; }

View File

@ -202,6 +202,9 @@ AccountManager::enableAccount( Account* account )
account->authenticate();
if ( account->preventEnabling() )
return;
account->setEnabled( true );
m_enabledAccounts << account;

View File

@ -24,6 +24,10 @@
#include "AtticaManager.h"
#include "ResolverAccount.h"
#ifndef ENABLE_HEADLESS
#include <QMessageBox>
#endif
#include <attica/content.h>
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.<br /><br />"
"Please use \"Install from file\" above, by fetching it from your distribution or compiling it yourself. Further instructions can be found here:<br /><br />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
{

View File

@ -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;

View File

@ -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 ) );
}

View File

@ -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;

View File

@ -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:

View File

@ -1,6 +1,7 @@
/* === This file is part of Tomahawk Player - <http://tomahawk-player.org> ===
*
* Copyright 2010-2011, Christian Muehlhaeuser <muesli@tomahawk-player.org>
* Copyright 2010-2011, Leo Franchi <lfranchi@kde.org>
*
* Tomahawk is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by

View File

@ -1,6 +1,7 @@
/* === This file is part of Tomahawk Player - <http://tomahawk-player.org> ===
*
* Copyright 2010-2011, Christian Muehlhaeuser <muesli@tomahawk-player.org>
* Copyright 2010-2011, Leo Franchi <lfranchi@kde.org>
*
* Tomahawk is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by

View File

@ -0,0 +1,40 @@
/* === This file is part of Tomahawk Player - <http://tomahawk-player.org> ===
*
* Copyright 2012, Leo Franchi <lfranchi@kde.org
*
* Tomahawk is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* Tomahawk is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with Tomahawk. If not, see <http://www.gnu.org/licenses/>.
*/
#ifndef MAC_FILE_HELPERS_H
#define MAC_FILE_HELPERS_H
#import <Cocoa/Cocoa.h>
#import <Foundation/Foundation.h>
// 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

View File

@ -0,0 +1,226 @@
/* === This file is part of Tomahawk Player - <http://tomahawk-player.org> ===
*
* Copyright 2012, Leo Franchi <lfranchi@kde.org
*
* Tomahawk is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* Tomahawk is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with Tomahawk. If not, see <http://www.gnu.org/licenses/>.
*/
#import "FileHelpers.h"
#import <CoreServices/CoreServices.h>
#import <Security/Security.h>
#import <sys/stat.h>
#import <sys/wait.h>
#import <dirent.h>
#import <unistd.h>
#import <sys/param.h>
#include <QDebug>
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

View File

@ -1,5 +1,6 @@
/* This file is part of Clementine.
/* This file is part of Tomabawk.
Copyright 2011, David Sansome <me@davidsansome.com>
Copyright 2012, Leo Franchi <lfranchi@kde.org>
Clementine is free software: you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by

View File

@ -39,7 +39,10 @@
#include <QMutex>
#include <QCryptographicHash>
#ifdef Q_WS_WIN
#include <quazip.h>
#include <quazipfile.h>
#ifdef Q_OS_WIN
#include <windows.h>
#include <shlobj.h>
#endif
@ -49,6 +52,10 @@
#include <sys/sysctl.h>
#endif
#ifdef QCA2_FOUND
#include <QtCrypto>
#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

View File

@ -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,

View File

@ -0,0 +1,37 @@
/* === This file is part of Tomahawk Player - <http://tomahawk-player.org> ===
*
* Copyright 2012, Leo Franchi <lfranchi@kde.org
*
* Tomahawk is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* Tomahawk is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with Tomahawk. If not, see <http://www.gnu.org/licenses/>.
*/
#ifndef TOMAHAWKUTILS_MAC_H
#define TOMAHAWKUTILS_MAC_H
#include <QObject>
#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

View File

@ -1,6 +1,75 @@
/* === This file is part of Tomahawk Player - <http://tomahawk-player.org> ===
*
* Copyright 2012, Leo Franchi <lfranchi@kde.org
*
* Tomahawk is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* Tomahawk is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with Tomahawk. If not, see <http://www.gnu.org/licenses/>.
*/
#include "TomahawkUtils.h"
#include "TomahawkUtils_Mac.h"
#include "mac/FileHelpers.h"
#include <QDir>
#include <QTemporaryFile>
#import <AppKit/NSApplication.h>
#import <Foundation/Foundation.h>
@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];
}
}

View File

@ -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();