From d3eb8df91a8acb842a04030f7092d480f363bd95 Mon Sep 17 00:00:00 2001 From: Leo Franchi Date: Wed, 3 Aug 2011 23:25:31 -0400 Subject: [PATCH] Add support for JSPF playlists, remote and local --- src/libtomahawk/CMakeLists.txt | 4 +- src/libtomahawk/globalactionmanager.cpp | 9 ++ src/libtomahawk/utils/jspfloader.cpp | 191 ++++++++++++++++++++++++ src/libtomahawk/utils/jspfloader.h | 77 ++++++++++ src/tomahawkapp.cpp | 11 +- 5 files changed, 290 insertions(+), 2 deletions(-) create mode 100644 src/libtomahawk/utils/jspfloader.cpp create mode 100644 src/libtomahawk/utils/jspfloader.h diff --git a/src/libtomahawk/CMakeLists.txt b/src/libtomahawk/CMakeLists.txt index 459cdc881..ff3a500f2 100644 --- a/src/libtomahawk/CMakeLists.txt +++ b/src/libtomahawk/CMakeLists.txt @@ -166,6 +166,7 @@ set( libSources utils/animatedsplitter.cpp utils/xspfloader.cpp utils/xspfgenerator.cpp + utils/jspfloader.cpp widgets/newplaylistwidget.cpp widgets/searchwidget.cpp @@ -336,6 +337,7 @@ set( libHeaders utils/animatedsplitter.h utils/xspfloader.h utils/xspfgenerator.h + utils/jspfloader.h widgets/newplaylistwidget.h widgets/searchwidget.h @@ -419,7 +421,7 @@ IF( APPLE ) FIND_LIBRARY( SCRIPTINGBRIDGE_LIBRARY ScriptingBridge ) MARK_AS_ADVANCED( COREAUDIO_LIBRARY COREFOUNDATION_LIBRARY FOUNDATION_LIBRARY SCRIPTINGBRIDGE_LIBRARY ) - SET( libSources ${libSources} + SET( libSources ${libSources} infosystem/infoplugins/mac/adium.mm infosystem/infoplugins/mac/adiumplugin.cpp widgets/maclineedit.mm diff --git a/src/libtomahawk/globalactionmanager.cpp b/src/libtomahawk/globalactionmanager.cpp index 2b28be069..b83238c98 100644 --- a/src/libtomahawk/globalactionmanager.cpp +++ b/src/libtomahawk/globalactionmanager.cpp @@ -41,6 +41,7 @@ #include #include +#include "utils/jspfloader.h" GlobalActionManager* GlobalActionManager::s_instance = 0; @@ -195,6 +196,14 @@ GlobalActionManager::parseTomahawkLink( const QString& url ) l->load( xspf ); connect( l, SIGNAL( ok( Tomahawk::playlist_ptr ) ), ViewManager::instance(), SLOT( show( Tomahawk::playlist_ptr ) ) ); + return true; + } else if( u.hasQueryItem( "jspf" ) ) { + QUrl jspf = QUrl::fromUserInput( u.queryItemValue( "jspf" ) ); + Tomahawk::JSPFLoader* l = new Tomahawk::JSPFLoader( true, this ); + tDebug() << "Loading jspiff:" << jspf.toString(); + l->load( jspf ); + connect( l, SIGNAL( ok( Tomahawk::playlist_ptr ) ), ViewManager::instance(), SLOT( show( Tomahawk::playlist_ptr ) ) ); + return true; } } diff --git a/src/libtomahawk/utils/jspfloader.cpp b/src/libtomahawk/utils/jspfloader.cpp new file mode 100644 index 000000000..1bd896bf8 --- /dev/null +++ b/src/libtomahawk/utils/jspfloader.cpp @@ -0,0 +1,191 @@ +/* === This file is part of Tomahawk Player - === + * + * Copyright 2010-2011, Leo Franchi . + */ + + +#include "jspfloader.h" + + +#include +#include +#include + +#include + +#include "utils/tomahawkutils.h" +#include "utils/logger.h" + +#include "sourcelist.h" +#include "playlist.h" + +using namespace Tomahawk; + +void +JSPFLoader::load( const QUrl& url ) +{ + QNetworkRequest request( url ); + Q_ASSERT( TomahawkUtils::nam() != 0 ); + QNetworkReply* reply = TomahawkUtils::nam()->get( request ); + + // isn't there a race condition here? something could happen before we connect() + // no---the event loop is needed to make the request, i think (leo) + connect( reply, SIGNAL( finished() ), + SLOT( networkLoadFinished() ) ); + + connect( reply, SIGNAL( error( QNetworkReply::NetworkError ) ), + SLOT( networkError( QNetworkReply::NetworkError ) ) ); +} + + +void +JSPFLoader::load( QFile& file ) +{ + if( file.open( QFile::ReadOnly ) ) + { + m_body = file.readAll(); + gotBody(); + } + else + { + tLog() << "Failed to open jspf file"; + reportError(); + } +} + + +void +JSPFLoader::reportError() +{ + emit failed(); + deleteLater(); +} + + +void +JSPFLoader::networkLoadFinished() +{ + QNetworkReply* reply = qobject_cast(sender()); + m_body = reply->readAll(); + gotBody(); +} + + +void +JSPFLoader::networkError( QNetworkReply::NetworkError e ) +{ + tLog() << Q_FUNC_INFO << "Network error loading jspf" << e; + reportError(); +} + + +void +JSPFLoader::gotBody() +{ + QJson::Parser p; + bool retOk; + QVariantMap wrapper = p.parse( m_body, &retOk ).toMap(); + + if ( !retOk ) + { + tLog() << "Failed to parse jspf json:" << p.errorString() << "on line" << p.errorLine(); + return; + } + + if ( !wrapper.contains( "playlist" ) ) + { + tLog() << "No playlist element in JSPF!"; + return; + } + + QVariantMap pl = wrapper.value( "playlist" ).toMap(); + QString origTitle = pl.value( "title" ).toString(); + m_info = pl.value( "info" ).toString(); + m_creator = pl.value( "creator" ).toString(); + + m_title = origTitle; + if ( m_title.isEmpty() ) + m_title = tr( "New Playlist" ); + if ( !m_overrideTitle.isEmpty() ) + m_title = m_overrideTitle; + + if ( pl.contains( "track" ) ) + { + QVariantList tracks = pl.value( "track" ).toList(); + + bool shownError = false; + foreach ( const QVariant& track, tracks ) + { + QVariantMap tM = track.toMap(); + QString artist, album, track, duration, annotation, url; + + artist = tM.value( "creator" ).toString(); + album = tM.value( "album" ).toString(); + track = tM.value( "title" ).toString(); + duration = tM.value( "duration" ).toString(); + annotation = tM.value( "annotation" ).toString(); + if ( tM.value( "location" ).toList().size() > 0 ) + url = tM.value( "location" ).toList().first().toString(); + + if( artist.isEmpty() || track.isEmpty() ) + { + if( !shownError ) + { + QMessageBox::warning( 0, tr( "Failed to save tracks" ), tr( "Some tracks in the playlist do not contain an artist and a title. They will be ignored." ), QMessageBox::Ok ); + shownError = true; + } + continue; + } + + query_ptr q = Tomahawk::Query::get( artist, track, album, uuid() ); + q->setDuration( duration.toInt() / 1000 ); + if( !url.isEmpty() ) + q->setResultHint( url ); + + m_entries << q; + } + } + + if ( origTitle.isEmpty() && m_entries.isEmpty() ) + { + if ( m_autoCreate ) + { + QMessageBox::critical( 0, tr( "XSPF Error" ), tr( "This is not a valid XSPF playlist." ) ); + deleteLater(); + return; + } + else + { + emit failed(); + return; + } + } + + if ( m_autoCreate ) + { + m_playlist = Playlist::create( SourceList::instance()->getLocal(), + uuid(), + m_title, + m_info, + m_creator, + false, + m_entries ); + + deleteLater(); + } + + emit ok( m_playlist ); +} diff --git a/src/libtomahawk/utils/jspfloader.h b/src/libtomahawk/utils/jspfloader.h new file mode 100644 index 000000000..1228eac2f --- /dev/null +++ b/src/libtomahawk/utils/jspfloader.h @@ -0,0 +1,77 @@ +/* === This file is part of Tomahawk Player - === + * + * Copyright 2010-2011, Leo Franchi . + */ + + +#ifndef JSPFLOADER_H +#define JSPFLOADER_H + +#include +#include +#include +#include +#include + +#include "playlist.h" +#include "typedefs.h" + +#include "dllmacro.h" +namespace Tomahawk +{ + +class DLLEXPORT JSPFLoader : public QObject +{ +Q_OBJECT + +public: + explicit JSPFLoader( bool autoCreate = true, QObject* parent = 0 ) + : QObject( parent ) + , m_autoCreate( autoCreate ) + {} + + virtual ~JSPFLoader() {} + + QList< Tomahawk::query_ptr > entries() const { return m_entries; } + void setOverrideTitle( const QString& newTitle ) { m_overrideTitle = newTitle; } + +signals: + void failed(); + void ok( const Tomahawk::playlist_ptr& ); + +public slots: + void load( const QUrl& url ); + void load( QFile& file ); + +private slots: + void networkLoadFinished(); + void networkError( QNetworkReply::NetworkError e ); + +private: + void reportError(); + void gotBody(); + + bool m_autoCreate; + QList< Tomahawk::query_ptr > m_entries; + QString m_title, m_info, m_creator, m_overrideTitle; + + QByteArray m_body; + Tomahawk::playlist_ptr m_playlist; +}; + +} + +#endif // JSPFLOADER_H diff --git a/src/tomahawkapp.cpp b/src/tomahawkapp.cpp index 76a332a00..cd2c7a502 100644 --- a/src/tomahawkapp.cpp +++ b/src/tomahawkapp.cpp @@ -54,6 +54,7 @@ #include "audio/audioengine.h" #include "utils/xspfloader.h" +#include "utils/jspfloader.h" #include "utils/logger.h" #include "utils/tomahawkutils.h" @@ -520,11 +521,19 @@ TomahawkApp::loadUrl( const QString& url ) { QFile f( url ); QFileInfo info( f ); - if ( f.exists() && info.suffix() == "xspf" ) { + if ( info.suffix() == "xspf" ) + { XSPFLoader* l = new XSPFLoader( true, this ); tDebug( LOGINFO ) << "Loading spiff:" << url; l->load( QUrl::fromUserInput( url ) ); + return true; + } else if ( info.suffix() == "jspf" ) + { + JSPFLoader* l = new JSPFLoader( true, this ); + tDebug( LOGINFO ) << "Loading j-spiff:" << url; + l->load( QUrl::fromUserInput( url ) ); + return true; } }