From 245d3e0bd8937d04e601603090fb9b9b0f26dbb0 Mon Sep 17 00:00:00 2001 From: dougma Date: Wed, 29 Apr 2009 16:02:53 +0100 Subject: [PATCH] playdar boffin work in progress --- app/boffin/App.cpp | 146 ++++++----------- app/boffin/App.h | 21 +-- app/boffin/BoffinRequest.cpp | 124 ++++++++++++++ app/boffin/BoffinRequest.h | 63 ++++++++ app/boffin/BoffinRqlRequest.cpp | 113 +++++++++++++ app/boffin/BoffinRqlRequest.h | 75 +++++++++ app/boffin/MediaPipeline.cpp | 65 ++------ app/boffin/MediaPipeline.h | 14 +- app/boffin/PlaydarApi.h | 30 ++++ app/boffin/PlaydarPollingRequest.cpp | 66 ++++++++ app/boffin/PlaydarPollingRequest.h | 44 +++++ app/boffin/PlaydarTagCloudModel.cpp | 151 ++++++++++++++++++ app/boffin/PlaydarTagCloudModel.h | 72 +++++++++ app/boffin/TPlaydarApi.hpp | 79 +++++++++ app/boffin/TrackSource.cpp | 35 ++++ app/boffin/TrackSource.h | 28 ++++ app/boffin/boffin.pro | 16 +- app/boffin/jsonGetMember.hpp | 53 ++++++ .../localresolver/LocalCollection.cpp | 4 +- 19 files changed, 1008 insertions(+), 191 deletions(-) create mode 100755 app/boffin/BoffinRequest.cpp create mode 100755 app/boffin/BoffinRequest.h create mode 100755 app/boffin/BoffinRqlRequest.cpp create mode 100755 app/boffin/BoffinRqlRequest.h create mode 100755 app/boffin/PlaydarApi.h create mode 100755 app/boffin/PlaydarPollingRequest.cpp create mode 100755 app/boffin/PlaydarPollingRequest.h create mode 100755 app/boffin/PlaydarTagCloudModel.cpp create mode 100755 app/boffin/PlaydarTagCloudModel.h create mode 100755 app/boffin/TPlaydarApi.hpp create mode 100755 app/boffin/TrackSource.cpp create mode 100755 app/boffin/TrackSource.h create mode 100755 app/boffin/jsonGetMember.hpp diff --git a/app/boffin/App.cpp b/app/boffin/App.cpp index 0f598097e..ca75ec87c 100644 --- a/app/boffin/App.cpp +++ b/app/boffin/App.cpp @@ -23,11 +23,13 @@ #include "PickDirsDialog.h" #include "ScanProgressWidget.h" #include "ScrobSocket.h" -#include "app/clientplugins/localresolver/LocalContentScannerThread.h" -#include "app/clientplugins/localresolver/LocalContentScanner.h" -#include "app/clientplugins/localresolver/LocalContentConfigurator.h" -#include "app/clientplugins/localresolver/TrackTagUpdater.h" -#include "app/clientplugins/localresolver/QueryError.h" +#include "TrackSource.h" +//#include "app/clientplugins/localresolver/LocalContentScannerThread.h" +//#include "app/clientplugins/localresolver/LocalContentScanner.h" +//#include "app/clientplugins/localresolver/LocalContentConfigurator.h" +//#include "app/clientplugins/localresolver/TrackTagUpdater.h" +//#include "app/clientplugins/localresolver/QueryError.h" +#include "lib/lastfm/ws/WsAccessManager.h" #include "lib/unicorn/QMessageBoxBuilder.h" #include #include @@ -35,22 +37,24 @@ #include #include #include +#include "BoffinRqlRequest.h" + #define OUTPUT_DEVICE_KEY "OutputDevice" App::App( int& argc, char** argv ) : unicorn::Application( argc, argv ) - , m_contentScannerThread( 0 ) - , m_contentScanner( 0 ) - , m_trackTagUpdater( 0 ) , m_mainwindow( 0 ) , m_cloud( 0 ) , m_scrobsocket( 0 ) , m_pipe( 0 ) , m_audioOutput( 0 ) , m_playing( false ) -{} + , m_api( "http://localhost:8888", "" ) +{ + m_wam = new WsAccessManager( this ); +} App::~App() @@ -64,10 +68,6 @@ App::~App() void App::cleanup() { - if (m_contentScanner) m_contentScanner->stop(); - if (m_contentScannerThread) m_contentScannerThread->wait(); - delete m_contentScanner; - delete m_contentScannerThread; } @@ -118,10 +118,10 @@ App::init( MainWindow* window ) throw( int /*exitcode*/ ) connect( m_pipe, SIGNAL(stopped()), m_scrobsocket, SLOT(stop()) ); /// parts of the scanning stuff - m_trackTagUpdater = TrackTagUpdater::create( - "http://musiclookup.last.fm/trackresolve", - 100, // number of days track tags are good - 5); // 5 minute delay between web requests + //m_trackTagUpdater = TrackTagUpdater::create( + // "http://musiclookup.last.fm/trackresolve", + // 100, // number of days track tags are good + // 5); // 5 minute delay between web requests /// connect connect( window->ui.play, SIGNAL(triggered()), SLOT(play()) ); @@ -134,74 +134,12 @@ App::init( MainWindow* window ) throw( int /*exitcode*/ ) QShortcut* cut = new QShortcut( Qt::Key_Space, window ); connect( cut, SIGNAL(activated()), SLOT(playPause()) ); cut->setContext( Qt::ApplicationShortcut ); - -/// go! - if (!scan( false )) - throw 1; //abort app -} - - -void -App::startAgain() -{ - scan( true ); -} - - -bool -App::scan( bool delete_all_files_first ) -{ -/// content scanner - try - { - LocalContentConfigurator cfg; - - QStringList const dirs = cfg.getScanDirs(); - if (delete_all_files_first || dirs.isEmpty() || cfg.getFileCount() == 0) - { - PickDirsDialog picker( m_mainwindow ); - picker.setDirs( dirs ); - if (picker.exec() == QDialog::Rejected) - return false; // abort the whole app - - if ( delete_all_files_first ) - cfg.deleteAllFiles(); - cfg.changeScanDirs( picker.dirs() ); - } - cfg.updateVolumeAvailability(); - } - catch (QueryError e) - { - // the db is probably an old version - qCritical() << "Database problem: " + e.text(); - - QMessageBoxBuilder( m_mainwindow ) - .setTitle( "Warning" ) - .setText( "Boffin suffered a database problem, consult the log for the gory details" ) - .exec(); - return false; // not much we can do. - } - cleanup(); + onScanningFinished(); - m_contentScanner = new LocalContentScanner; - connect(m_contentScanner, SIGNAL(trackScanned(Track)), m_trackTagUpdater, SLOT(needsUpdate())); - m_contentScannerThread = new LocalContentScannerThread(m_contentScanner); - -////// scanning widget - ScanProgressWidget* progress = new ScanProgressWidget; - m_mainwindow->setCentralWidget( progress ); - connect( m_contentScanner, SIGNAL(trackScanned( Track )), progress, SLOT(onNewTrack( Track )) ); - connect( m_contentScanner, SIGNAL(dirScanStart( QString )), progress, SLOT(onNewDirectory( QString )) ); - connect( m_contentScanner, SIGNAL(finished()), progress, SLOT(onFinished()) ); - //queue so the progress widget can update its status label - connect( m_contentScanner, SIGNAL(finished()), SLOT(onScanningFinished()), Qt::QueuedConnection ); - - m_mainwindow->ui.rescan->setEnabled( false ); - - m_contentScannerThread->start(); - - return true; +/// go! + //if (!scan( false )) + // throw 1; //abort app } @@ -223,7 +161,6 @@ App::onOutputDeviceActionTriggered( QAction* a ) #include "TagCloudView.h" #include "TagDelegate.h" -#include "TagCloudModel.h" #include "PlaydarTagCloudModel.h" #include "lib/lastfm/ws/WsAccessManager.h" @@ -237,12 +174,8 @@ App::onScanningFinished() disconnect( sender(), 0, this, 0 ); //only once pls - PlaydarApi api("http://localhost:8888", "67adc74f-e373-4e1f-ab5a-4c9d6ca20c3a"); - WsAccessManager* wam = new WsAccessManager(this); - PlaydarTagCloudModel* model = new PlaydarTagCloudModel(api, wam); - + PlaydarTagCloudModel* model = new PlaydarTagCloudModel(m_api, m_wam); m_cloud = new TagCloudView; -// m_cloud->setModel( new TagCloudModel ); m_cloud->setModel( model ); m_cloud->setItemDelegate( new TagDelegate ); m_mainwindow->setCentralWidget( m_cloud ); @@ -273,7 +206,24 @@ App::play() .exec(); return; } - m_pipe->playTags( m_cloud->currentTags() ); + + QStringList tags = m_cloud->currentTags(); + for (int i = 0; i < tags.count(); ++i) + tags[i] = "tag:\"" + tags[i] + '"'; + + QString rql = tags.join(" and "); + BoffinRqlRequest* req = new BoffinRqlRequest(m_wam, m_api, rql); + TrackSource* source = new TrackSource(req); + connect(source, SIGNAL(ready()), this, SLOT(onReadyToPlay())); + req->start(); + onPreparing(); +} + + +void +App::onReadyToPlay() +{ + m_pipe->play( (TrackSource*) sender() ); } @@ -298,7 +248,7 @@ App::xspf() if (path.size()) { m_mainwindow->QMainWindow::setWindowTitle( "Resolving XSPF..." ); - m_pipe->playXspf( path ); + // m_pipe->playXspf( path ); } } @@ -395,13 +345,13 @@ App::onWordle() if(!d) { d = new WordleDialog( m_mainwindow ); QString output; - TagCloudModel model( this, 0 ); - for(int i = 0; i < model.rowCount(); ++i) { - QModelIndex index = model.index( i, 0 ); - QString weight = index.data( TagCloudModel::WeightRole ).toString(); - QString tag = index.data().toString().trimmed().simplified().replace( ' ', '~' ); - output += tag + ':' + weight + '\n'; - } + //TagCloudModel model( this, 0 ); + //for(int i = 0; i < model.rowCount(); ++i) { + // QModelIndex index = model.index( i, 0 ); + // QString weight = index.data( TagCloudModel::WeightRole ).toString(); + // QString tag = index.data().toString().trimmed().simplified().replace( ' ', '~' ); + // output += tag + ':' + weight + '\n'; + //} d->setText( output ); } d.show(); diff --git a/app/boffin/App.h b/app/boffin/App.h index c7caa4a58..c0b644359 100644 --- a/app/boffin/App.h +++ b/app/boffin/App.h @@ -20,12 +20,17 @@ #ifndef APP_H #define APP_H +#include #include #include "lib/unicorn/UnicornApplication.h" -#include +#include "PlaydarApi.h" + + namespace Phonon { class AudioOutput; } + class TagCloudView; + class App : public unicorn::Application { Q_OBJECT @@ -35,20 +40,18 @@ class App : public unicorn::Application ~App(); void init( class MainWindow* ) throw( int /*exitcode*/ ); - + void play( class TrackSource *); + public slots: void play(); void xspf(); //prompts to choose a xspf to resolve - /** returns false if user cancels the picker dialog */ - bool scan( bool delete_all_files_first ); - void startAgain(); - void playPause(); private slots: void onOutputDeviceActionTriggered( class QAction* ); + void onReadyToPlay(); void onPreparing(); void onStarted( const Track& ); void onResumed(); @@ -62,9 +65,6 @@ private slots: private: void cleanup(); - class LocalContentScannerThread* m_contentScannerThread; - class LocalContentScanner* m_contentScanner; - class TrackTagUpdater* m_trackTagUpdater; class MainWindow* m_mainwindow; QPointer m_cloud; class ScrobSocket* m_scrobsocket; @@ -73,6 +73,9 @@ private slots: Phonon::AudioOutput* m_audioOutput; bool m_playing; + + PlaydarApi m_api; + class WsAccessManager* m_wam; }; #endif //APP_H diff --git a/app/boffin/BoffinRequest.cpp b/app/boffin/BoffinRequest.cpp new file mode 100755 index 000000000..283b50c3c --- /dev/null +++ b/app/boffin/BoffinRequest.cpp @@ -0,0 +1,124 @@ +#include +#include + +#include +#include "json_spirit/json_spirit.h" +#include "lib/lastfm/ws/WsAccessManager.h" + +#include "BoffinRequest.h" +#include "jsonGetMember.hpp" + + +BoffinRequest::BoffinRequest(WsAccessManager* wam, PlaydarApi& api) +: m_wam(wam) +, m_api(api) +, m_tagcloudReply(0) +, m_pollReply(0) +{ +} + +BoffinRequest::~BoffinRequest() +{ + delete m_tagcloudReply; + delete m_pollReply; +} + + +void +BoffinRequest::onReqFinished() +{ + QNetworkReply *reply = (QNetworkReply*) sender(); + if (reply->error() == QNetworkReply::NoError) { + QByteArray ba( reply->readAll() ); + handleResponse(ba.constData(), ba.size()); + } + fail(""); +} + +void +BoffinRequest::onPollFinished() +{ + QNetworkReply *reply = (QNetworkReply*) sender(); + if (reply->error() == QNetworkReply::NoError) { + QByteArray ba( reply->readAll() ); + handlePollResponse(ba.constData(), ba.size()); + } + fail(""); +} + +// virtual +void +BoffinRequest::issueRequest() +{ + m_tagcloudReply = m_wam->get( + QNetworkRequest( + m_api.boffinTagcloud())); + + if (m_tagcloudReply) { + connect(m_tagcloudReply, SIGNAL(finished()), this, SLOT(onReqFinished())); + } else { + fail("couldn't issue boffin request"); + } +} + +// virtual +void +BoffinRequest::issuePoll(unsigned msDelay) +{ + QTimer::singleShot(msDelay, this, SLOT(onTimer())); +} + + +// virtual +bool +BoffinRequest::handleJsonPollResponse(int poll, + const json_spirit::Object& , + const json_spirit::Array& results) +{ + QList taglist; + BOOST_FOREACH(const json_spirit::Value& i, results) { + std::string tagName, hostName; + int count; + double score; + if (jsonGetMember(i, "name", tagName) && + jsonGetMember(i, "source", hostName) && + jsonGetMember(i, "count", count) && + jsonGetMember(i, "score", score)) + { + taglist << BoffinTagItem(tagName, hostName, count, static_cast(score)); + } + } + emit tags(taglist); + return poll < 4; +} + +void +BoffinRequest::onTimer() +{ + // start the poll now + delete m_pollReply; // free any previous + + m_pollReply = m_wam->get( + QNetworkRequest( + m_api.getResults( + QString::fromStdString(qid())))); + + if (m_pollReply) { + connect(m_pollReply, SIGNAL(finished()), this, SLOT(onPollFinished())); + } else { + fail("couldn't poll for boffin results"); + } +} + +// returns true if another poll should be made +// virtual + + +//virtual +void +BoffinRequest::fail(const char* message) +{ + message; + emit error(); +} + diff --git a/app/boffin/BoffinRequest.h b/app/boffin/BoffinRequest.h new file mode 100755 index 000000000..8c21fdfd5 --- /dev/null +++ b/app/boffin/BoffinRequest.h @@ -0,0 +1,63 @@ +#ifndef BOFFIN_REQUEST_H +#define BOFFIN_REQUEST_H + +#include "PlaydarApi.h" +#include "PlaydarPollingRequest.h" + + +class QNetworkReply; +class WsAccessManager; + +struct BoffinTagItem +{ + BoffinTagItem(const std::string &name, const std::string &host, int count, float weight) + : m_name(QString::fromStdString(name)) + , m_host(QString::fromStdString(host)) + , m_count(count) + , m_weight(weight) + { + } + + QString m_name; + QString m_host; + int m_count; + float m_weight; +}; + +class BoffinRequest + : public QObject + , public PlaydarPollingRequest +{ + Q_OBJECT + +public: + BoffinRequest(WsAccessManager* wam, PlaydarApi& api); + ~BoffinRequest(); + +signals: + void error(); + void tags(QList tags); + +private slots: + void onReqFinished(); + void onPollFinished(); + void onTimer(); + +private: + virtual void issueRequest(); + virtual void issuePoll(unsigned msDelay); + virtual bool handleJsonPollResponse(int poll, const json_spirit::Object& query, const json_spirit::Array& results); + virtual void fail(const char* message); + + PlaydarApi m_api; + WsAccessManager *m_wam; + QNetworkReply *m_tagcloudReply; + QNetworkReply *m_pollReply; +}; + +class StatRequest +{ + +}; + +#endif diff --git a/app/boffin/BoffinRqlRequest.cpp b/app/boffin/BoffinRqlRequest.cpp new file mode 100755 index 000000000..eeb88f939 --- /dev/null +++ b/app/boffin/BoffinRqlRequest.cpp @@ -0,0 +1,113 @@ +#include +#include + +#include +#include "json_spirit/json_spirit.h" +#include "lib/lastfm/ws/WsAccessManager.h" + +#include "BoffinRqlRequest.h" +#include "jsonGetMember.hpp" + + +BoffinRqlRequest::BoffinRqlRequest(WsAccessManager* wam, PlaydarApi& api, QString rql) +: m_wam(wam) +, m_api(api) +, m_rqlReply(0) +, m_pollReply(0) +, m_rql(rql) +{ +} + +BoffinRqlRequest::~BoffinRqlRequest() +{ +} + +void +BoffinRqlRequest::onReqFinished() +{ + QNetworkReply *reply = (QNetworkReply*) sender(); + if (reply->error() == QNetworkReply::NoError) { + QByteArray ba( reply->readAll() ); + handleResponse(ba.constData(), ba.size()); + } + fail(""); +} + +void +BoffinRqlRequest::onPollFinished() +{ + QNetworkReply *reply = (QNetworkReply*) sender(); + if (reply->error() == QNetworkReply::NoError) { + QByteArray ba( reply->readAll() ); + handlePollResponse(ba.constData(), ba.size()); + } + fail(""); +} + +// virtual +void +BoffinRqlRequest::issueRequest() +{ + m_rqlReply = m_wam->get(QNetworkRequest(m_api.boffinRql(m_rql))); + if (m_rqlReply) { + connect(m_rqlReply, SIGNAL(finished()), this, SLOT(onReqFinished())); + } else { + fail("couldn't issue boffin rql request"); + } +} + +// virtual +void +BoffinRqlRequest::issuePoll(unsigned msDelay) +{ + QTimer::singleShot(msDelay, this, SLOT(onTimer())); +} + +//virtual +bool +BoffinRqlRequest::handleJsonPollResponse(int poll, const json_spirit::Object& query, const json_spirit::Array& results) +{ + query; + + QList trackList; + BOOST_FOREACH(const json_spirit::Value& i, results) { + int duration = 0; + jsonGetMember(i, "duration", duration); + + std::string artist, album, track, source, mimetype, url; + if (jsonGetMember(i, "artist", artist) && + jsonGetMember(i, "album", album) && + jsonGetMember(i, "track", track) && + jsonGetMember(i, "source", source) && + jsonGetMember(i, "mimetype", mimetype) && + jsonGetMember(i, "url", url) ) + { + trackList << BoffinPlayableItem(artist, album, track, source, mimetype, url, duration); + } + } + emit tracks(trackList); + return poll < 4; +} + +//virtual +void +BoffinRqlRequest::fail(const char* message) +{ + message; + emit error(); +} + + +void +BoffinRqlRequest::onTimer() +{ + // start the poll now + delete m_pollReply; // free any previous + + m_pollReply = m_wam->get(QNetworkRequest(m_api.getResults(QString::fromStdString(qid())))); + if (m_pollReply) { + connect(m_pollReply, SIGNAL(finished()), this, SLOT(onPollFinished())); + } else { + fail("couldn't poll for boffin rql results"); + } +} \ No newline at end of file diff --git a/app/boffin/BoffinRqlRequest.h b/app/boffin/BoffinRqlRequest.h new file mode 100755 index 000000000..4ee02b050 --- /dev/null +++ b/app/boffin/BoffinRqlRequest.h @@ -0,0 +1,75 @@ +#ifndef BOFFIN_RQL_REQUEST_H +#define BOFFIN_RQL_REQUEST_H + +#include "PlaydarApi.h" +#include "PlaydarPollingRequest.h" + + +class QNetworkReply; +class WsAccessManager; + + +struct BoffinPlayableItem +{ + BoffinPlayableItem(const std::string& artist, + const std::string& album, + const std::string& track, + const std::string& source, + const std::string& mimetype, + const std::string& url, + int duration) + : m_artist(QString::fromStdString(artist)) + , m_album(QString::fromStdString(album)) + , m_track(QString::fromStdString(track)) + , m_source(QString::fromStdString(source)) + , m_mimetype(QString::fromStdString(mimetype)) + , m_url(QUrl::fromPercentEncoding(QByteArray(url.data()))) + , m_duration(duration) + { + } + + QString m_artist; + QString m_album; + QString m_track; + QString m_source; + QString m_mimetype; + QUrl m_url; + int m_duration; +}; + + + +class BoffinRqlRequest + : public QObject + , public PlaydarPollingRequest +{ + Q_OBJECT +public: + BoffinRqlRequest(WsAccessManager* wam, PlaydarApi& api, QString rql); + ~BoffinRqlRequest(); + +signals: + void error(); + void tracks(QList tracks); + +private slots: + void onReqFinished(); + void onPollFinished(); + void onTimer(); + +private: + virtual void issueRequest(); + virtual void issuePoll(unsigned msDelay); + virtual bool handleJsonPollResponse(int poll, const json_spirit::Object& query, const json_spirit::Array& results); + virtual void fail(const char* message); + + PlaydarApi m_api; + WsAccessManager *m_wam; + QNetworkReply *m_rqlReply; + QNetworkReply *m_pollReply; + + QString m_rql; +}; + +#endif + diff --git a/app/boffin/MediaPipeline.cpp b/app/boffin/MediaPipeline.cpp index d2a70f29a..430b83205 100644 --- a/app/boffin/MediaPipeline.cpp +++ b/app/boffin/MediaPipeline.cpp @@ -17,18 +17,14 @@ * 51 Franklin Steet, Fifth Floor, Boston, MA 02110-1301, USA. * ***************************************************************************/ -#include "MediaPipeline.h" -#include "app/client/Resolver.h" -#include "app/client/XspfResolvingTrackSource.h" -#include "app/client/LocalRql.h" -#include "app/client/LocalRadioTrackSource.h" -#include "app/clientplugins/localresolver/LocalRqlPlugin.h" -#include "app/clientplugins/localresolver/TrackResolver.h" +#include #include +#include #include #include #include -#include +#include "MediaPipeline.h" +#include "TrackSource.h" MediaPipeline::MediaPipeline( Phonon::AudioOutput* ao, QObject* parent ) @@ -44,13 +40,6 @@ MediaPipeline::MediaPipeline( Phonon::AudioOutput* ao, QObject* parent ) connect( mo, SIGNAL(aboutToFinish()), SLOT(enqueue()) ); // fires just before track finishes connect( mo, SIGNAL(currentSourceChanged( Phonon::MediaSource )), SLOT(onPhononSourceChanged( Phonon::MediaSource )) ); Phonon::createPath( mo, ao ); - -/// local rql - m_localRqlPlugin = new LocalRqlPlugin; - m_localRql = new LocalRql( QList() << m_localRqlPlugin ); -/// content resolver - m_trackResolver = new TrackResolver; - m_resolver = new Resolver( QList() << m_trackResolver ); } @@ -92,49 +81,11 @@ static inline QWidget* findTopLevelWidget( QObject* o ) void -MediaPipeline::playTags( QStringList tags ) -{ - for (int i = 0; i < tags.count(); ++i) - tags[i] = "tag:\"" + tags[i] + '"'; - QString const rql = tags.join( " or " ); - LocalRqlResult* result = m_localRql->startParse( rql ); - - if (!result) { - emit error( "Could not load Local Content plugin." ); - return; - } - - //FIXME this synconicity is evil, but so is asyncronicity here - QEventLoop loop; - connect( result, SIGNAL(parseGood( unsigned )), &loop, SLOT(quit()) ); - connect( result, SIGNAL(parseBad( int, QString, int )), &loop, SLOT(quit()) ); - loop.exec(); - - LocalRadioTrackSource* source = new LocalRadioTrackSource( result ); - play( source ); - source->start(); -} - - -void -MediaPipeline::playXspf( const QString& path ) -{ - XspfResolvingTrackSource* source = new XspfResolvingTrackSource( m_resolver, path ); - play( source ); - source->start(); -} - - -void -MediaPipeline::play( AbstractTrackSource* trackSource ) +MediaPipeline::play( TrackSource* trackSource ) { - delete m_source; +// delete m_source; m_source = trackSource; - - connect( m_source, SIGNAL(trackAvailable()), SLOT(enqueue()) ); - connect( m_source, SIGNAL(error( Ws::Error )), SLOT(onSourceError( Ws::Error )) ); - - emit preparing(); + enqueue(); } @@ -283,6 +234,8 @@ MediaPipeline::enqueue() // state changes, so we must prefilter them. if (!t.url().isValid()) continue; + + qDebug() << t.url().toString(); m_tracks[t.url()] = t; diff --git a/app/boffin/MediaPipeline.h b/app/boffin/MediaPipeline.h index fa82d2c7f..c3942275a 100644 --- a/app/boffin/MediaPipeline.h +++ b/app/boffin/MediaPipeline.h @@ -24,7 +24,6 @@ #include #include -class AbstractTrackSource; namespace Phonon { @@ -34,6 +33,7 @@ namespace Phonon } + class MediaPipeline : public QObject { Q_OBJECT @@ -44,8 +44,7 @@ class MediaPipeline : public QObject Phonon::State state() const; - void playTags( QStringList ); - void playXspf( const QString& path ); + void play( class TrackSource* ); public slots: void setPaused( bool ); @@ -53,7 +52,6 @@ public slots: void skip(); signals: - void preparing(); //before station starts, only happens one after play*() is called void started( const Track& ); void paused(); void resumed(); @@ -69,16 +67,10 @@ private slots: private: Phonon::MediaObject* mo; Phonon::AudioOutput* ao; - class ILocalRqlPlugin* m_localRqlPlugin; - class LocalRql* m_localRql; - class ITrackResolverPlugin* m_trackResolver; - class Resolver* m_resolver; - QPointer m_source; + TrackSource* m_source; QMap m_tracks; bool m_errorRecover; bool m_phonon_sucks; - - void play( AbstractTrackSource* ); }; diff --git a/app/boffin/PlaydarApi.h b/app/boffin/PlaydarApi.h new file mode 100755 index 000000000..bccaaeee6 --- /dev/null +++ b/app/boffin/PlaydarApi.h @@ -0,0 +1,30 @@ +#ifndef PLAYDAR_API_H +#define PLAYDAR_API_H + +#include "TPlaydarApi.hpp" +#include +#include + +typedef QList< QPair > ParamList; + +class PlaydarApiQtPolicy +{ +public: + static void paramsAdd(ParamList& params, const QString& name, const QString& value) + { + params.append(QPair< QString, QString>(name, value)); + } + + static QUrl createUrl(const QString& base, const QString& path, const ParamList& params) + { + QUrl url(base + path); + url.setQueryItems(params); + return url; + } +}; + + +typedef TPlaydarApi PlaydarApi; + +#endif + diff --git a/app/boffin/PlaydarPollingRequest.cpp b/app/boffin/PlaydarPollingRequest.cpp new file mode 100755 index 000000000..033ababb7 --- /dev/null +++ b/app/boffin/PlaydarPollingRequest.cpp @@ -0,0 +1,66 @@ +#include "PlaydarPollingRequest.h" +#include "jsonGetMember.hpp" + +using namespace std; +using namespace json_spirit; + + +PlaydarPollingRequest::PlaydarPollingRequest() + : m_pollCount(0) +{ +} + +PlaydarPollingRequest::~PlaydarPollingRequest() +{ +} + +void +PlaydarPollingRequest::start() +{ + issueRequest(); +} + +const string& +PlaydarPollingRequest::qid() +{ + return m_qid; +} + +void +PlaydarPollingRequest::handleResponse(const char *data, unsigned size) +{ + Value v; + if (read(string(data, size), v) && + jsonGetMember(v, "qid", m_qid)) + { + issuePoll(0); + return; + } + fail("bad json in poll response"); +} + +void +PlaydarPollingRequest::handlePollResponse(const char *data, unsigned size) +{ + m_pollCount++; + + Value v; + string qid; + int refresh_interval; + Object query; + Array results; + + if (read(string(data, size), v) && + jsonGetMember(v, "qid", qid) && + jsonGetMember(v, "refresh_interval", refresh_interval) && + jsonGetMember(v, "query", query) && + jsonGetMember(v, "results", results) ) + { + if (handleJsonPollResponse(m_pollCount, query, results)) { + issuePoll(refresh_interval); + return; + } + } + fail("bad json in poll response"); +} + diff --git a/app/boffin/PlaydarPollingRequest.h b/app/boffin/PlaydarPollingRequest.h new file mode 100755 index 000000000..4195f1de9 --- /dev/null +++ b/app/boffin/PlaydarPollingRequest.h @@ -0,0 +1,44 @@ +#ifndef PLAYDAR_POLLING_REQUEST_H +#define PLAYDAR_POLLING_REQUEST_H + +#include +#include "json_spirit/json_spirit.h" + + +// abstract base class for polling kind of requests +// +class PlaydarPollingRequest +{ +public: + PlaydarPollingRequest(); + ~PlaydarPollingRequest(); + + void start(); + + const std::string& qid(); + +private: + virtual void issueRequest() = 0; + virtual void issuePoll(unsigned msDelay) = 0; + + // return true if another poll should be made + virtual bool handleJsonPollResponse( + int poll, + const json_spirit::Object& query, + const json_spirit::Array& results) = 0; + + virtual void fail(const char* message) = 0; + +protected: + // derived class calls this with the response of the initial request + void handleResponse(const char *data, unsigned size); + + // derived class calls this with the response from a poll + void handlePollResponse(const char *data, unsigned size); + +private: + std::string m_qid; + int m_pollCount; +}; + +#endif diff --git a/app/boffin/PlaydarTagCloudModel.cpp b/app/boffin/PlaydarTagCloudModel.cpp new file mode 100755 index 000000000..53527f81c --- /dev/null +++ b/app/boffin/PlaydarTagCloudModel.cpp @@ -0,0 +1,151 @@ +#include "PlaydarTagCloudModel.h" +#include +#include + +PlaydarTagCloudModel::PlaydarTagCloudModel(PlaydarApi& p, class WsAccessManager* wam) +:m_api(p) +,m_wam(wam) +{ +} + +PlaydarTagCloudModel::~PlaydarTagCloudModel() +{ +} + +void +PlaydarTagCloudModel::startGetTags() +{ + qRegisterMetaType >("QList"); + + BoffinRequest* req = new BoffinRequest(m_wam, m_api); + connect(req, SIGNAL(tags(QList)), this, SLOT(onTags(QList))); + connect(req, SIGNAL(error()), this, SLOT(onTagError())); + req->start(); +} + +void +PlaydarTagCloudModel::onTags(QList tags) +{ + typedef QMap TagWeightMap; + typedef TagWeightMap::iterator TagWeightMapIterator; + TagWeightMap tagWeightMap; + + foreach(const BoffinTagItem& i, tags) { + if (!m_hostFilter.contains(i.m_host)) { + // host is not being filtered. + TagWeightMapIterator it = tagWeightMap.find(i.m_name); + if (it == tagWeightMap.end()) { + // first instance of this tag + tagWeightMap.insert(i.m_name, i.m_weight); + } else { + // multiple hosts have this tag; add their weights: + it.value() += i.m_weight; + } + } + } + + m_tagHash.clear(); + m_logTagHash.clear(); + + m_maxWeight = 0; + m_minLogWeight = FLT_MAX; + + for (TagWeightMapIterator it = tagWeightMap.begin(); it != tagWeightMap.end(); it++) + { + float& weight = it.value(); + const QString& name = it.key(); + + m_maxWeight = qMax( weight, m_maxWeight ); + float logWeight = log( weight ); + m_minLogWeight = qMin( logWeight, m_minLogWeight); + m_logTagHash.insert( logWeight, name ); + m_tagHash.insert( weight, name ); + } + + m_tags = tags; + + reset(); +} + +void +PlaydarTagCloudModel::onTagError() +{ + +} + +// virtual +QVariant +PlaydarTagCloudModel::data( const QModelIndex& index, int role ) const +{ + switch( role ) + { + case Qt::DisplayRole: + { + QMultiMap< float, QString >::const_iterator i = m_tagHash.constEnd(); + i -= index.row() + 1; + return i.value(); + } + + case PlaydarTagCloudModel::WeightRole: + { + QMultiMap< float, QString>::const_iterator i = m_tagHash.constEnd(); + i -= index.row() + 1; + return QVariant::fromValue((i.key() / m_maxWeight)); + } + + case PlaydarTagCloudModel::LinearWeightRole: + { + QMultiMap< float, QString >::const_iterator i = m_logTagHash.constEnd(); + i -= index.row() + 1; + return QVariant::fromValue( ( i.key() - m_minLogWeight ) / ((m_logTagHash.constEnd() -1 ).key() - m_minLogWeight)); + } + + default: + return QVariant(); + } +} + + +// virtual +QModelIndex +PlaydarTagCloudModel::index( int row, int column, const QModelIndex& parent /*= QModelIndex()*/) const +{ + return parent.isValid() ? + QModelIndex() : + createIndex( row, column ); +} + +//virtual +QModelIndex +PlaydarTagCloudModel::parent( const QModelIndex& ) const +{ + return QModelIndex(); +} + +//virtual +int +PlaydarTagCloudModel::rowCount( const QModelIndex& ) const +{ + return m_tagHash.count(); +} + +//virtual +int +PlaydarTagCloudModel::columnCount( const QModelIndex& ) const +{ + return 1; +} + +void +PlaydarTagCloudModel::setHostFilter(QSet hosts) +{ + m_hostFilter = hosts; + onTags(m_tags); // recalc things... +} + +void +PlaydarTagCloudModel::setTagMapping(QMap tagMap) +{ + m_tagMap = tagMap; + onTags(m_tags); +} diff --git a/app/boffin/PlaydarTagCloudModel.h b/app/boffin/PlaydarTagCloudModel.h new file mode 100755 index 000000000..dded2ec35 --- /dev/null +++ b/app/boffin/PlaydarTagCloudModel.h @@ -0,0 +1,72 @@ +/*************************************************************************** + * Copyright 2005-2009 Last.fm Ltd. * + * * + * This program 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 2 of the License, or * + * (at your option) any later version. * + * * + * This program 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 this program; if not, write to the * + * Free Software Foundation, Inc., * + * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. * + ***************************************************************************/ + +#ifndef PLAYDAR_TAG_CLOUD_MODEL_H +#define PLAYDAR_TAG_CLOUD_MODEL_H + +#include +#include +#include +#include +#include +#include "PlaydarApi.h" +#include "BoffinRequest.h" + + +class PlaydarTagCloudModel : public QAbstractItemModel +{ + Q_OBJECT + +public: + enum CustomRoles { WeightRole = Qt::UserRole, LinearWeightRole }; + + PlaydarTagCloudModel(PlaydarApi& p, class WsAccessManager* wam); + ~PlaydarTagCloudModel(void); + + void startGetTags(); + + void setHostFilter(QSet hosts); // exclude hosts from tagcloud + void setTagMapping(QMap tagMap); // map tagname -> preferred tagname + + virtual QModelIndex index( int row, int column, const QModelIndex& parent = QModelIndex()) const; + virtual QModelIndex parent( const QModelIndex& ) const; + virtual int rowCount( const QModelIndex& = QModelIndex() ) const; + virtual int columnCount( const QModelIndex& =QModelIndex() ) const; + virtual QVariant data( const QModelIndex&, int role = Qt::DisplayRole ) const; + +private slots: + void onTags(QList tags); + void onTagError(); + +private: + PlaydarApi m_api; + WsAccessManager* m_wam; + + QSet m_hostFilter; + QMap m_tagMap; + + QList m_tags; // the last taglist provided via onTags + + QMultiMap m_tagHash; + QMultiMap m_logTagHash; + float m_maxWeight; + float m_minLogWeight; +}; + +#endif diff --git a/app/boffin/TPlaydarApi.hpp b/app/boffin/TPlaydarApi.hpp new file mode 100755 index 000000000..10d2da49b --- /dev/null +++ b/app/boffin/TPlaydarApi.hpp @@ -0,0 +1,79 @@ +#ifndef TPLAYDAR_API_HPP +#define TPLAYDAR_API_HPP + +// Policy needs to provide: +// +// void paramsAdd(ParamsT& p, StringT name, StringT value) +// UrlT createUrl(StringT base, StringT path, ParamsT p) + +template +class TPlaydarApi : public Policy +{ +public: + TPlaydarApi(const StringT& baseUrl, const StringT& token) + : m_baseUrl(baseUrl) + , m_token(token) + { + } + + void setAuthToken(const StringT& token) + { + m_token = token; + } + + UrlT auth1(const StringT& productName) + { + ParamsT params; + paramsAdd( params, "website", productName); + paramsAdd( params, "name", productName); + return makeUrl("/auth/", params); + } + + UrlT apiCall(const ParamsT& params) + { + return makeUrl("/api/", params); + } + + UrlT stat() + { + ParamsT params; + paramsAdd(params, "method", "stat"); + paramsAdd(params, "auth", m_token); + return apiCall(params); + } + + UrlT getResults(const StringT& qid) + { + ParamsT params; + paramsAdd(params, "method", "get_results"); + paramsAdd(params, "qid", qid); + paramsAdd(params, "auth", m_token); + return apiCall(params); + } + + // boffin does its own thing: + UrlT boffinTagcloud() + { + ParamsT params; + paramsAdd(params, "auth", m_token); + return makeUrl("/boffin/tagcloud", params); + } + + UrlT boffinRql(const StringT& rql) + { + ParamsT params; + paramsAdd(params, "auth", m_token); + return makeUrl("/boffin/rql/" + rql, params); + } + +private: + UrlT makeUrl(const StringT& path, const ParamsT& params = ParamsT()) + { + return createUrl(m_baseUrl, path, params); + } + + StringT m_baseUrl; + StringT m_token; +}; + +#endif \ No newline at end of file diff --git a/app/boffin/TrackSource.cpp b/app/boffin/TrackSource.cpp new file mode 100755 index 000000000..6e2d4209c --- /dev/null +++ b/app/boffin/TrackSource.cpp @@ -0,0 +1,35 @@ +#include "TrackSource.h" + +TrackSource::TrackSource(BoffinRqlRequest* req) +{ + connect(req, SIGNAL(tracks(QList)), this, SLOT(onTracks(QList))); +} + +static Track toTrack(const BoffinPlayableItem& item) +{ + Track t; + MutableTrack mt(t); + mt.setArtist(item.m_artist); + mt.setAlbum(item.m_album); + mt.setTitle(item.m_track); + mt.setDuration(item.m_duration); + mt.setUrl(QUrl(item.m_url)); + mt.setSource(Track::Player); + //QString m_source; + //QString m_mimetype; + return t; +} + +Track +TrackSource::takeNextTrack() +{ + return m_tracks.isEmpty() ? Track() : toTrack(m_tracks.takeFirst()); +} + +void +TrackSource::onTracks(QList tracks) +{ + m_tracks = tracks; + emit ready(); +} + diff --git a/app/boffin/TrackSource.h b/app/boffin/TrackSource.h new file mode 100755 index 000000000..f09d7dfd7 --- /dev/null +++ b/app/boffin/TrackSource.h @@ -0,0 +1,28 @@ +#ifndef TRACK_SOURCE_H +#define TRACK_SOURCE_H + +#include +#include +#include "BoffinRqlRequest.h" + + +class TrackSource + : public QObject +{ + Q_OBJECT + +public: + TrackSource(BoffinRqlRequest* req); + Track takeNextTrack(); + +signals: + void ready(); + +private slots: + void onTracks(QList tracks); + +private: + QList m_tracks; +}; + +#endif diff --git a/app/boffin/boffin.pro b/app/boffin/boffin.pro index 6335150d6..21637eff3 100644 --- a/app/boffin/boffin.pro +++ b/app/boffin/boffin.pro @@ -1,4 +1,4 @@ -CONFIG += types unicorn resolver sqlite3 taglib radio boost +CONFIG += types unicorn radio boost QT += opengl sql phonon VERSION = 0.0.6 @@ -15,17 +15,3 @@ macx-g++:release { CONFIG += app_bundle } -SOURCES += ../client/Resolver.cpp \ - ../client/XspfTrackSource.cpp \ - ../client/ResolvingTrackSource.cpp \ - ../client/XspfResolvingTrackSource.cpp \ - ../client/LocalRql.cpp \ - ../client/LocalRadioTrackSource.cpp - -HEADERS += ../client/Resolver.h \ - ../client/XspfTrackSource.h \ - ../client/ResolvingTrackSource.h \ - ../client/XspfResolvingTrackSource.h \ - ../client/LocalRql.h \ - ../client/LocalRadioTrackSource.h - diff --git a/app/boffin/jsonGetMember.hpp b/app/boffin/jsonGetMember.hpp new file mode 100755 index 000000000..a2ea03380 --- /dev/null +++ b/app/boffin/jsonGetMember.hpp @@ -0,0 +1,53 @@ +/*************************************************************************** + * Copyright 2005-2009 Last.fm Ltd. * + * * + * This program 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 2 of the License, or * + * (at your option) any later version. * + * * + * This program 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 this program; if not, write to the * + * Free Software Foundation, Inc., * + * 51 Franklin Steet, Fifth Floor, Boston, MA 02110-1301, USA. * + ***************************************************************************/ + +#ifndef JSON_GET_MEMBER_HPP +#define JSON_GET_MEMBER_HPP + +#include +#include "json_spirit/json_spirit.h" + +// gets the named property from the json value +// +// returns true if it's got ok. +// +// todo: support nested objects, eg: "playable.result.id" +// todo: support arrays, eg: "results[1].id" +// +// ( T can be any type acceptable to json_spirit's get_value method ) +// +template +bool +jsonGetMember(const json_spirit::Value& value, const char* name, T& out) +{ + using namespace json_spirit; + + if (value.type() == obj_type) { + // yeah, only objects have values. + BOOST_FOREACH(const Pair& pair, value.get_obj()) { + if (pair.name_ == name) { + out = pair.value_.get_value(); + return true; + } + } + } + return false; +} + +#endif diff --git a/app/clientplugins/localresolver/LocalCollection.cpp b/app/clientplugins/localresolver/LocalCollection.cpp index 41a06b96b..2914aca74 100644 --- a/app/clientplugins/localresolver/LocalCollection.cpp +++ b/app/clientplugins/localresolver/LocalCollection.cpp @@ -142,7 +142,7 @@ LocalCollection::initDatabase() QUERY( "CREATE TABLE directories (" "id INTEGER NOT NULL PRIMARY KEY AUTOINCREMENT," "source INTEGER," // sources foreign key - "path TEXT NON NULL );" ); + "path TEXT NOT NULL );" ); QUERY( "CREATE INDEX directories_path_idx ON directories ( path );" ); @@ -154,7 +154,7 @@ LocalCollection::initDatabase() QUERY( "CREATE TABLE exclusions (" "id INTEGER NOT NULL PRIMARY KEY AUTOINCREMENT," - "path TEXT NON NULL," + "path TEXT NOT NULL," "source INTEGER," // sources foreign key "subDirs INTEGER );" );