From 3a7cab851d9d3a83d3e64c1612f88c673bd8544e Mon Sep 17 00:00:00 2001 From: Leo Franchi Date: Fri, 9 Sep 2011 16:33:39 -0400 Subject: [PATCH] Functional downloading, installing, uninstalling, managing --- src/AtticaManager.cpp | 289 +++++++++++++++++++++++++++ src/AtticaManager.h | 88 ++++++++ src/GetNewStuffDelegate.cpp | 11 +- src/GetNewStuffModel.cpp | 44 +++- src/GetNewStuffModel.h | 10 +- src/libtomahawk/tomahawksettings.cpp | 53 ++++- src/libtomahawk/tomahawksettings.h | 12 +- src/settingsdialog.cpp | 15 ++ src/settingsdialog.h | 2 + 9 files changed, 493 insertions(+), 31 deletions(-) create mode 100644 src/AtticaManager.cpp create mode 100644 src/AtticaManager.h diff --git a/src/AtticaManager.cpp b/src/AtticaManager.cpp new file mode 100644 index 000000000..4bc087a9e --- /dev/null +++ b/src/AtticaManager.cpp @@ -0,0 +1,289 @@ +/* === This file is part of Tomahawk Player - === + * + * Copyright 2010-2011, Leo Franchi + * + * Tomahawk is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * 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 . + */ + +#include "AtticaManager.h" + +#include "utils/logger.h" +#include "tomahawksettings.h" +#include "utils/tomahawkutils.h" + +#include +#include +#include + +#include +#include +#include +#include "tomahawkapp.h" + +using namespace Attica; + +AtticaManager* AtticaManager::s_instance = 0; + +AtticaManager::AtticaManager( QObject* parent ) +{ + connect( &m_manager, SIGNAL( providerAdded( Attica::Provider ) ), this, SLOT( providerAdded( Attica::Provider ) ) ); + + // resolvers + m_manager.addProviderFile( QUrl( "http://bakery.tomahawk-player.org:10480/resolvers/providers.xml" ) ); +} + +AtticaManager::~AtticaManager() +{ + +} + +Content::List +AtticaManager::resolvers() const +{ + return m_resolvers; +} + +AtticaManager::ResolverState +AtticaManager::resolverState ( const Content& resolver ) const +{ + if ( !m_resolverStates.contains( resolver.id() ) ) + { + return AtticaManager::Uninstalled; + } + + return m_resolverStates[ resolver.id() ]; +} + +bool +AtticaManager::resolversLoaded() const +{ + return !m_resolvers.isEmpty(); +} + +QString +AtticaManager::pathFromId( const QString& resolverId ) const +{ + foreach( const Content& content, m_resolvers ) + { + if ( content.id() == resolverId ) + return QString( "%1/%2/contents/code/main.js" ).arg( TomahawkUtils::appDataDir().absolutePath() ).arg( QString( "atticaresolvers/%1" ).arg( resolverId ) ); + } + + return QString(); +} + + +void +AtticaManager::providerAdded( const Provider& provider ) +{ + if ( provider.name() == "Tomahawk Resolvers" ) + { + m_resolverProvider = provider; + + ListJob< Content >* job = m_resolverProvider.searchContents( Category::List(), QString(), Provider::Rating ); + connect( job, SIGNAL( finished( Attica::BaseJob* ) ), this, SLOT( resolversList( Attica::BaseJob* ) ) ); + job->start(); + } +} + +void +AtticaManager::resolversList( BaseJob* j ) +{ + ListJob< Content >* job = static_cast< ListJob< Content >* >( j ); + + m_resolvers = job->itemList(); + m_resolverStates = TomahawkSettings::instance()->atticaResolverStates(); +} + +void +AtticaManager::installResolver( const Content& resolver ) +{ + Q_ASSERT( !resolver.id().isNull() ); + + m_resolverStates[ resolver.id() ] = Installing; + emit resolverStateChanged( resolver.id() ); + + ItemJob< DownloadItem >* job = m_resolverProvider.downloadLink( resolver.id() ); + connect( job, SIGNAL( finished( Attica::BaseJob* ) ), this, SLOT( resolverDownloadFinished( Attica::BaseJob* ) ) ); + job->setProperty( "resolverId", resolver.id() ); + + job->start(); +} + +void +AtticaManager::resolverDownloadFinished ( BaseJob* j ) +{ + ItemJob< DownloadItem >* job = static_cast< ItemJob< DownloadItem >* >( j ); + + if ( job->metadata().error() == Attica::Metadata::NoError ) + { + DownloadItem item = job->result(); + QUrl url = item.url(); + // download the resolver itself :) + QNetworkReply* reply = TomahawkUtils::nam()->get( QNetworkRequest( url ) ); + connect( reply, SIGNAL( finished() ), this, SLOT( payloadFetched() ) ); + reply->setProperty( "resolverId", job->property( "resolverId" ) ); + } + else + { + tLog() << "Failed to do resolver download job!" << job->metadata().error(); + } + + +} + +void +AtticaManager::payloadFetched() +{ + QNetworkReply* reply = qobject_cast< QNetworkReply* >( sender() ); + Q_ASSERT( reply ); + + // we got a zip file, save it to a temporary file, then unzip it to our destination data dir + if ( reply->error() == QNetworkReply::NoError ) + { + QTemporaryFile f( QDir::tempPath() + QDir::separator() + "tomahawkattica_XXXXXX.zip" ); + if ( !f.open() ) + { + tLog() << "Failed to write zip file to temp file:" << f.fileName(); + return; + } + f.write( reply->readAll() ); + f.close(); + + QString resolverId = reply->property( "resolverId" ).toString(); + QString resolverPath = extractPayload( f.fileName(), resolverId ); + + if( !resolverPath.isEmpty() ) + { +// TomahawkApp::instance()->enableScriptResolver( resolverPath ); + m_resolverStates[ resolverId ] = Installed; + TomahawkSettings::instance()->setAtticaResolverState( resolverId, Installed ); + emit resolverInstalled( resolverId ); + emit resolverStateChanged( resolverId ); + } + } + 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 ) ) + { + tLog() << "Failed to QuaZip open:" << zipFile.getZipError(); + return QString(); + } + + if ( !zipFile.goToFirstFile() ) + { + tLog() << "Failed to go to first file in zip archive: " << zipFile.getZipError(); + return QString(); + } + + 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() ); + + // The path is *always* resovlerid/contents/code/main.js + return QString( QFile( resolverDir.absolutePath() + "/contents/code/main.js" ).fileName() ); +} + + +void +AtticaManager::uninstallResolver( const Content& resolver ) +{ + TomahawkApp::instance()->disableScriptResolver( resolver.id() ); + m_resolverStates[ resolver.id() ] = Uninstalled; + + emit resolverUninstalled( resolver.id() ); + emit resolverStateChanged( resolver.id() ); + + // uninstalling is easy... just delete it! :) + QDir resolverDir = TomahawkUtils::appDataDir(); + resolverDir.cd( QString( "atticaresolvers/%1" ).arg( resolver.id() ) ); + removeDirectory( resolverDir.absolutePath() ); + +} + +// taken from util/fileutils.cpp in kdevplatform +bool +AtticaManager::removeDirectory( const QString& dir ) const +{ + const QDir aDir(dir); + + bool has_err = false; + if (aDir.exists()) { + foreach(const QFileInfo& entry, aDir.entryInfoList(QDir::NoDotAndDotDot | QDir::Dirs | QDir::Files | QDir::NoSymLinks)) { + QString path = entry.absoluteFilePath(); + if (entry.isDir()) { + has_err = !removeDirectory(path) || has_err; + } else if (!QFile::remove(path)) { + has_err = true; + } + } + if (!aDir.rmdir(aDir.absolutePath())) { + has_err = true; + } + } + return !has_err; +} \ No newline at end of file diff --git a/src/AtticaManager.h b/src/AtticaManager.h new file mode 100644 index 000000000..e6ce4e3f4 --- /dev/null +++ b/src/AtticaManager.h @@ -0,0 +1,88 @@ +/* === This file is part of Tomahawk Player - === + * + * Copyright 2010-2011, Leo Franchi + * + * Tomahawk is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * 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 . + */ + +#ifndef ATTICAMANAGER_H +#define ATTICAMANAGER_H + +#include +#include +#include +#include + + +class AtticaManager : public QObject +{ + Q_OBJECT +public: + enum ResolverState { + Uninstalled = 0, + Installing, + Installed, + NeedsUpgrade, + Upgrading, + Failed + }; + + typedef QHash< QString, AtticaManager::ResolverState > StateHash; + + static AtticaManager* instance() { + if ( !s_instance ) + s_instance = new AtticaManager(); + + return s_instance; + } + + explicit AtticaManager ( QObject* parent = 0 ); + virtual ~AtticaManager(); + + bool resolversLoaded() const; + + Attica::Content::List resolvers() const; + ResolverState resolverState( const Attica::Content& resolver ) const; + + void installResolver( const Attica::Content& resolver ); + void uninstallResolver( const Attica::Content& resolver ); + QString pathFromId( const QString& resolverId ) const; + +signals: + void resolversReloaded( const Attica::Content::List& resolvers ); + + void resolverStateChanged( const QString& resolverId ); + void resolverInstalled( const QString& resolverId ); + void resolverUninstalled( const QString& resolverId ); + +private slots: + void providerAdded( const Attica::Provider& ); + void resolversList( Attica::BaseJob* ); + void resolverDownloadFinished( Attica::BaseJob* ); + void payloadFetched(); + +private: + QString extractPayload( const QString& filename, const QString& resolverId ) const; + bool removeDirectory( const QString& dir ) const; + + Attica::ProviderManager m_manager; + + Attica::Provider m_resolverProvider; + Attica::Content::List m_resolvers; + StateHash m_resolverStates; + + static AtticaManager* s_instance; +}; + +#endif // ATTICAMANAGER_H diff --git a/src/GetNewStuffDelegate.cpp b/src/GetNewStuffDelegate.cpp index fcf596cde..a86c59082 100644 --- a/src/GetNewStuffDelegate.cpp +++ b/src/GetNewStuffDelegate.cpp @@ -24,6 +24,7 @@ #include #include #include +#include "AtticaManager.h" #define PADDING 4 @@ -103,20 +104,20 @@ GetNewStuffDelegate::paint( QPainter* painter, const QStyleOptionViewItem& optio // Go from right edge now, stars, install button, and downloaded info // install / status button - GetNewStuffModel::States state = static_cast< GetNewStuffModel::States >( index.data( GetNewStuffModel::StateRole ).toInt() ); + AtticaManager::ResolverState state = static_cast< AtticaManager::ResolverState >( index.data( GetNewStuffModel::StateRole ).toInt() ); QString actionText; switch( state ) { - case GetNewStuffModel::Uninstalled: + case AtticaManager::Uninstalled: actionText = tr( "Install" ); break; - case GetNewStuffModel::Installing: + case AtticaManager::Installing: actionText = tr( "Installing" ); break; - case GetNewStuffModel::Failed: + case AtticaManager::Failed: actionText = tr( "Failed" ); break; - case GetNewStuffModel::Installed: + case AtticaManager::Installed: actionText = tr( "Uninstall" ); break; } diff --git a/src/GetNewStuffModel.cpp b/src/GetNewStuffModel.cpp index f4e935fbf..58ebc1666 100644 --- a/src/GetNewStuffModel.cpp +++ b/src/GetNewStuffModel.cpp @@ -28,12 +28,12 @@ GetNewStuffModel::GetNewStuffModel( QObject* parent ) : QAbstractListModel ( parent ) { - m_clicked = false; if ( AtticaManager::instance()->resolversLoaded() ) m_contentList = AtticaManager::instance()->resolvers(); connect( AtticaManager::instance(), SIGNAL( resolversReloaded( Attica::Content::List ) ), this, SLOT( resolversReloaded( Attica::Content::List ) ) ); + connect( AtticaManager::instance(), SIGNAL( resolverStateChanged( QString ) ), this, SLOT( resolverStateChanged( QString ) ) ); } @@ -49,6 +49,20 @@ GetNewStuffModel::resolversReloaded( const Attica::Content::List& resolvers ) endResetModel(); } +void +GetNewStuffModel::resolverStateChanged( const QString& resolverId ) +{ + for ( int i = 0; i < m_contentList.count(); i++ ) + { + const Attica::Content resolver = m_contentList[ i ]; + if ( resolver.id() == resolverId ) + { + QModelIndex idx = index( i, 0, QModelIndex() ); + emit dataChanged( idx, idx ); + } + } +} + QVariant GetNewStuffModel::data( const QModelIndex& index, int role ) const @@ -80,7 +94,7 @@ GetNewStuffModel::data( const QModelIndex& index, int role ) const case AuthorRole: return resolver.author(); case StateRole: - return m_clicked ? Installed : Uninstalled; + return (int)AtticaManager::instance()->resolverState( resolver ); } return QVariant(); } @@ -94,8 +108,32 @@ GetNewStuffModel::rowCount( const QModelIndex& parent ) const bool GetNewStuffModel::setData( const QModelIndex &index, const QVariant &value, int role ) { + if ( !hasIndex( index.row(), index.column(), index.parent() ) ) + return false; + // the install/uninstall button was clicked - m_clicked = !m_clicked; + const Attica::Content resolver = m_contentList[ index.row() ]; + + AtticaManager::ResolverState state = AtticaManager::instance()->resolverState( resolver ); + + switch( state ) + { + case AtticaManager::Uninstalled: + // install + AtticaManager::instance()->installResolver( resolver ); + break; + case AtticaManager::Installing: + case AtticaManager::Upgrading: + // Do nothing, busy + break; + case AtticaManager::Installed: + // Uninstall + AtticaManager::instance()->uninstallResolver( resolver ); + break; + case AtticaManager::NeedsUpgrade: + // TODO + break; + }; emit dataChanged( index, index ); return true; diff --git a/src/GetNewStuffModel.h b/src/GetNewStuffModel.h index b7f4d6401..a44e3f274 100644 --- a/src/GetNewStuffModel.h +++ b/src/GetNewStuffModel.h @@ -44,13 +44,6 @@ public: ResolverType = 0, }; - enum States { - Uninstalled = 0, - Installing = 1, - Failed = 2, - Installed = 3 - }; - explicit GetNewStuffModel( QObject* parent = 0 ); virtual ~GetNewStuffModel(); @@ -60,10 +53,9 @@ public: private slots: void resolversReloaded( const Attica::Content::List& ); + void resolverStateChanged( const QString& resolverId ); private: - bool m_clicked; - Attica::Content::List m_contentList; }; diff --git a/src/libtomahawk/tomahawksettings.cpp b/src/libtomahawk/tomahawksettings.cpp index f1e9ff582..d857001ba 100644 --- a/src/libtomahawk/tomahawksettings.cpp +++ b/src/libtomahawk/tomahawksettings.cpp @@ -36,6 +36,28 @@ using namespace Tomahawk; TomahawkSettings* TomahawkSettings::s_instance = 0; +inline QDataStream& operator<<(QDataStream& out, const AtticaManager::StateHash& states) +{ + out << (quint32)states.count(); + foreach( const QString& key, states.keys() ) + out << key << (qint32)states[ key ]; + return out; +} + +inline QDataStream& operator>>(QDataStream& in, AtticaManager::StateHash& states) +{ + quint32 count = 0; + in >> count; + for ( uint i = 0; i < count; i++ ) + { + QString key; + qint32 val; + in >> key; + in >> val; + states[ key ] = (AtticaManager::ResolverState)val; + } + return in; +} TomahawkSettings* TomahawkSettings::instance() @@ -70,6 +92,9 @@ TomahawkSettings::TomahawkSettings( QObject* parent ) // insert upgrade code here as required setValue( "configversion", VERSION ); } + + qRegisterMetaType< AtticaManager::StateHash >( "AtticaManager::StateHash" ); + qRegisterMetaTypeStreamOperators("AtticaManager::StateHash"); } @@ -849,28 +874,34 @@ TomahawkSettings::setEnabledScriptResolvers( const QStringList& resolvers ) } void -TomahawkSettings::addAtticaInstalledResolver( const QString& resolver ) +TomahawkSettings::setAtticaResolverState( const QString& resolver, AtticaManager::ResolverState state ) { - QStringList resolvers = value( "script/atticainstalled", QStringList() ).toStringList(); - resolvers << resolver; - setValue( "script/atticainstalled", resolvers ); + AtticaManager::StateHash resolvers = value( "script/resolverstates" ).value< AtticaManager::StateHash >(); + resolvers.insert( resolver, state ); + setValue( "script/atticaresolverstates", QVariant::fromValue< AtticaManager::StateHash >( resolvers ) ); } -QStringList -TomahawkSettings::atticaInstalledResolvers() const +AtticaManager::StateHash +TomahawkSettings::atticaResolverStates() const { - return value( "script/atticainstalled", QString() ).toStringList(); + return value( "script/atticaresolverstates" ).value< AtticaManager::StateHash >(); } void -TomahawkSettings::removeAtticaInstalledResolver( const QString& resolver ) +TomahawkSettings::setAtticaResolverStates( const AtticaManager::StateHash states ) { - QStringList resolvers = value( "script/atticainstalled", QStringList() ).toStringList(); - resolvers.removeAll( resolver ); - setValue( "script/atticainstalled", resolvers ); + setValue( "script/atticaresolverstates", QVariant::fromValue< AtticaManager::StateHash >( states ) ); } +void +TomahawkSettings::removeAtticaResolverState ( const QString& resolver ) +{ + AtticaManager::StateHash resolvers = value( "script/atticaresolverstates" ).value< AtticaManager::StateHash >(); + resolvers.remove( resolver ); + setValue( "script/atticaresolverstates", QVariant::fromValue< AtticaManager::StateHash >( resolvers ) ); +} + QString TomahawkSettings::scriptDefaultPath() const { diff --git a/src/libtomahawk/tomahawksettings.h b/src/libtomahawk/tomahawksettings.h index a6b108b92..a90f0da82 100644 --- a/src/libtomahawk/tomahawksettings.h +++ b/src/libtomahawk/tomahawksettings.h @@ -23,8 +23,10 @@ #include "dllmacro.h" +#include "AtticaManager.h" #include "playlist.h" +class AtticaManager; /** * Convenience wrapper around QSettings for tomahawk-specific config */ @@ -184,9 +186,11 @@ public: QStringList enabledScriptResolvers() const; void setEnabledScriptResolvers( const QStringList& resolvers ); - QStringList atticaInstalledResolvers() const; - void addAtticaInstalledResolver( const QString& resolver ); - void removeAtticaInstalledResolver( const QString& resolver ); + AtticaManager::StateHash atticaResolverStates() const; + void setAtticaResolverStates( const AtticaManager::StateHash states ); + + void setAtticaResolverState( const QString& resolver, AtticaManager::ResolverState state ); + void removeAtticaResolverState( const QString& resolver ); QString scriptDefaultPath() const; void setScriptDefaultPath( const QString& path ); @@ -210,4 +214,6 @@ private: static TomahawkSettings* s_instance; }; +Q_DECLARE_METATYPE(AtticaManager::StateHash); + #endif diff --git a/src/settingsdialog.cpp b/src/settingsdialog.cpp index 519667392..d6a606478 100644 --- a/src/settingsdialog.cpp +++ b/src/settingsdialog.cpp @@ -53,6 +53,7 @@ #include "ui_stackedsettingsdialog.h" #include #include "GetNewStuffDialog.h" +#include "AtticaManager.h" static QString md5( const QByteArray& src ) @@ -194,6 +195,8 @@ SettingsDialog::SettingsDialog( QWidget *parent ) connect( ui->scriptList->selectionModel(), SIGNAL( selectionChanged( QItemSelection,QItemSelection ) ), this, SLOT( scriptSelectionChanged() ) ); connect( ui->addScript, SIGNAL( clicked( bool ) ), this, SLOT( addScriptResolver() ) ); connect( ui->removeScript, SIGNAL( clicked( bool ) ), this, SLOT( removeScriptResolver() ) ); + connect( AtticaManager::instance(), SIGNAL( resolverInstalled( QString ) ), this, SLOT( atticaResolverInstalled( QString ) ) ); + connect( AtticaManager::instance(), SIGNAL( resolverUninstalled( QString ) ), this, SLOT( atticaResolverUninstalled( QString ) ) ); connect( ui->buttonBrowse_2, SIGNAL( clicked() ), SLOT( showPathSelector() ) ); connect( ui->proxyButton, SIGNAL( clicked() ), SLOT( showProxySettings() ) ); @@ -555,6 +558,18 @@ SettingsDialog::getMoreResolvers() } +void +SettingsDialog::atticaResolverInstalled( const QString& resolverId ) +{ + m_resolversModel->addResolver( AtticaManager::instance()->pathFromId( resolverId ) ); +} + +void +SettingsDialog::atticaResolverUninstalled ( const QString& resolverId ) +{ + m_resolversModel->removeResolver( AtticaManager::instance()->pathFromId( resolverId ) ); +} + void SettingsDialog::scriptSelectionChanged() diff --git a/src/settingsdialog.h b/src/settingsdialog.h index 6a990079b..5ea91345c 100644 --- a/src/settingsdialog.h +++ b/src/settingsdialog.h @@ -86,6 +86,8 @@ private slots: void removeScriptResolver(); void getMoreResolvers(); void getMoreResolversFinished( int ); + void atticaResolverInstalled( const QString& ); + void atticaResolverUninstalled( const QString& ); void openResolverConfig( const QString& ); void sipItemClicked ( const QModelIndex& );