diff --git a/data/js/tomahawk.js b/data/js/tomahawk.js index 9369baaa6..db15be3fa 100644 --- a/data/js/tomahawk.js +++ b/data/js/tomahawk.js @@ -615,6 +615,12 @@ Tomahawk.PluginManager = { Tomahawk.registerScriptPlugin(type, object.id); }, + unregisterPlugin: function(type, object) { + this.objects[this.identifyObject(object)] = object; + + Tomahawk.log("unregisterPlugin: " + type + " id: " + object.id); + Tomahawk.unregisterScriptPlugin(type, object.id); + }, invokeSync: function (objectId, methodName, params) { if (!this.objects[objectId]) { diff --git a/src/libtomahawk/Result.cpp b/src/libtomahawk/Result.cpp index 578241557..486b5fc1b 100644 --- a/src/libtomahawk/Result.cpp +++ b/src/libtomahawk/Result.cpp @@ -303,6 +303,8 @@ Result::setResolvedByCollection( const Tomahawk::collection_ptr& collection , bo m_collection = collection; if ( emitOnlineEvents ) { + Q_ASSERT( !collection.isNull() ); + connect( collection.data(), SIGNAL( destroyed( QObject * ) ), SLOT( onOffline() ), Qt::QueuedConnection ); connect( collection->source().data(), SIGNAL( online() ), SLOT( onOnline() ), Qt::QueuedConnection ); connect( collection->source().data(), SIGNAL( offline() ), SLOT( onOffline() ), Qt::QueuedConnection ); } diff --git a/src/libtomahawk/SourceList.h b/src/libtomahawk/SourceList.h index cbed603ba..34874f832 100644 --- a/src/libtomahawk/SourceList.h +++ b/src/libtomahawk/SourceList.h @@ -52,6 +52,8 @@ public: QList sources( bool onlyOnline = false ) const; unsigned int count() const; + void addScriptCollection( const Tomahawk::collection_ptr& collection ); + void removeScriptCollection( const Tomahawk::collection_ptr& collection ); QList scriptCollections() const; Tomahawk::source_ptr get( const QString& username, const QString& friendlyName = QString(), bool autoCreate = false ); @@ -81,9 +83,6 @@ private slots: void latchedOn( const Tomahawk::source_ptr& ); void latchedOff( const Tomahawk::source_ptr& ); - void addScriptCollection( const Tomahawk::collection_ptr& collection ); - void removeScriptCollection( const Tomahawk::collection_ptr& collection ); - private: void add( const Tomahawk::source_ptr& source ); diff --git a/src/libtomahawk/resolvers/ExternalResolver.h b/src/libtomahawk/resolvers/ExternalResolver.h index 9a553d0bd..57a1eeb74 100644 --- a/src/libtomahawk/resolvers/ExternalResolver.h +++ b/src/libtomahawk/resolvers/ExternalResolver.h @@ -25,9 +25,6 @@ #include "DllMacro.h" #include "Resolver.h" #include "ScriptCommandQueue.h" -#include "ScriptCommand_AllArtists.h" -#include "ScriptCommand_AllAlbums.h" -#include "ScriptCommand_AllTracks.h" #include "ScriptCommand_LookupUrl.h" #include "Typedefs.h" @@ -49,9 +46,6 @@ class DLLEXPORT ExternalResolver : public Resolver Q_OBJECT friend class ScriptCommandQueue; - friend class ScriptCommand_AllArtists; - friend class ScriptCommand_AllAlbums; - friend class ScriptCommand_AllTracks; friend class ScriptCommand_LookupUrl; public: @@ -120,10 +114,6 @@ protected: ScriptCommandQueue* m_commandQueue; // Should only be called by ScriptCommands - // ScriptCollection - virtual void artists( const Tomahawk::collection_ptr& collection ) = 0; - virtual void albums( const Tomahawk::collection_ptr& collection, const Tomahawk::artist_ptr& artist ) = 0; - virtual void tracks( const Tomahawk::collection_ptr& collection, const Tomahawk::album_ptr& album ) = 0; // UrlLookup virtual void lookupUrl( const QString& url ) = 0; diff --git a/src/libtomahawk/resolvers/JSResolver.cpp b/src/libtomahawk/resolvers/JSResolver.cpp index 043407f65..bb79e7f0d 100644 --- a/src/libtomahawk/resolvers/JSResolver.cpp +++ b/src/libtomahawk/resolvers/JSResolver.cpp @@ -48,11 +48,9 @@ #include #include #include -#include #include #include #include -#include #include using namespace Tomahawk; @@ -68,6 +66,8 @@ JSResolver::JSResolver( const QString& accountId, const QString& scriptPath, con d->name = QFileInfo( filePath() ).baseName(); d->scriptAccount.reset( new JSAccount( d->name ) ); d->scriptAccount->setResolver( this ); + d->scriptAccount->setFilePath( filePath() ); + d->scriptAccount->setIcon( icon( QSize( 0, 0 ) ) ); // set the icon, if we launch properly we'll get the icon the resolver reports d->icon = TomahawkUtils::defaultPixmap( TomahawkUtils::DefaultResolver, TomahawkUtils::Original, QSize( 128, 128 ) ); @@ -312,115 +312,6 @@ JSResolver::start() } -void -JSResolver::artists( const Tomahawk::collection_ptr& collection ) -{ - if ( QThread::currentThread() != thread() ) - { - QMetaObject::invokeMethod( this, "artists", Qt::QueuedConnection, Q_ARG( Tomahawk::collection_ptr, collection ) ); - return; - } - - Q_D( const JSResolver ); - - if ( /* !m_collections.contains( collection->name() ) || */ //if the collection doesn't belong to this resolver - !d->capabilities.testFlag( Browsable ) ) //or this resolver doesn't even support collections - { - emit artistsFound( QList< Tomahawk::artist_ptr >() ); - return; - } - - QString eval = QString( "artists( '%1' )" ) - .arg( JSAccount::escape( collection->name() ) ); - - QVariantMap m = callOnResolver( eval ).toMap(); - if ( m.isEmpty() ) - { - // if the resolver doesn't return anything, async api is used - return; - } - - QString errorMessage = tr( "Script Resolver Warning: API call %1 returned data synchronously." ).arg( eval ); - JobStatusView::instance()->model()->addJob( new ErrorStatusMessage( errorMessage ) ); - tDebug() << errorMessage << m; -} - - -void -JSResolver::albums( const Tomahawk::collection_ptr& collection, const Tomahawk::artist_ptr& artist ) -{ - if ( QThread::currentThread() != thread() ) - { - QMetaObject::invokeMethod( this, "albums", Qt::QueuedConnection, - Q_ARG( Tomahawk::collection_ptr, collection ), - Q_ARG( Tomahawk::artist_ptr, artist ) ); - return; - } - - Q_D( const JSResolver ); - - if ( /* !m_collections.contains( collection->name() ) || */ //if the collection doesn't belong to this resolver - !d->capabilities.testFlag( Browsable ) ) //or this resolver doesn't even support collections - { - emit albumsFound( QList< Tomahawk::album_ptr >() ); - return; - } - - QString eval = QString( "albums( '%1', '%2' )" ) - .arg( JSAccount::escape( collection->name() ) ) - .arg( JSAccount::escape( artist->name() ) ); - - QVariantMap m = callOnResolver( eval ).toMap(); - if ( m.isEmpty() ) - { - // if the resolver doesn't return anything, async api is used - return; - } - - QString errorMessage = tr( "Script Resolver Warning: API call %1 returned data synchronously." ).arg( eval ); - JobStatusView::instance()->model()->addJob( new ErrorStatusMessage( errorMessage ) ); - tDebug() << errorMessage << m; -} - - -void -JSResolver::tracks( const Tomahawk::collection_ptr& collection, const Tomahawk::album_ptr& album ) -{ - if ( QThread::currentThread() != thread() ) - { - QMetaObject::invokeMethod( this, "tracks", Qt::QueuedConnection, - Q_ARG( Tomahawk::collection_ptr, collection ), - Q_ARG( Tomahawk::album_ptr, album ) ); - return; - } - - Q_D( const JSResolver ); - - if ( /* !m_collections.contains( collection->name() ) || */ //if the collection doesn't belong to this resolver - !d->capabilities.testFlag( Browsable ) ) //or this resolver doesn't even support collections - { - emit tracksFound( QList< Tomahawk::query_ptr >() ); - return; - } - - QString eval = QString( "tracks( '%1', '%2', '%3' )" ) - .arg( JSAccount::escape( collection->name() ) ) - .arg( JSAccount::escape( album->artist()->name() ) ) - .arg( JSAccount::escape( album->name() ) ); - - QVariantMap m = callOnResolver( eval ).toMap(); - if ( m.isEmpty() ) - { - // if the resolver doesn't return anything, async api is used - return; - } - - QString errorMessage = tr( "Script Resolver Warning: API call %1 returned data synchronously." ).arg( eval ); - JobStatusView::instance()->model()->addJob( new ErrorStatusMessage( errorMessage ) ); - tDebug() << errorMessage << m; -} - - bool JSResolver::canParseUrl( const QString& url, UrlType type ) { @@ -518,140 +409,6 @@ JSResolver::resolve( const Tomahawk::query_ptr& query ) } QVariantMap m = callOnResolver( eval ).toMap(); - if ( m.isEmpty() ) - { - // if the resolver doesn't return anything, async api is used - return; - } - - qDebug() << "JavaScript Result:" << m; - - const QString qid = query->id(); - const QVariantList reslist = m.value( "results" ).toList(); - - QList< Tomahawk::result_ptr > results = parseResultVariantList( reslist ); - - Tomahawk::Pipeline::instance()->reportResults( qid, results ); -} - - -QList< Tomahawk::result_ptr > -JSResolver::parseResultVariantList( const QVariantList& reslist ) -{ - QList< Tomahawk::result_ptr > results; - - foreach( const QVariant& rv, reslist ) - { - QVariantMap m = rv.toMap(); - // TODO we need to handle preview urls separately. they should never trump a real url, and we need to display - // the purchaseUrl for the user to upgrade to a full stream. - if ( m.value( "preview" ).toBool() == true ) - continue; - - int duration = m.value( "duration", 0 ).toInt(); - if ( duration <= 0 && m.contains( "durationString" ) ) - { - QTime time = QTime::fromString( m.value( "durationString" ).toString(), "hh:mm:ss" ); - duration = time.secsTo( QTime( 0, 0 ) ) * -1; - } - - Tomahawk::track_ptr track = Tomahawk::Track::get( m.value( "artist" ).toString(), - m.value( "track" ).toString(), - m.value( "album" ).toString(), - m.value( "albumArtist" ).toString(), - duration, - QString(), - m.value( "albumpos" ).toUInt(), - m.value( "discnumber" ).toUInt() ); - if ( !track ) - continue; - - Tomahawk::result_ptr rp = Tomahawk::Result::get( m.value( "url" ).toString(), track ); - if ( !rp ) - continue; - - rp->setBitrate( m.value( "bitrate" ).toUInt() ); - rp->setSize( m.value( "size" ).toUInt() ); - rp->setRID( uuid() ); - rp->setFriendlySource( name() ); - rp->setPurchaseUrl( m.value( "purchaseUrl" ).toString() ); - rp->setLinkUrl( m.value( "linkUrl" ).toString() ); - rp->setScore( m.value( "score" ).toFloat() ); - rp->setChecked( m.value( "checked" ).toBool() ); - - //FIXME - if ( m.contains( "year" ) ) - { - QVariantMap attr; - attr[ "releaseyear" ] = m.value( "year" ); -// rp->track()->setAttributes( attr ); - } - - rp->setMimetype( m.value( "mimetype" ).toString() ); - if ( rp->mimetype().isEmpty() ) - { - rp->setMimetype( TomahawkUtils::extensionToMimetype( m.value( "extension" ).toString() ) ); - Q_ASSERT( !rp->mimetype().isEmpty() ); - } - - rp->setResolvedByResolver( this ); - - - // find collection - const QString collectionId = m.value( "collectionId" ).toString(); - if ( !collectionId.isEmpty() ) - { - Tomahawk::collection_ptr collection = Tomahawk::collection_ptr(); - if ( !collection.isNull() ) - { - rp->setResolvedByCollection( collection ); - } - } - - results << rp; - } - - return results; -} - - -QList< Tomahawk::artist_ptr > -JSResolver::parseArtistVariantList( const QVariantList& reslist ) -{ - QList< Tomahawk::artist_ptr > results; - - foreach( const QVariant& rv, reslist ) - { - const QString val = rv.toString(); - if ( val.trimmed().isEmpty() ) - continue; - - Tomahawk::artist_ptr ap = Tomahawk::Artist::get( val, false ); - - results << ap; - } - - return results; -} - - -QList< Tomahawk::album_ptr > -JSResolver::parseAlbumVariantList( const Tomahawk::artist_ptr& artist, const QVariantList& reslist ) -{ - QList< Tomahawk::album_ptr > results; - - foreach( const QVariant& rv, reslist ) - { - const QString val = rv.toString(); - if ( val.trimmed().isEmpty() ) - continue; - - Tomahawk::album_ptr ap = Tomahawk::Album::get( artist, val, false ); - - results << ap; - } - - return results; } @@ -662,6 +419,7 @@ JSResolver::stop() d->stopped = true; + scriptAccount()->stop(); Tomahawk::Pipeline::instance()->removeResolver( this ); emit stopped(); @@ -738,6 +496,15 @@ JSResolver::loadDataFromWidgets() } +ScriptAccount* +JSResolver::scriptAccount() const +{ + Q_D( const JSResolver ); + + return d->scriptAccount.get(); +} + + void JSResolver::onCapabilitiesChanged( Tomahawk::ExternalResolver::Capabilities capabilities ) { @@ -747,30 +514,6 @@ JSResolver::onCapabilitiesChanged( Tomahawk::ExternalResolver::Capabilities capa } -void -JSResolver::onCollectionIconFetched() -{ - QNetworkReply* reply = qobject_cast< QNetworkReply* >( sender() ); - if ( reply != 0 ) - { - Tomahawk::collection_ptr collection; - /* collection = m_collections.value( reply->property( "collectionName" ).toString() ); */ - if ( !collection.isNull() ) - { - if ( reply->error() == QNetworkReply::NoError ) - { - QImageReader imageReader( reply ); - QPixmap collectionIcon = QPixmap::fromImageReader( &imageReader ); - - if ( !collectionIcon.isNull() ) - qobject_cast< Tomahawk::ScriptCollection* >( collection.data() )->setIcon( collectionIcon ); - } - } - reply->deleteLater(); - } -} - - QVariantMap JSResolver::resolverSettings() { diff --git a/src/libtomahawk/resolvers/JSResolver.h b/src/libtomahawk/resolvers/JSResolver.h index c13ba7f06..2f0b301dd 100644 --- a/src/libtomahawk/resolvers/JSResolver.h +++ b/src/libtomahawk/resolvers/JSResolver.h @@ -73,15 +73,13 @@ public: QVariantMap loadDataFromWidgets(); + ScriptAccount* scriptAccount() const; + public slots: void resolve( const Tomahawk::query_ptr& query ) override; void stop() override; void start() override; - // For ScriptCollection - void artists( const Tomahawk::collection_ptr& collection ) override; - void albums( const Tomahawk::collection_ptr& collection, const Tomahawk::artist_ptr& artist ) override; - void tracks( const Tomahawk::collection_ptr& collection, const Tomahawk::album_ptr& album ) override; // For UrlLookup void lookupUrl( const QString& url ) override; @@ -91,9 +89,6 @@ signals: protected: QVariant callOnResolver( const QString& scriptSource ); -private slots: - void onCollectionIconFetched(); - private: void init(); @@ -105,11 +100,6 @@ private: QVariantMap resolverUserConfig(); QVariantMap resolverInit(); - QList< Tomahawk::result_ptr > parseResultVariantList( const QVariantList& reslist ); - QList< Tomahawk::artist_ptr > parseArtistVariantList( const QVariantList& reslist ); - QList< Tomahawk::album_ptr > parseAlbumVariantList( const Tomahawk::artist_ptr& artist, - const QVariantList& reslist ); - Q_DECLARE_PRIVATE( JSResolver ) QScopedPointer d_ptr; }; diff --git a/src/libtomahawk/resolvers/JSResolverHelper.cpp b/src/libtomahawk/resolvers/JSResolverHelper.cpp index 684486af5..8734163cb 100644 --- a/src/libtomahawk/resolvers/JSResolverHelper.cpp +++ b/src/libtomahawk/resolvers/JSResolverHelper.cpp @@ -41,6 +41,10 @@ #include "SourceList.h" #include "UrlHandler.h" #include "JSAccount.h" +#include "../Album.h" +#include "../Artist.h" +#include "../Result.h" +#include "../Track.h" #include #include @@ -133,8 +137,18 @@ JSResolverHelper::log( const QString& message ) void JSResolverHelper::addTrackResults( const QVariantMap& results ) { - qDebug() << "Resolver reporting results:" << results; - QList< Tomahawk::result_ptr > tracks = m_resolver->parseResultVariantList( results.value("results").toList() ); + tLog() << "Resolver reporting results:" << m_resolver->name() << results; + + Q_ASSERT( results["results"].toMap().isEmpty() ); + + QList< Tomahawk::result_ptr > tracks = m_resolver->scriptAccount()->parseResultVariantList( results.value("results").toList() ); + + foreach( const result_ptr& track, tracks ) + { + tLog() << "Found result: " << track->track()->track() << "by" << track->track()->artist(); + track->setResolvedByResolver( m_resolver ); + track->setFriendlySource( "FOOBAR" ); + } QString qid = results.value("qid").toString(); @@ -142,86 +156,6 @@ JSResolverHelper::addTrackResults( const QVariantMap& results ) } -void -JSResolverHelper::addArtistResults( const QVariantMap& results ) -{ - qDebug() << "Resolver reporting artists:" << results; - QList< Tomahawk::artist_ptr > artists = m_resolver->parseArtistVariantList( results.value( "artists" ).toList() ); - - QString qid = results.value("qid").toString(); - - Tomahawk::collection_ptr collection = Tomahawk::collection_ptr(); - if ( collection.isNull() ) - return; - - tDebug() << Q_FUNC_INFO << "about to push" << artists.count() << "artists"; - foreach( const Tomahawk::artist_ptr& artist, artists) - tDebug() << artist->name(); - - emit m_resolver->artistsFound( artists ); -} - - -void -JSResolverHelper::addAlbumResults( const QVariantMap& results ) -{ - qDebug() << "Resolver reporting albums:" << results; - QString artistName = results.value( "artist" ).toString(); - if ( artistName.trimmed().isEmpty() ) - return; - Tomahawk::artist_ptr artist = Tomahawk::Artist::get( artistName, false ); - QList< Tomahawk::album_ptr > albums = m_resolver->parseAlbumVariantList( artist, results.value( "albums" ).toList() ); - - QString qid = results.value("qid").toString(); - - Tomahawk::collection_ptr collection = Tomahawk::collection_ptr(); - if ( collection.isNull() ) - return; - - tDebug() << Q_FUNC_INFO << "about to push" << albums.count() << "albums"; - foreach( const Tomahawk::album_ptr& album, albums) - tDebug() << album->name(); - - emit m_resolver->albumsFound( albums ); -} - - -void -JSResolverHelper::addAlbumTrackResults( const QVariantMap& results ) -{ - qDebug() << "Resolver reporting album tracks:" << results; - QString artistName = results.value( "artist" ).toString(); - if ( artistName.trimmed().isEmpty() ) - return; - QString albumName = results.value( "album" ).toString(); - if ( albumName.trimmed().isEmpty() ) - return; - - Tomahawk::artist_ptr artist = Tomahawk::Artist::get( artistName, false ); - Tomahawk::album_ptr album = Tomahawk::Album::get( artist, albumName, false ); - - QList< Tomahawk::result_ptr > tracks = m_resolver->parseResultVariantList( results.value("results").toList() ); - - QString qid = results.value("qid").toString(); - - Tomahawk::collection_ptr collection = Tomahawk::collection_ptr(); - if ( collection.isNull() ) - return; - - QList< Tomahawk::query_ptr > queries; - foreach ( const Tomahawk::result_ptr& result, tracks ) - { - result->setScore( 1.0 ); - result->setResolvedByCollection( collection ); - queries.append( result->toQuery() ); - } - - tDebug() << Q_FUNC_INFO << "about to push" << tracks.count() << "tracks"; - - emit m_resolver->tracksFound( queries ); -} - - query_ptr JSResolverHelper::parseTrack( const QVariantMap& track ) { @@ -396,12 +330,19 @@ JSResolverHelper::reportScriptJobResults( const QVariantMap& result ) void -JSResolverHelper::registerScriptPlugin(const QString& type, const QString& objectId) +JSResolverHelper::registerScriptPlugin( const QString& type, const QString& objectId ) { m_resolver->d_func()->scriptAccount->registerScriptPlugin( type, objectId ); } +void +JSResolverHelper::unregisterScriptPlugin( const QString& type, const QString& objectId ) +{ + m_resolver->d_func()->scriptAccount->unregisterScriptPlugin( type, objectId ); +} + + void JSResolverHelper::tracksAdded( const QList&, const ModelMode, const collection_ptr&) { diff --git a/src/libtomahawk/resolvers/JSResolverHelper.h b/src/libtomahawk/resolvers/JSResolverHelper.h index 1dec71510..ebb709145 100644 --- a/src/libtomahawk/resolvers/JSResolverHelper.h +++ b/src/libtomahawk/resolvers/JSResolverHelper.h @@ -143,10 +143,6 @@ public slots: void addTrackResults( const QVariantMap& results ); - void addArtistResults( const QVariantMap& results ); - void addAlbumResults( const QVariantMap& results ); - void addAlbumTrackResults( const QVariantMap& results ); - void addUrlResult( const QString& url, const QVariantMap& result ); void reportCapabilities( const QVariant& capabilities ); @@ -154,6 +150,7 @@ public slots: void reportScriptJobResults( const QVariantMap& result ); void registerScriptPlugin( const QString& type, const QString& objectId ); + void unregisterScriptPlugin( const QString& type, const QString& objectId ); private slots: void gotStreamUrl( IODeviceCallback callback, NetworkReply* reply ); diff --git a/src/libtomahawk/resolvers/ScriptAccount.cpp b/src/libtomahawk/resolvers/ScriptAccount.cpp index 778b12e9b..8afed7153 100644 --- a/src/libtomahawk/resolvers/ScriptAccount.cpp +++ b/src/libtomahawk/resolvers/ScriptAccount.cpp @@ -26,6 +26,15 @@ #include "../utils/LinkGenerator.h" #include "ScriptLinkGeneratorPlugin.h" #include "ScriptInfoPlugin.h" +#include "SourceList.h" +#include "ScriptCollection.h" + +// TODO: +#include "../Result.h" +#include "../Track.h" +#include + +#include using namespace Tomahawk; @@ -37,6 +46,51 @@ ScriptAccount::ScriptAccount( const QString& name ) } +void +ScriptAccount::stop() +{ + foreach( const QWeakPointer< ScriptCollection >& collection, m_collections.hash().values() ) + { + unregisterScriptPlugin( "collection", collection.data()->scriptObject()->id() ); + } +} + + +const QString +ScriptAccount::name() const +{ + return m_name; +} + + +void +ScriptAccount::setIcon(const QPixmap& icon) +{ + m_icon = icon; +} + + +const QPixmap +ScriptAccount::icon() const +{ + return m_icon; +} + + +void +ScriptAccount::setFilePath( const QString& filePath ) +{ + m_filePath = filePath; +} + + +const QString +ScriptAccount::filePath() const +{ + return m_filePath; +} + + static QString requestIdGenerator() { @@ -102,6 +156,33 @@ ScriptAccount::registerScriptPlugin( const QString& type, const QString& objectI } + +void +ScriptAccount::unregisterScriptPlugin( const QString& type, const QString& objectId ) +{ + scriptobject_ptr object = m_objects.value( objectId ); + if( !object ) + { + tLog() << "ScriptAccount" << name() << "tried to unregister plugin that was not registered"; + return; + } + + if ( type == "collection" ) + { + collection_ptr collection = scriptCollection( objectId ); + if ( !collection.isNull() ) + { + SourceList::instance()->removeScriptCollection( collection ); + } + } + else + { + tLog() << "This plugin type is not handled by Tomahawk or simply cannot be removed yet"; + Q_ASSERT( false ); + } +} + + void ScriptAccount::onScriptObjectDeleted() { @@ -137,6 +218,54 @@ ScriptAccount::scriptPluginFactory( const QString& type, const scriptobject_ptr& // add it to infosystem Tomahawk::InfoSystem::InfoSystem::instance()->addInfoPlugin( infoPlugin ); } + else if( type == "collection" ) + { + if ( !scriptCollection( object->id() ).isNull() ) + return; + + const QVariantMap collectionInfo = object->syncInvoke( "collection" ).toMap(); + + if ( collectionInfo.isEmpty() || + !collectionInfo.contains( "prettyname" ) || + !collectionInfo.contains( "description" ) ) + return; + + const QString prettyname = collectionInfo.value( "prettyname" ).toString(); + const QString desc = collectionInfo.value( "description" ).toString(); + + // at this point we assume that all the tracks browsable through a resolver belong to the local source + Tomahawk::ScriptCollection* sc = new Tomahawk::ScriptCollection( object, SourceList::instance()->getLocal(), this ); + QSharedPointer collection( sc ); + collection->setWeakRef( collection.toWeakRef() ); + + sc->setServiceName( prettyname ); + sc->setDescription( desc ); + + if ( collectionInfo.contains( "trackcount" ) ) //a resolver might not expose this + { + bool ok = false; + int trackCount = collectionInfo.value( "trackcount" ).toInt( &ok ); + if ( ok ) + sc->setTrackCount( trackCount ); + } + + if ( collectionInfo.contains( "iconfile" ) ) + { + QString iconPath = QFileInfo( filePath() ).path() + "/" + + collectionInfo.value( "iconfile" ).toString(); + + QPixmap iconPixmap; + bool ok = iconPixmap.load( iconPath ); + if ( ok && !iconPixmap.isNull() ) + sc->setIcon( iconPixmap ); + } + + SourceList::instance()->addScriptCollection( collection ); + + sc->fetchIcon( collectionInfo.value( "iconurl" ).toString() ); + + m_collections.insert( object->id(), collection ); + } else { tLog() << "This plugin type is not handled by Tomahawk"; @@ -150,3 +279,92 @@ ScriptAccount::onJobDeleted( const QString& jobId ) { m_jobs.remove( jobId ); } + + +QList< Tomahawk::result_ptr > +ScriptAccount::parseResultVariantList( const QVariantList& reslist ) +{ + QList< Tomahawk::result_ptr > results; + + foreach( const QVariant& rv, reslist ) + { + QVariantMap m = rv.toMap(); + // TODO we need to handle preview urls separately. they should never trump a real url, and we need to display + // the purchaseUrl for the user to upgrade to a full stream. + if ( m.value( "preview" ).toBool() == true ) + continue; + + int duration = m.value( "duration", 0 ).toInt(); + if ( duration <= 0 && m.contains( "durationString" ) ) + { + QTime time = QTime::fromString( m.value( "durationString" ).toString(), "hh:mm:ss" ); + duration = time.secsTo( QTime( 0, 0 ) ) * -1; + } + + Tomahawk::track_ptr track = Tomahawk::Track::get( m.value( "artist" ).toString(), + m.value( "track" ).toString(), + m.value( "album" ).toString(), + m.value( "albumArtist" ).toString(), + duration, + QString(), + m.value( "albumpos" ).toUInt(), + m.value( "discnumber" ).toUInt() ); + if ( !track ) + continue; + + Tomahawk::result_ptr rp = Tomahawk::Result::get( m.value( "url" ).toString(), track ); + if ( !rp ) + continue; + + rp->setBitrate( m.value( "bitrate" ).toUInt() ); + rp->setSize( m.value( "size" ).toUInt() ); + rp->setRID( uuid() ); + rp->setPurchaseUrl( m.value( "purchaseUrl" ).toString() ); + rp->setLinkUrl( m.value( "linkUrl" ).toString() ); + rp->setScore( m.value( "score" ).toFloat() ); + rp->setChecked( m.value( "checked" ).toBool() ); + + //FIXME + if ( m.contains( "year" ) ) + { + QVariantMap attr; + attr[ "releaseyear" ] = m.value( "year" ); +// rp->track()->setAttributes( attr ); + } + + rp->setMimetype( m.value( "mimetype" ).toString() ); + if ( rp->mimetype().isEmpty() ) + { + rp->setMimetype( TomahawkUtils::extensionToMimetype( m.value( "extension" ).toString() ) ); + Q_ASSERT( !rp->mimetype().isEmpty() ); + } + + rp->setFriendlySource( name() ); + + // find collection + const QString collectionId = m.value( "collectionId" ).toString(); + if ( !collectionId.isEmpty() ) + { + if ( scriptCollection( collectionId ).isNull() ) + { + tLog() << "Resolver returned invalid collection id"; + Q_ASSERT( false ); + } + else + { + rp->setResolvedByCollection( scriptCollection( collectionId ) ); + } + } + + results << rp; + } + + return results; +} + + +const QSharedPointer< ScriptCollection > +ScriptAccount::scriptCollection( const QString& id ) const +{ + return m_collections.hash().value( id ); +} diff --git a/src/libtomahawk/resolvers/ScriptAccount.h b/src/libtomahawk/resolvers/ScriptAccount.h index 6e9a0ef9a..102760a2d 100644 --- a/src/libtomahawk/resolvers/ScriptAccount.h +++ b/src/libtomahawk/resolvers/ScriptAccount.h @@ -27,7 +27,11 @@ #include //TODO: pimple +#include "../utils/WeakObjectHash.h" +#include "ScriptCollection.h" #include +#include + #include "../DllMacro.h" @@ -44,6 +48,16 @@ public: ScriptAccount( const QString& name ); virtual ~ScriptAccount() {} + void stop(); + + const QString name() const; + + void setIcon( const QPixmap& icon ); + const QPixmap icon() const; + + void setFilePath( const QString& filePath ); + const QString filePath() const; + ScriptJob* invoke( const scriptobject_ptr& scriptObject, const QString& methodName, const QVariantMap& arguments ); virtual const QVariant syncInvoke( const scriptobject_ptr& scriptObject, const QString& methodName, const QVariantMap& arguments ) = 0; @@ -51,9 +65,14 @@ public: void reportScriptJobResult( const QVariantMap& result ); void registerScriptPlugin( const QString& type, const QString& objectId ); + void unregisterScriptPlugin( const QString& type, const QString& objectId ); virtual void scriptPluginFactory( const QString& type, const scriptobject_ptr& object ); + QList< Tomahawk::result_ptr > parseResultVariantList( const QVariantList& reslist ); + + const QSharedPointer< ScriptCollection > scriptCollection( const QString& id ) const; + private slots: void onJobDeleted( const QString& jobId ); @@ -61,8 +80,11 @@ private slots: private: // TODO: pimple, might be renamed before tho QString m_name; + QPixmap m_icon; + QString m_filePath; QHash< QString, ScriptJob* > m_jobs; QHash< QString, scriptobject_ptr > m_objects; + Utils::WeakObjectHash< ScriptCollection > m_collections; }; } // ns: Tomahawk diff --git a/src/libtomahawk/resolvers/ScriptCollection.cpp b/src/libtomahawk/resolvers/ScriptCollection.cpp index 8dc1b8bee..fa384d0ab 100644 --- a/src/libtomahawk/resolvers/ScriptCollection.cpp +++ b/src/libtomahawk/resolvers/ScriptCollection.cpp @@ -20,45 +20,45 @@ #include "ScriptCollection.h" #include "Source.h" -#include "ExternalResolverGui.h" #include "utils/TomahawkUtilsGui.h" +#include "utils/NetworkAccessManager.h" #include "utils/Logger.h" #include "resolvers/ScriptCommand_AllArtists.h" #include "resolvers/ScriptCommand_AllAlbums.h" #include "resolvers/ScriptCommand_AllTracks.h" +#include "ScriptAccount.h" +#include #include using namespace Tomahawk; -ScriptCollection::ScriptCollection( const QString& id, +ScriptCollection::ScriptCollection( const scriptobject_ptr& scriptObject, const source_ptr& source, - ExternalResolver* resolver, + ScriptAccount* scriptAccount, QObject* parent ) - : Collection( source, QString( "scriptcollection:" + resolver->name() + ":" + uuid() ), parent ) - , m_id( id ) + : Collection( source, QString( "scriptcollection:" + scriptAccount->name() + ":" + uuid() ), parent ) + , ScriptPlugin( scriptObject ) + , m_scriptAccount( scriptAccount ) , m_trackCount( -1 ) //null value { - Q_ASSERT( resolver ); - qDebug() << Q_FUNC_INFO << resolver->name() << Collection::name(); + Q_ASSERT( scriptAccount ); + qDebug() << Q_FUNC_INFO << scriptAccount->name() << Collection::name(); - m_resolver = resolver; - - m_servicePrettyName = m_resolver->name(); + m_servicePrettyName = scriptAccount->name(); } ScriptCollection::~ScriptCollection() { - } -const QString -ScriptCollection::id() const +ScriptAccount* +ScriptCollection::scriptAccount() const { - return m_id; + return m_scriptAccount; } @@ -73,7 +73,7 @@ QString ScriptCollection::prettyName() const { return tr( "%1 Collection", - "Name of a collection based on a resolver, e.g. Subsonic Collection" ) + "Name of a collection based on a script pluginsc, e.g. Subsonic Collection" ) .arg( m_servicePrettyName ); } @@ -181,3 +181,39 @@ ScriptCollection::trackCount() const { return m_trackCount; } + + +void +ScriptCollection::fetchIcon( const QString& iconUrlString ) +{ + if ( !iconUrlString.isEmpty() ) + { + QUrl iconUrl = QUrl::fromEncoded( iconUrlString.toLatin1() ); + if ( iconUrl.isValid() ) + { + QNetworkRequest req( iconUrl ); + tDebug() << "Creating a QNetworkReply with url:" << req.url().toString(); + QNetworkReply* reply = Tomahawk::Utils::nam()->get( req ); + + connect( reply, SIGNAL( finished() ), + this, SLOT( onIconFetched() ) ); + } + } +} + + +void +ScriptCollection::onIconFetched() +{ + QNetworkReply* reply = qobject_cast< QNetworkReply* >( sender() ); + if ( reply != 0 ) + { + if( reply->error() == QNetworkReply::NoError ) + { + QImageReader imageReader( reply ); + setIcon( QPixmap::fromImageReader( &imageReader ) ); + } + + reply->deleteLater(); + } +} diff --git a/src/libtomahawk/resolvers/ScriptCollection.h b/src/libtomahawk/resolvers/ScriptCollection.h index ed7b6af8a..67fbe84a2 100644 --- a/src/libtomahawk/resolvers/ScriptCollection.h +++ b/src/libtomahawk/resolvers/ScriptCollection.h @@ -21,7 +21,7 @@ #ifndef SCRIPTCOLLECTION_H #define SCRIPTCOLLECTION_H -#include "ExternalResolver.h" +#include "ScriptPlugin.h" #include "collection/Collection.h" #include "collection/ArtistsRequest.h" #include "collection/AlbumsRequest.h" @@ -34,19 +34,27 @@ namespace Tomahawk { +class ScriptAccount; -class DLLEXPORT ScriptCollection : public Collection +class DLLEXPORT ScriptCollection : public Collection, public ScriptPlugin { - Q_OBJECT +Q_OBJECT + + // access to ScriptObject + friend class ScriptCommand_AllArtists; + friend class ScriptCommand_AllAlbums; + friend class ScriptCommand_AllTracks; + friend class JSResolver; + public: - explicit ScriptCollection( const QString& id, + explicit ScriptCollection( const scriptobject_ptr& scriptObject, const source_ptr& source, - ExternalResolver* resolver, + ScriptAccount* scriptAccount, QObject* parent = nullptr ); virtual ~ScriptCollection(); - const QString id() const; + ScriptAccount* scriptAccount() const; /** * @brief setServiceName sets the name of the service that provides the ScriptCollection. @@ -62,6 +70,7 @@ public: QString itemName() const override; BackendType backendType() const override { return ScriptCollectionType; } + void fetchIcon( const QString& iconUrl ); void setIcon( const QPixmap& icon ); const QPixmap icon( const QSize& size ) const override; QPixmap bigIcon() const override; @@ -69,8 +78,6 @@ public: void setDescription( const QString& text ); QString description() const override; - virtual ExternalResolver* resolver() { return m_resolver; } - Tomahawk::ArtistsRequest* requestArtists() override; Tomahawk::AlbumsRequest* requestAlbums( const Tomahawk::artist_ptr& artist ) override; Tomahawk::TracksRequest* requestTracks( const Tomahawk::album_ptr& album ) override; @@ -78,9 +85,11 @@ public: void setTrackCount( int count ); int trackCount() const override; +private slots: + void onIconFetched(); + private: - QString m_id; - ExternalResolver* m_resolver; + ScriptAccount* m_scriptAccount; QString m_servicePrettyName; QString m_description; int m_trackCount; diff --git a/src/libtomahawk/resolvers/ScriptCommand_AllAlbums.cpp b/src/libtomahawk/resolvers/ScriptCommand_AllAlbums.cpp index e4a5783d8..ba6ba27b1 100644 --- a/src/libtomahawk/resolvers/ScriptCommand_AllAlbums.cpp +++ b/src/libtomahawk/resolvers/ScriptCommand_AllAlbums.cpp @@ -22,9 +22,10 @@ #include "Album.h" #include "Artist.h" -#include "ExternalResolver.h" +#include "ScriptAccount.h" #include "PlaylistEntry.h" #include "ScriptCollection.h" +#include "ScriptJob.h" using namespace Tomahawk; @@ -75,10 +76,12 @@ ScriptCommand_AllAlbums::exec() return; } - connect( collection->resolver(), SIGNAL( albumsFound( QList< Tomahawk::album_ptr > ) ), - this, SLOT( onResolverDone( QList< Tomahawk::album_ptr > ) ) ); + QVariantMap arguments; + arguments[ "artist" ] = m_artist->name(); - collection->resolver()->albums( m_collection, m_artist ); + ScriptJob* job = collection->scriptObject()->invoke( "albums", arguments ); + connect( job, SIGNAL( done( QVariantMap ) ), SLOT( onAlbumsJobDone( QVariantMap ) ), Qt::QueuedConnection ); + job->start(); } @@ -94,8 +97,19 @@ ScriptCommand_AllAlbums::reportFailure() void -ScriptCommand_AllAlbums::onResolverDone( const QList< Tomahawk::album_ptr >& a ) +ScriptCommand_AllAlbums::onAlbumsJobDone(const QVariantMap& result) { + ScriptJob* job = qobject_cast< ScriptJob* >( sender() ); + Q_ASSERT( job ); + + if ( job->error() ) + { + reportFailure(); + return; + } + + QList< Tomahawk::album_ptr > a = parseAlbumVariantList( m_artist, result[ "albums" ].toList() ); + if ( m_filter.isEmpty() ) emit albums( a ); else @@ -110,4 +124,26 @@ ScriptCommand_AllAlbums::onResolverDone( const QList< Tomahawk::album_ptr >& a ) emit albums( filtered ); } emit done(); + + job->deleteLater(); +} + + +QList< Tomahawk::album_ptr > +ScriptCommand_AllAlbums::parseAlbumVariantList( const Tomahawk::artist_ptr& artist, const QVariantList& reslist ) +{ + QList< Tomahawk::album_ptr > results; + + foreach( const QVariant& rv, reslist ) + { + const QString val = rv.toString(); + if ( val.trimmed().isEmpty() ) + continue; + + Tomahawk::album_ptr ap = Tomahawk::Album::get( artist, val, false ); + + results << ap; + } + + return results; } diff --git a/src/libtomahawk/resolvers/ScriptCommand_AllAlbums.h b/src/libtomahawk/resolvers/ScriptCommand_AllAlbums.h index a3c2ff137..38e4a1ed0 100644 --- a/src/libtomahawk/resolvers/ScriptCommand_AllAlbums.h +++ b/src/libtomahawk/resolvers/ScriptCommand_AllAlbums.h @@ -49,9 +49,11 @@ protected: virtual void reportFailure(); private slots: - void onResolverDone( const QList< Tomahawk::album_ptr >& ); + void onAlbumsJobDone( const QVariantMap& result ); private: + static QList< Tomahawk::album_ptr > parseAlbumVariantList( const Tomahawk::artist_ptr& artist, + const QVariantList& reslist ); Tomahawk::collection_ptr m_collection; Tomahawk::artist_ptr m_artist; QString m_filter; diff --git a/src/libtomahawk/resolvers/ScriptCommand_AllArtists.cpp b/src/libtomahawk/resolvers/ScriptCommand_AllArtists.cpp index b7e7ce893..9da5a4d7b 100644 --- a/src/libtomahawk/resolvers/ScriptCommand_AllArtists.cpp +++ b/src/libtomahawk/resolvers/ScriptCommand_AllArtists.cpp @@ -19,10 +19,13 @@ #include "ScriptCommand_AllArtists.h" #include "Artist.h" -#include "ExternalResolver.h" +#include "ScriptAccount.h" #include "ScriptCollection.h" +#include "ScriptObject.h" +#include "ScriptJob.h" #include "utils/Logger.h" +#include "../Typedefs.h" using namespace Tomahawk; @@ -59,16 +62,11 @@ void ScriptCommand_AllArtists::exec() { Tomahawk::ScriptCollection* collection = qobject_cast< Tomahawk::ScriptCollection* >( m_collection.data() ); - if ( collection == 0 ) - { - emit artists( QList< Tomahawk::artist_ptr >() ); - return; - } + Q_ASSERT( collection ); - connect( collection->resolver(), SIGNAL( artistsFound( QList< Tomahawk::artist_ptr > ) ), - this, SLOT( onResolverDone( QList< Tomahawk::artist_ptr > ) ) ); - - collection->resolver()->artists( m_collection ); + ScriptJob* job = collection->scriptObject()->invoke( "artists" ); + connect( job, SIGNAL( done( QVariantMap ) ), SLOT( onArtistsJobDone( QVariantMap ) ), Qt::QueuedConnection ); + job->start(); } @@ -81,10 +79,24 @@ ScriptCommand_AllArtists::reportFailure() } -void ScriptCommand_AllArtists::onResolverDone( const QList< Tomahawk::artist_ptr >& a ) +void +ScriptCommand_AllArtists::onArtistsJobDone( const QVariantMap& result ) { + ScriptJob* job = qobject_cast< ScriptJob* >( sender() ); + Q_ASSERT( job ); + + if ( job->error() ) + { + reportFailure(); + return; + } + + QList< Tomahawk::artist_ptr > a = parseArtistVariantList( result[ "artists" ].toList() ); + if ( m_filter.isEmpty() ) + { emit artists( a ); + } else { QList< Tomahawk::artist_ptr > filtered; @@ -93,7 +105,30 @@ void ScriptCommand_AllArtists::onResolverDone( const QList< Tomahawk::artist_ptr if ( artist->name().contains( m_filter ) ) filtered.append( artist ); } - emit artists( filtered ); + emit artists( a ); } + emit done(); + + job->deleteLater(); +} + + +QList< Tomahawk::artist_ptr > +ScriptCommand_AllArtists::parseArtistVariantList( const QVariantList& reslist ) +{ + QList< Tomahawk::artist_ptr > results; + + foreach( const QVariant& rv, reslist ) + { + const QString val = rv.toString(); + if ( val.trimmed().isEmpty() ) + continue; + + Tomahawk::artist_ptr ap = Tomahawk::Artist::get( val, false ); + + results << ap; + } + + return results; } diff --git a/src/libtomahawk/resolvers/ScriptCommand_AllArtists.h b/src/libtomahawk/resolvers/ScriptCommand_AllArtists.h index 03643956d..7dad2b382 100644 --- a/src/libtomahawk/resolvers/ScriptCommand_AllArtists.h +++ b/src/libtomahawk/resolvers/ScriptCommand_AllArtists.h @@ -48,9 +48,11 @@ protected: void reportFailure() override; private slots: - void onResolverDone( const QList< Tomahawk::artist_ptr >& ); + void onArtistsJobDone( const QVariantMap& result ); private: + static QList< Tomahawk::artist_ptr > parseArtistVariantList( const QVariantList& reslist ); + Tomahawk::collection_ptr m_collection; QString m_filter; }; diff --git a/src/libtomahawk/resolvers/ScriptCommand_AllTracks.cpp b/src/libtomahawk/resolvers/ScriptCommand_AllTracks.cpp index f02a68ca0..1c233c317 100644 --- a/src/libtomahawk/resolvers/ScriptCommand_AllTracks.cpp +++ b/src/libtomahawk/resolvers/ScriptCommand_AllTracks.cpp @@ -18,11 +18,15 @@ #include "ScriptCommand_AllTracks.h" -#include "ExternalResolver.h" + +#include "ScriptAccount.h" #include "PlaylistEntry.h" #include "ScriptCollection.h" - +#include "Artist.h" +#include "Album.h" +#include "ScriptJob.h" #include "utils/Logger.h" +#include "../Result.h" using namespace Tomahawk; @@ -66,10 +70,13 @@ ScriptCommand_AllTracks::exec() return; } - connect( collection->resolver(), SIGNAL( tracksFound( QList< Tomahawk::query_ptr > ) ), - this, SLOT( onResolverDone( QList< Tomahawk::query_ptr > ) ) ); + QVariantMap arguments; + arguments[ "artist" ] = m_album->artist()->name(); + arguments[ "album" ] = m_album->name(); - collection->resolver()->tracks( m_collection, m_album ); + ScriptJob* job = collection->scriptObject()->invoke( "tracks", arguments ); + connect( job, SIGNAL( done( QVariantMap ) ), SLOT( onTracksJobDone( QVariantMap ) ), Qt::QueuedConnection ); + job->start(); } @@ -89,8 +96,37 @@ ScriptCommand_AllTracks::reportFailure() void -ScriptCommand_AllTracks::onResolverDone( const QList< Tomahawk::query_ptr >& q ) +ScriptCommand_AllTracks::onTracksJobDone( const QVariantMap& result ) { - emit tracks( q ); + ScriptJob* job = qobject_cast< ScriptJob* >( sender() ); + Q_ASSERT( job ); + + qDebug() << "Resolver reporting album tracks:" << result; + + if ( job->error() ) + { + reportFailure(); + return; + } + + QSharedPointer< ScriptCollection > collection = m_collection.objectCast< ScriptCollection >(); + Q_ASSERT( !collection.isNull() ); + + QList< Tomahawk::result_ptr > t = collection->scriptAccount()->parseResultVariantList( result[ "tracks"].toList() ); + + + QList< Tomahawk::query_ptr > queries; + foreach ( const Tomahawk::result_ptr& result, t ) + { + result->setScore( 1.0 ); + result->setResolvedByCollection( m_collection ); + queries.append( result->toQuery() ); + } + + tDebug() << Q_FUNC_INFO << "about to push" << queries.count() << "tracks"; + + emit tracks( queries ); emit done(); + + job->deleteLater(); } diff --git a/src/libtomahawk/resolvers/ScriptCommand_AllTracks.h b/src/libtomahawk/resolvers/ScriptCommand_AllTracks.h index 845fc75f0..3106e9582 100644 --- a/src/libtomahawk/resolvers/ScriptCommand_AllTracks.h +++ b/src/libtomahawk/resolvers/ScriptCommand_AllTracks.h @@ -43,11 +43,11 @@ signals: void done(); protected: - void exec() override; + Q_INVOKABLE void exec() override; void reportFailure() override; private slots: - void onResolverDone( const QList< Tomahawk::query_ptr >& ); + void onTracksJobDone( const QVariantMap& result ); private: Tomahawk::collection_ptr m_collection; diff --git a/src/libtomahawk/resolvers/ScriptObject.h b/src/libtomahawk/resolvers/ScriptObject.h index f6314d5fc..9d9810f84 100644 --- a/src/libtomahawk/resolvers/ScriptObject.h +++ b/src/libtomahawk/resolvers/ScriptObject.h @@ -42,6 +42,8 @@ public: ScriptObject( const QString& id, ScriptAccount* parent ); virtual ~ScriptObject(); + QString id() const; + void setWeakRef( const scriptobject_wptr& weakRef ); const scriptobject_wptr weakRef() const; @@ -53,8 +55,6 @@ public: const QVariant syncInvoke( const QString& methodName, const QVariantMap& arguments = QVariantMap() ); protected: - QString id() const; - void startJob( ScriptJob* scriptJob ); private: diff --git a/src/libtomahawk/utils/WeakObjectHash.h b/src/libtomahawk/utils/WeakObjectHash.h index 0de08e607..da9489d49 100644 --- a/src/libtomahawk/utils/WeakObjectHash.h +++ b/src/libtomahawk/utils/WeakObjectHash.h @@ -72,7 +72,7 @@ public: m_hash.insert( key, value.toWeakRef() ); } - const QHash< QString, QWeakPointer >& hash() { return m_hash; } + const QHash< QString, QWeakPointer >& hash() const { return m_hash; } virtual void remove( const QString& key ) { m_hash.remove( key ); } private: