PageRenderTime 230ms CodeModel.GetById 60ms app.highlight 129ms RepoModel.GetById 32ms app.codeStats 0ms

/src/libtomahawk/AtticaManager.cpp

http://github.com/tomahawk-player/tomahawk
C++ | 798 lines | 587 code | 162 blank | 49 comment | 93 complexity | e311f19e06400eea1ad5683c66cc50e9 MD5 | raw file
  1/* === This file is part of Tomahawk Player - <http://tomahawk-player.org> ===
  2 *
  3 *   Copyright 2010-2011, Leo Franchi <lfranchi@kde.org>
  4 *   Copyright 2015, Christian Muehlhaeuser <muesli@tomahawk-player.org>
  5 *
  6 *   Tomahawk is free software: you can redistribute it and/or modify
  7 *   it under the terms of the GNU General Public License as published by
  8 *   the Free Software Foundation, either version 3 of the License, or
  9 *   (at your option) any later version.
 10 *
 11 *   Tomahawk is distributed in the hope that it will be useful,
 12 *   but WITHOUT ANY WARRANTY; without even the implied warranty of
 13 *   MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
 14 *   GNU General Public License for more details.
 15 *
 16 *   You should have received a copy of the GNU General Public License
 17 *   along with Tomahawk. If not, see <http://www.gnu.org/licenses/>.
 18 */
 19
 20#include "AtticaManager.h"
 21
 22#include "utils/TomahawkUtils.h"
 23#include "TomahawkSettings.h"
 24#include "Pipeline.h"
 25#include "Source.h"
 26#include "config.h"
 27
 28#include "utils/Logger.h"
 29#include "accounts/ResolverAccount.h"
 30#include "accounts/AccountManager.h"
 31#include "utils/BinaryInstallerHelper.h"
 32#include "utils/Closure.h"
 33#include "utils/NetworkAccessManager.h"
 34
 35#include <attica/downloaditem.h>
 36
 37#include <QCoreApplication>
 38#include <QNetworkReply>
 39#include <QTemporaryFile>
 40#include <QDir>
 41#include <QTimer>
 42#include <QDomDocument>
 43#include <QDomElement>
 44#include <QDomNode>
 45
 46using namespace Attica;
 47
 48AtticaManager* AtticaManager::s_instance = 0;
 49
 50
 51// Sort binary resolvers above script resolvers, and script resolvers by download count
 52bool
 53resolverSort( const Attica::Content& first, const Attica::Content& second )
 54{
 55    if ( !first.attribute( "typeid" ).isEmpty() && second.attribute( "typeid" ).isEmpty() )
 56        return true;
 57
 58    return first.downloads() > second.downloads();
 59}
 60
 61
 62AtticaManager::AtticaManager( QObject* parent )
 63    : QObject( parent )
 64    , m_manager( Attica::ProviderManager::ProviderFlags( Attica::ProviderManager::DisablePlugins ) )
 65    , m_resolverJobsLoaded( 0 )
 66{
 67    connect( &m_manager, SIGNAL( providerAdded( Attica::Provider ) ), this, SLOT( providerAdded( Attica::Provider ) ) );
 68
 69    // resolvers
 70//    m_manager.addProviderFile( QUrl( "http://v09.bakery.tomahawk-player.org/resolvers/providers.xml" ) );
 71
 72    const QString url = QString( "%1/resolvers/providers.xml?version=%2" ).arg( hostname() ).arg( TomahawkUtils::appFriendlyVersion() );
 73    QNetworkReply* reply = Tomahawk::Utils::nam()->get( QNetworkRequest( QUrl( url ) ) );
 74    NewClosure( reply, SIGNAL( finished() ), this, SLOT( providerFetched( QNetworkReply* ) ), reply );
 75    connect( reply, SIGNAL( error( QNetworkReply::NetworkError ) ), this, SLOT( providerError( QNetworkReply::NetworkError ) ) );
 76
 77//     m_manager.addProviderFile( QUrl( "http://lycophron/resolvers/providers.xml" ) );
 78
 79    qRegisterMetaType< Attica::Content >( "Attica::Content" );
 80}
 81
 82
 83AtticaManager::~AtticaManager()
 84{
 85    savePixmapsToCache();
 86
 87    foreach( const QString& id, m_resolverStates.keys() )
 88    {
 89        if ( !m_resolverStates[ id ].pixmap )
 90            continue;
 91
 92        delete m_resolverStates[ id ].pixmap;
 93    }
 94}
 95
 96
 97void
 98AtticaManager::fetchMissingIcons()
 99{
100    foreach ( Content resolver, m_resolvers )
101    {
102        if ( !m_resolverStates.contains( resolver.id() ) )
103            m_resolverStates.insert( resolver.id(), Resolver() );
104
105        if ( !m_resolverStates.value( resolver.id() ).pixmap && !resolver.icons().isEmpty() && !resolver.icons().first().url().isEmpty() )
106        {
107            QNetworkReply* fetch = Tomahawk::Utils::nam()->get( QNetworkRequest( resolver.icons().first().url() ) );
108            fetch->setProperty( "resolverId", resolver.id() );
109
110            connect( fetch, SIGNAL( finished() ), this, SLOT( resolverIconFetched() ) );
111        }
112    }
113}
114
115
116QString
117AtticaManager::hostname() const
118{
119    return "http://v09.bakery.tomahawk-player.org";
120}
121
122
123void
124AtticaManager::loadPixmapsFromCache()
125{
126    QDir cacheDir = TomahawkUtils::appDataDir();
127    if ( !cacheDir.cd( "atticacache" ) ) // doesn't exist, no cache
128        return;
129
130    qDebug() << "Loading resolvers from cache dir:" << cacheDir.absolutePath();
131    qDebug() << "Currently we know about these resolvers:" << m_resolverStates.keys();
132    foreach ( const QString& file, cacheDir.entryList( QStringList() << "*.png", QDir::Files | QDir::NoSymLinks ) )
133    {
134        // load all the pixmaps
135        QFileInfo info( file );
136        if ( !m_resolverStates.contains( info.baseName() ) )
137        {
138            tLog() << "Found resolver icon cached for resolver we no longer see in synchrotron repo:" << info.baseName();
139            continue;
140        }
141
142        QPixmap* icon = new QPixmap( cacheDir.absoluteFilePath( file ) );
143        if ( !icon->isNull() )
144        {
145            m_resolverStates[ info.baseName() ].pixmap = icon;
146        }
147    }
148}
149
150
151void
152AtticaManager::savePixmapsToCache()
153{
154    QDir cacheDir = TomahawkUtils::appDataDir();
155    if ( !cacheDir.cd( "atticacache" ) ) // doesn't exist, create
156    {
157        cacheDir.mkdir( "atticacache" );
158        cacheDir.cd( "atticache" );
159    }
160
161    foreach( const QString& id, m_resolverStates.keys() )
162    {
163        if ( !m_resolverStates[ id ].pixmap || !m_resolverStates[ id ].pixmapDirty )
164            continue;
165
166        const QString filename = cacheDir.absoluteFilePath( QString( "%1.png" ).arg( id ) );
167        QFile f( filename );
168        if ( !f.open( QIODevice::WriteOnly ) )
169        {
170            tLog() << "Failed to open cache file for writing:" << filename;
171        }
172        else
173        {
174            if ( !m_resolverStates[ id ].pixmap->save( &f ) )
175            {
176                tLog() << "Failed to save pixmap into opened file for writing:" << filename;
177            }
178        }
179    }
180}
181
182
183QPixmap
184AtticaManager::iconForResolver( const Content& resolver )
185{
186    if ( !m_resolverStates[ resolver.id() ].pixmap )
187        return QPixmap();
188
189    return *m_resolverStates.value( resolver.id() ).pixmap;
190}
191
192
193Content::List
194AtticaManager::resolvers() const
195{
196    return m_resolvers;
197}
198
199
200Content
201AtticaManager::resolverForId( const QString& id ) const
202{
203    foreach ( const Attica::Content& c, m_resolvers )
204    {
205        if ( c.id() == id )
206            return c;
207    }
208
209    return Content();
210}
211
212
213
214AtticaManager::ResolverState
215AtticaManager::resolverState ( const Content& resolver ) const
216{
217    if ( !m_resolverStates.contains( resolver.id() ) )
218    {
219        return AtticaManager::Uninstalled;
220    }
221
222    return m_resolverStates[ resolver.id() ].state;
223}
224
225
226bool
227AtticaManager::resolversLoaded() const
228{
229    return !m_resolvers.isEmpty();
230}
231
232
233QString
234AtticaManager::pathFromId( const QString& resolverId ) const
235{
236    if ( !m_resolverStates.contains( resolverId ) )
237        return QString();
238
239    return m_resolverStates.value( resolverId ).scriptPath;
240}
241
242
243void
244AtticaManager::uploadRating( const Content& c )
245{
246    m_resolverStates[ c.id() ].userRating = c.rating();
247
248    for ( int i = 0; i < m_resolvers.count(); i++ )
249    {
250        if ( m_resolvers[ i ].id() == c.id() )
251        {
252            Attica::Content atticaContent = m_resolvers[ i ];
253            atticaContent.setRating( c.rating() );
254            m_resolvers[ i ] = atticaContent;
255            break;
256        }
257    }
258
259    TomahawkSettings::instance()->setAtticaResolverStates( m_resolverStates );
260
261    PostJob* job = m_resolverProvider.voteForContent( c.id(), (uint)c.rating() );
262    connect( job, SIGNAL( finished( Attica::BaseJob* ) ), job, SLOT( deleteLater() ) );
263
264    job->start();
265
266    emit resolverStateChanged( c.id() );
267}
268
269
270bool
271AtticaManager::userHasRated( const Content& c ) const
272{
273    return m_resolverStates[ c.id() ].userRating != -1;
274}
275
276
277bool
278AtticaManager::hasCustomAccountForAttica( const QString &id ) const
279{
280    return m_customAccounts.keys().contains( id );
281}
282
283
284Tomahawk::Accounts::Account*
285AtticaManager::customAccountForAttica( const QString &id ) const
286{
287    return m_customAccounts.value( id );
288}
289
290
291void
292AtticaManager::registerCustomAccount( const QString &atticaId, Tomahawk::Accounts::Account *account )
293{
294    m_customAccounts.insert( atticaId, account );
295}
296
297
298AtticaManager::Resolver
299AtticaManager::resolverData(const QString &atticaId) const
300{
301    return m_resolverStates.value( atticaId );
302}
303
304
305void
306AtticaManager::providerError( QNetworkReply::NetworkError err )
307{
308    Q_UNUSED( err );
309
310    // So those who care know
311    emit resolversLoaded( Content::List() );
312}
313
314
315void
316AtticaManager::providerFetched( QNetworkReply* reply )
317{
318    Q_ASSERT( reply );
319    if ( !reply )
320        return;
321
322    m_manager.addProviderFromXml( reply->readAll() );
323}
324
325
326void
327AtticaManager::providerAdded( const Provider& provider )
328{
329    if ( provider.name() == "Tomahawk Resolvers" )
330    {
331        m_resolverProvider = provider;
332        m_resolvers.clear();
333
334        m_resolverStates = TomahawkSettings::instance()->atticaResolverStates();
335
336        ListJob<Category>* job = m_resolverProvider.requestCategories();
337        connect( job, SIGNAL( finished( Attica::BaseJob* ) ), this, SLOT( categoriesReturned( Attica::BaseJob* ) ) );
338        job->start();
339    }
340}
341
342
343void
344AtticaManager::categoriesReturned( BaseJob* j )
345{
346    ListJob< Category >* job = static_cast< ListJob< Category >* >( j );
347
348    Category::List categories = job->itemList();
349    foreach ( const Category& category, categories )
350    {
351        ListJob< Content >* job = m_resolverProvider.searchContents( Category::List() << category, QString(), Provider::Downloads, 0, 50 );
352
353        if ( category.name() == "Resolver" )
354            connect( job, SIGNAL( finished( Attica::BaseJob* ) ), this, SLOT( resolversList( Attica::BaseJob* ) ) );
355        else if ( category.name() == "BinaryResolver" )
356            connect( job, SIGNAL( finished( Attica::BaseJob* ) ), this, SLOT( binaryResolversList( Attica::BaseJob* ) ) );
357
358        job->start();
359    }
360}
361
362
363void
364AtticaManager::resolversList( BaseJob* j )
365{
366    ListJob< Content >* job = static_cast< ListJob< Content >* >( j );
367
368    m_resolvers.append( job->itemList() );
369
370    // Sanity check. if any resolvers are installed that don't exist on the hd, remove them.
371    foreach ( const QString& rId, m_resolverStates.keys() )
372    {
373        if ( m_resolverStates[ rId ].state == Installed ||
374             m_resolverStates[ rId ].state == NeedsUpgrade )
375        {
376            if ( m_resolverStates[ rId ].binary )
377                continue;
378
379            // Guess location on disk
380            QDir dir( QString( "%1/atticaresolvers/%2" ).arg( TomahawkUtils::appDataDir().absolutePath() ).arg( rId ) );
381            if ( !dir.exists() )
382            {
383                // Uh oh
384                qWarning() << "Found attica resolver marked as installed that didn't exist on disk! Setting to uninstalled: " << rId << dir.absolutePath();
385                m_resolverStates[ rId ].state = Uninstalled;
386                TomahawkSettings::instance()->setAtticaResolverState( rId, Uninstalled );
387            }
388        }
389    }
390
391    // load icon cache from disk, and fetch any we are missing
392    loadPixmapsFromCache();
393
394    fetchMissingIcons();
395
396
397    if ( ++m_resolverJobsLoaded == 2 )
398    {
399        qSort( m_resolvers.begin(), m_resolvers.end(), resolverSort );
400        syncServerData();
401        emit resolversLoaded( m_resolvers );
402    }
403}
404
405
406void
407AtticaManager::binaryResolversList( BaseJob* j )
408{
409    ListJob< Content >* job = static_cast< ListJob< Content >* >( j );
410
411    Content::List binaryResolvers = job->itemList();
412
413    QString platform;
414#if defined(Q_OS_MAC)
415    platform = "osx";
416#elif defined(Q_OS_WIN)
417    platform = "win";
418#elif defined(Q_OS_LINUX) && defined(__GNUC__) && defined(__x86_64__)
419    platform = "linux-x64";
420#elif defined(Q_OS_LINUX) // Horrible assumption here...
421    platform = "linux-x86";
422#endif
423
424    // Override if no binary resolvers were requested
425#ifndef WITH_BINARY_ATTICA
426    platform = QString();
427#endif
428
429    foreach ( const Content& c, binaryResolvers )
430    {
431        if ( !c.attribute( "typeid" ).isEmpty() && c.attribute( "typeid" ) == platform )
432        {
433            // We have a binary resolver for this platform
434            qDebug() << "WE GOT A BINARY RESOLVER:" << c.id() << c.name() << c.attribute( "signature" );
435            m_resolvers.append( c );
436            if ( !m_resolverStates.contains( c.id() ) )
437            {
438                Resolver r;
439                r.binary = true;
440                m_resolverStates.insert( c.id(), r );
441            }
442            else if ( m_resolverStates[ c.id() ].binary != true )
443            { // HACK workaround... why is this not set in the first place sometimes? Migration issue?
444                m_resolverStates[ c.id() ].binary = true;
445            }
446
447
448        }
449    }
450
451    if ( ++m_resolverJobsLoaded == 2 )
452    {
453        qSort( m_resolvers.begin(), m_resolvers.end(), resolverSort );
454        syncServerData();
455        emit resolversLoaded( m_resolvers );
456    }
457}
458
459
460void
461AtticaManager::resolverIconFetched()
462{
463    QNetworkReply* reply = qobject_cast< QNetworkReply* >( sender() );
464    Q_ASSERT( reply );
465    reply->deleteLater();
466
467    const QString resolverId = reply->property( "resolverId" ).toString();
468
469    if ( reply->error() != QNetworkReply::NoError )
470    {
471        tLog() << "Failed to fetch resolver icon image:" << reply->errorString();
472        return;
473    }
474
475    QByteArray data = reply->readAll();
476    QPixmap* icon = new QPixmap;
477    icon->loadFromData( data );
478    m_resolverStates[ resolverId ].pixmap = icon;
479    m_resolverStates[ resolverId ].pixmapDirty = true;
480
481    emit resolverIconUpdated( resolverId );
482}
483
484
485void
486AtticaManager::syncServerData()
487{
488    // look for any newer. m_resolvers has list from server, and m_resolverStates will contain any locally installed ones
489    // also update ratings
490    tLog() << "Syncing server data!";
491    foreach ( const QString& id, m_resolverStates.keys() )
492    {
493        Resolver r = m_resolverStates[ id ];
494        for ( int i = 0; i < m_resolvers.size(); i++ )
495        {
496            Attica::Content upstream = m_resolvers[ i ];
497            // same resolver
498            if ( id != upstream.id() )
499                continue;
500
501            // Update our rating with the server's idea of rating if we haven't rated it
502            if ( m_resolverStates[ id ].userRating != -1 )
503            {
504                upstream.setRating( m_resolverStates[ id ].userRating );
505                m_resolvers[ i ] = upstream;
506            }
507
508            // DO we need to upgrade?
509            tDebug( LOGVERBOSE ) << "Upgrade check for" << upstream.id() << "local is" << r.version << "upstream is" << upstream.version() << "and state is: " << r.state;
510            if ( ( r.state == Installed || r.state == NeedsUpgrade ) &&
511                 !upstream.version().isEmpty() )
512            {
513                if ( TomahawkUtils::newerVersion( r.version, upstream.version() ) )
514                {
515                    tLog() << "Doing upgrade of: " << id;
516                    m_resolverStates[ id ].state = NeedsUpgrade;
517                    QMetaObject::invokeMethod( this, "upgradeResolver", Qt::QueuedConnection, Q_ARG( Attica::Content, upstream ) );
518                }
519            }
520        }
521    }
522}
523
524
525void
526AtticaManager::installResolver( const Content& resolver, bool autoCreate )
527{
528    doInstallResolver( resolver, autoCreate, 0 );
529}
530
531
532void
533AtticaManager::installResolverWithHandler( const Content& resolver, Tomahawk::Accounts::AtticaResolverAccount* handler )
534{
535    doInstallResolver( resolver, false, handler );
536}
537
538
539void AtticaManager::doInstallResolver( const Content& resolver, bool autoCreate, Tomahawk::Accounts::AtticaResolverAccount* handler )
540{
541    Q_ASSERT( !resolver.id().isNull() );
542
543    emit startedInstalling( resolver.id() );
544
545    if ( m_resolverStates[ resolver.id() ].state != Upgrading )
546        m_resolverStates[ resolver.id() ].state = Installing;
547
548    m_resolverStates[ resolver.id() ].scriptPath = resolver.attribute( "mainscript" );
549    m_resolverStates[ resolver.id() ].version = resolver.version();
550    emit resolverStateChanged( resolver.id() );
551
552//    ItemJob< DownloadItem >* job = m_resolverProvider.downloadLink( resolver.id() );
553    QUrl url( QString( "%1/resolvers/v1/content/download/%2/1" ).arg( hostname() ).arg( resolver.id() ) );
554
555    TomahawkUtils::urlAddQueryItem( url, "tomahawkversion", TomahawkUtils::appFriendlyVersion() );
556    QNetworkReply* r = Tomahawk::Utils::nam()->get( QNetworkRequest( url ) );
557    NewClosure( r, SIGNAL( finished() ), this, SLOT( resolverDownloadFinished( QNetworkReply* ) ), r );
558    r->setProperty( "resolverId", resolver.id() );
559    r->setProperty( "createAccount", autoCreate );
560    r->setProperty( "handler", QVariant::fromValue< QObject* >( handler ) );
561    r->setProperty( "binarySignature", resolver.attribute("signature"));
562}
563
564
565void
566AtticaManager::upgradeResolver( const Content& resolver )
567{
568    tLog() << "UPGRADING:" << resolver.id() << m_resolverStates[ resolver.id() ].state;
569    Q_ASSERT( m_resolverStates.contains( resolver.id() ) );
570    Q_ASSERT( m_resolverStates[ resolver.id() ].state == NeedsUpgrade );
571
572    if ( !m_resolverStates.contains( resolver.id() ) || m_resolverStates[ resolver.id() ].state != NeedsUpgrade )
573        return;
574
575    m_resolverStates[ resolver.id() ].state = Upgrading;
576    emit resolverStateChanged( resolver.id() );
577
578    uninstallResolver( resolver );
579    installResolver( resolver, false );
580}
581
582
583void
584AtticaManager::resolverDownloadFinished ( QNetworkReply *j )
585{
586    Q_ASSERT( j );
587    if ( !j )
588        return;
589
590    if ( j->error() == QNetworkReply::NoError )
591    {
592        QDomDocument doc;
593        doc.setContent( j );
594
595       const QDomNodeList nodes = doc.documentElement().elementsByTagName( "downloadlink" );
596       if ( nodes.isEmpty() )
597       {
598           tLog() << "Found no download link for resolver:" << doc.toString();
599           return;
600       }
601
602       QUrl url( nodes.item( 0 ).toElement().text() );
603       // download the resolver itself :)
604       tDebug() << "Downloading resolver from url:" << url.toString();
605
606       const QDomNodeList signatures = doc.documentElement().elementsByTagName( "signature" );
607
608       // Use the original signature provided
609       QString signature = j->property( "binarySignature" ).toString();
610       if ( signatures.size() > 0 )
611       {
612            // THis download has an overriding signature. Take that one instead
613           const QString sig = signatures.item( 0 ).toElement().text();
614           tLog() << "Found overridden signature in binary download:" << sig;
615           signature = sig;
616       }
617       QNetworkReply* reply = Tomahawk::Utils::nam()->get( QNetworkRequest( url ) );
618       connect( reply, SIGNAL( finished() ), this, SLOT( payloadFetched() ) );
619       reply->setProperty( "resolverId", j->property( "resolverId" ) );
620       reply->setProperty( "createAccount", j->property( "createAccount" ) );
621       reply->setProperty( "handler", j->property( "handler" ) );
622       reply->setProperty( "binarySignature", signature );
623    }
624    else
625    {
626        tLog() << "Failed to do resolver download job!" << j->errorString() << j->error();
627    }
628}
629
630
631void
632AtticaManager::payloadFetched()
633{
634    QNetworkReply* reply = qobject_cast< QNetworkReply* >( sender() );
635    Q_ASSERT( reply );
636    reply->deleteLater();
637
638    bool installedSuccessfully = false;
639    const QString resolverId = reply->property( "resolverId" ).toString();
640
641    // we got a zip file, save it to a temporary file, then unzip it to our destination data dir
642    if ( reply->error() == QNetworkReply::NoError )
643    {
644        QTemporaryFile* f = new QTemporaryFile( QDir::tempPath() + QDir::separator() + "tomahawkattica_XXXXXX.zip" );
645        if ( !f->open() )
646        {
647            tLog() << "Failed to write zip file to temp file:" << f->fileName();
648            return;
649        }
650        f->write( reply->readAll() );
651        f->close();
652
653        if ( m_resolverStates[ resolverId ].binary )
654        {
655            // First ensure the signature matches. If we can't verify it, abort!
656            const QString signature = reply->property( "binarySignature" ).toString();
657            // Must have a signature for binary resolvers...
658            Q_ASSERT( !signature.isEmpty() );
659            if ( signature.isEmpty() )
660                return;
661            if ( !TomahawkUtils::verifyFile( f->fileName(), signature ) )
662            {
663                qWarning() << "FILE SIGNATURE FAILED FOR BINARY RESOLVER! WARNING! :" << f->fileName() << signature;
664            }
665            else
666            {
667                TomahawkUtils::extractBinaryResolver( f->fileName(), new BinaryInstallerHelper( f, resolverId, reply->property( "createAccount" ).toBool(), this ) );
668                // Don't emit success or failed yet, helper will do that.
669                return;
670            }
671        }
672        else
673        {
674            QDir dir( TomahawkUtils::extractScriptPayload( f->fileName(), resolverId ) );
675            QString resolverPath = dir.absoluteFilePath( m_resolverStates[ resolverId ].scriptPath );
676
677            if ( !resolverPath.isEmpty() )
678            {
679                // update with absolute, not relative, path
680                m_resolverStates[ resolverId ].scriptPath = resolverPath;
681
682                Tomahawk::Accounts::AtticaResolverAccount* handlerAccount = qobject_cast< Tomahawk::Accounts::AtticaResolverAccount* >( reply->property( "handler" ).value< QObject* >() );
683                const bool createAccount = reply->property( "createAccount" ).toBool();
684                if ( handlerAccount )
685                {
686                    handlerAccount->setPath( resolverPath );
687                    Tomahawk::Accounts::AccountManager::instance()->enableAccount( handlerAccount );
688                }
689                else if ( createAccount )
690                {
691                    // Do the install / add to tomahawk
692                    Tomahawk::Accounts::Account* resolver = Tomahawk::Accounts::ResolverAccountFactory::createFromPath( resolverPath, "resolveraccount", true );
693                    Tomahawk::Accounts::AccountManager::instance()->addAccount( resolver );
694                    TomahawkSettings::instance()->addAccount( resolver->accountId() );
695                }
696
697                fetchMissingIcons();
698                installedSuccessfully = true;
699            }
700        }
701
702        delete f;
703    }
704    else
705    {
706        tLog() << "Failed to download attica payload...:" << reply->errorString();
707    }
708
709
710    if ( installedSuccessfully )
711    {
712        tDebug( LOGVERBOSE ) << "Setting installed state to resolver:" << resolverId;
713        m_resolverStates[ resolverId ].state = Installed;
714        TomahawkSettings::instance()->setAtticaResolverStates( m_resolverStates );
715        emit resolverInstalled( resolverId );
716        emit resolverStateChanged( resolverId );
717    }
718    else
719    {
720        emit resolverInstallationFailed( resolverId );
721    }
722}
723
724
725void
726AtticaManager::uninstallResolver( const QString& pathToResolver )
727{
728    // when is this used? find and fix
729    Q_ASSERT(false);
730
731    // User manually removed a resolver not through attica dialog, simple remove
732    QRegExp r( ".*([^/]*)/contents/code/main.js" );
733    r.indexIn( pathToResolver );
734    const QString& atticaId = r.cap( 1 );
735    tDebug() << "Got resolver ID to remove:" << atticaId;
736    if ( !atticaId.isEmpty() ) // this is an attica-installed resolver, mark as uninstalled
737    {
738        foreach ( const Content& resolver, m_resolvers )
739        {
740            if ( resolver.id() == atticaId ) // this is the one
741            {
742                m_resolverStates[ atticaId ].state = Uninstalled;
743                delete m_resolverStates[ resolver.id() ].pixmap;
744                m_resolverStates[ atticaId ].pixmap = 0;
745                TomahawkSettings::instance()->setAtticaResolverState( atticaId, Uninstalled );
746
747                doResolverRemove( atticaId );
748            }
749        }
750    }
751}
752
753
754void
755AtticaManager::uninstallResolver( const Content& resolver )
756{
757    if ( m_resolverStates[ resolver.id() ].state != Upgrading )
758    {
759        emit resolverUninstalled( resolver.id() );
760        emit resolverStateChanged( resolver.id() );
761
762        m_resolverStates[ resolver.id() ].state = Uninstalled;
763        TomahawkSettings::instance()->setAtticaResolverState( resolver.id(), Uninstalled );
764    }
765
766    delete m_resolverStates[ resolver.id() ].pixmap;
767    m_resolverStates[ resolver.id() ].pixmap = 0;
768
769    doResolverRemove( resolver.id() );
770}
771
772
773void
774AtticaManager::doResolverRemove( const QString& id ) const
775{
776    // uninstalling is easy... just delete it! :)
777    QDir resolverDir = TomahawkUtils::appDataDir();
778    if ( !resolverDir.cd( QString( "atticaresolvers/%1" ).arg( id ) ) )
779        return;
780
781    if ( id.isEmpty() )
782        return;
783
784    // sanity check
785    if ( !resolverDir.absolutePath().contains( "atticaresolvers" ) ||
786        !resolverDir.absolutePath().contains( id ) )
787        return;
788
789    TomahawkUtils::removeDirectory( resolverDir.absolutePath() );
790
791    QDir cacheDir = TomahawkUtils::appDataDir();
792    if ( !cacheDir.cd( "atticacache" ) )
793        return;
794
795    const bool removed = cacheDir.remove( id + ".png" );
796    tDebug() << "Tried to remove cached image, succeeded?" << removed << cacheDir.filePath( id );
797}
798