1
0
mirror of https://github.com/tomahawk-player/tomahawk.git synced 2025-03-21 16:29:43 +01:00

Functional downloading, installing, uninstalling, managing

This commit is contained in:
Leo Franchi 2011-09-09 16:33:39 -04:00
parent 158bbff8de
commit 3a7cab851d
9 changed files with 493 additions and 31 deletions

289
src/AtticaManager.cpp Normal file
View File

@ -0,0 +1,289 @@
/* === This file is part of Tomahawk Player - <http://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
* 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 "AtticaManager.h"
#include "utils/logger.h"
#include "tomahawksettings.h"
#include "utils/tomahawkutils.h"
#include <attica/downloaditem.h>
#include <quazip/quazip.h>
#include <quazip/quazipfile.h>
#include <QNetworkReply>
#include <QtCore/qtemporaryfile.h>
#include <QDir>
#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;
}

88
src/AtticaManager.h Normal file
View File

@ -0,0 +1,88 @@
/* === This file is part of Tomahawk Player - <http://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
* 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 ATTICAMANAGER_H
#define ATTICAMANAGER_H
#include <QObject>
#include <attica/provider.h>
#include <attica/providermanager.h>
#include <attica/content.h>
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

View File

@ -24,6 +24,7 @@
#include <QtGui/QPainter>
#include <QApplication>
#include <QMouseEvent>
#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;
}

View File

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

View File

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

View File

@ -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>("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
{

View File

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

View File

@ -53,6 +53,7 @@
#include "ui_stackedsettingsdialog.h"
#include <playlist/dynamic/widgets/LoadingSpinner.h>
#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()

View File

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