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:
commit
b972558171
@ -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:
|
||||
|
20
data/misc/tomahawk_pubkey.pem
Normal file
20
data/misc/tomahawk_pubkey.pem
Normal 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-----
|
@ -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>
|
||||
|
@ -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 )
|
||||
{
|
||||
|
@ -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 );
|
||||
|
||||
|
@ -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() );
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -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();
|
||||
|
@ -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 ) );
|
||||
|
@ -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;
|
||||
};
|
||||
|
@ -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"
|
||||
|
@ -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
|
||||
|
@ -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 )
|
||||
|
@ -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();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
|
@ -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;
|
||||
|
@ -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;
|
||||
}
|
||||
|
@ -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; }
|
||||
|
@ -202,6 +202,9 @@ AccountManager::enableAccount( Account* account )
|
||||
|
||||
account->authenticate();
|
||||
|
||||
if ( account->preventEnabling() )
|
||||
return;
|
||||
|
||||
account->setEnabled( true );
|
||||
m_enabledAccounts << account;
|
||||
|
||||
|
@ -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
|
||||
{
|
||||
|
@ -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;
|
||||
|
@ -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 ) );
|
||||
}
|
||||
|
||||
|
@ -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;
|
||||
|
@ -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:
|
||||
|
@ -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
|
||||
|
@ -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
|
||||
|
40
src/libtomahawk/mac/FileHelpers.h
Normal file
40
src/libtomahawk/mac/FileHelpers.h
Normal 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
|
226
src/libtomahawk/mac/FileHelpers.mm
Normal file
226
src/libtomahawk/mac/FileHelpers.mm
Normal 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
|
@ -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
|
||||
|
@ -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
|
||||
|
@ -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,
|
||||
|
37
src/libtomahawk/utils/TomahawkUtils_Mac.h
Normal file
37
src/libtomahawk/utils/TomahawkUtils_Mac.h
Normal 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
|
@ -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];
|
||||
}
|
||||
|
||||
|
||||
}
|
||||
|
@ -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();
|
||||
|
Loading…
x
Reference in New Issue
Block a user