From b3d0a577bbd825c5414f7485dba07736d3f5584d Mon Sep 17 00:00:00 2001 From: Steven Robertson Date: Wed, 24 Nov 2010 09:39:29 +0000 Subject: [PATCH 001/329] Updating the installer images. --- admin/win/page_header.bmp | Bin 25818 -> 25818 bytes admin/win/welcome.bmp | Bin 154542 -> 154542 bytes 2 files changed, 0 insertions(+), 0 deletions(-) diff --git a/admin/win/page_header.bmp b/admin/win/page_header.bmp index 2ca78cd428c121dd7fdd1a76afb1fc56d0e7dbfc..c50025a22b810a7e755768c16e73ce7f1ec97ea1 100755 GIT binary patch delta 3985 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 IzVPV3042x2tN;K2 diff --git a/admin/win/welcome.bmp b/admin/win/welcome.bmp index 8fc4078bf76dadc2352b1ac84a354af657c692c1..580504f50354f345e789f921266ebcdd518037ff 100755 GIT binary patch literal 154542 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 literal 154542 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

%0oLd6n+-L^Qu_;|s~#8-UwifzEAf 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 Z_!u=lz?xBa`E}(0Yrn?FsPSO^{{a58ip&52 From 7778c92a171a0313e19100afbf7061645573734b Mon Sep 17 00:00:00 2001 From: Christian Muehlhaeuser Date: Thu, 17 Mar 2011 09:19:47 +0100 Subject: [PATCH 002/329] * Fix loading spinner being stuck in certain cases. --- src/libtomahawk/playlist/collectionflatmodel.cpp | 12 +++++++----- src/libtomahawk/playlist/collectionflatmodel.h | 3 ++- src/libtomahawk/playlist/playlistmanager.cpp | 15 +++++++++------ 3 files changed, 18 insertions(+), 12 deletions(-) diff --git a/src/libtomahawk/playlist/collectionflatmodel.cpp b/src/libtomahawk/playlist/collectionflatmodel.cpp index 8268df1fb..5cbf2ab56 100644 --- a/src/libtomahawk/playlist/collectionflatmodel.cpp +++ b/src/libtomahawk/playlist/collectionflatmodel.cpp @@ -44,6 +44,7 @@ CollectionFlatModel::headerData( int section, Qt::Orientation orientation, int r return TrackModel::headerData( section, orientation, role ); } + void CollectionFlatModel::addCollections( const QList< collection_ptr >& collections ) { @@ -184,13 +185,14 @@ CollectionFlatModel::onTracksAdded( const QList& tracks ) { qDebug() << Q_FUNC_INFO << tracks.count() << rowCount( QModelIndex() ); - if( !m_loadingCollections.isEmpty() && sender() && qobject_cast< Collection* >( sender() ) ) { // we are keeping track and are called as a slot + if( !m_loadingCollections.isEmpty() && sender() && qobject_cast< Collection* >( sender() ) ) + { + // we are keeping track and are called as a slot m_loadingCollections.removeAll( qobject_cast< Collection* >( sender() ) ); - - if( m_loadingCollections.isEmpty() ) - emit doneLoadingCollections(); } - + if( m_loadingCollections.isEmpty() ) + emit doneLoadingCollections(); + bool kickOff = m_tracksToAdd.isEmpty(); m_tracksToAdd << tracks; diff --git a/src/libtomahawk/playlist/collectionflatmodel.h b/src/libtomahawk/playlist/collectionflatmodel.h index 5e32ce030..9eeafee52 100644 --- a/src/libtomahawk/playlist/collectionflatmodel.h +++ b/src/libtomahawk/playlist/collectionflatmodel.h @@ -35,7 +35,7 @@ public: QVariant headerData( int section, Qt::Orientation orientation, int role ) const; void addCollections( const QList< Tomahawk::collection_ptr >& collections ); - + void addCollection( const Tomahawk::collection_ptr& collection ); void removeCollection( const Tomahawk::collection_ptr& collection ); @@ -50,6 +50,7 @@ signals: void itemSizeChanged( const QModelIndex& index ); void doneLoadingCollections(); + private slots: void onDataChanged(); diff --git a/src/libtomahawk/playlist/playlistmanager.cpp b/src/libtomahawk/playlist/playlistmanager.cpp index 158de0f12..9de989a64 100644 --- a/src/libtomahawk/playlist/playlistmanager.cpp +++ b/src/libtomahawk/playlist/playlistmanager.cpp @@ -255,10 +255,10 @@ PlaylistManager::show( const Tomahawk::collection_ptr& collection ) view->setModel( model ); view->setFrameShape( QFrame::NoFrame ); view->setAttribute( Qt::WA_MacShowFocusRect, 0 ); - model->addCollection( collection ); - + m_loadingSpinner->fadeIn(); connect( model, SIGNAL( doneLoadingCollections() ), m_loadingSpinner, SLOT( fadeOut() ) ); + model->addCollection( collection ); m_collectionViews.insert( collection, view ); } @@ -339,18 +339,23 @@ PlaylistManager::showSuperCollection() QList< collection_ptr > toAdd; foreach( const Tomahawk::source_ptr& source, SourceList::instance()->sources() ) { + bool addedStuff = false; if ( !m_superCollections.contains( source->collection() ) ) { m_superCollections.append( source->collection() ); toAdd << source->collection(); m_superAlbumModel->addCollection( source->collection() ); + addedStuff = true; } + if ( addedStuff ) + m_loadingSpinner->fadeIn(); + m_superCollectionFlatModel->setTitle( tr( "All available tracks" ) ); m_superAlbumModel->setTitle( tr( "All available albums" ) ); } m_superCollectionFlatModel->addCollections( toAdd ); - + if ( m_currentMode == 0 ) { setPage( m_superCollectionView ); @@ -359,9 +364,7 @@ PlaylistManager::showSuperCollection() { setPage( m_superAlbumView ); } - - m_loadingSpinner->fadeIn(); - + emit numSourcesChanged( m_superCollections.count() ); return true; From 9934b6e1d41d09abea8ef7b4aebf2bb3293969ba Mon Sep 17 00:00:00 2001 From: Christian Muehlhaeuser Date: Thu, 17 Mar 2011 09:35:13 +0100 Subject: [PATCH 003/329] * This certainly slowed down collection loading. --- src/libtomahawk/playlist/collectionflatmodel.cpp | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/src/libtomahawk/playlist/collectionflatmodel.cpp b/src/libtomahawk/playlist/collectionflatmodel.cpp index 5cbf2ab56..825c75746 100644 --- a/src/libtomahawk/playlist/collectionflatmodel.cpp +++ b/src/libtomahawk/playlist/collectionflatmodel.cpp @@ -210,8 +210,8 @@ CollectionFlatModel::processTracksToAdd() int maxc = qMin( chunkSize, m_tracksToAdd.count() ); int c = rowCount( QModelIndex() ); - //emit beginInsertRows( QModelIndex(), c, c + maxc - 1 ); - beginResetModel(); + emit beginInsertRows( QModelIndex(), c, c + maxc - 1 ); + //beginResetModel(); PlItem* plitem; QList< Tomahawk::query_ptr >::iterator iter = m_tracksToAdd.begin(); @@ -229,8 +229,8 @@ CollectionFlatModel::processTracksToAdd() m_tracksToAdd.erase( m_tracksToAdd.begin(), iter ); - endResetModel(); - //emit endInsertRows(); + //endResetModel(); + emit endInsertRows(); qDebug() << Q_FUNC_INFO << rowCount( QModelIndex() ); if ( m_tracksToAdd.count() ) From 9f7e1b5f75ddecc36bbb215c15299505839b1d17 Mon Sep 17 00:00:00 2001 From: Leo Franchi Date: Thu, 17 Mar 2011 19:52:12 -0400 Subject: [PATCH 004/329] move loading spinner to each trackview --- src/libtomahawk/playlist/collectionflatmodel.cpp | 14 +++++++------- src/libtomahawk/playlist/collectionflatmodel.h | 4 +--- src/libtomahawk/playlist/playlistmanager.cpp | 12 ------------ src/libtomahawk/playlist/playlistmanager.h | 2 -- src/libtomahawk/playlist/trackmodel.h | 2 ++ src/libtomahawk/playlist/trackview.cpp | 5 +++++ src/libtomahawk/playlist/trackview.h | 2 ++ 7 files changed, 17 insertions(+), 24 deletions(-) diff --git a/src/libtomahawk/playlist/collectionflatmodel.cpp b/src/libtomahawk/playlist/collectionflatmodel.cpp index 825c75746..0d6b92fdf 100644 --- a/src/libtomahawk/playlist/collectionflatmodel.cpp +++ b/src/libtomahawk/playlist/collectionflatmodel.cpp @@ -51,19 +51,17 @@ CollectionFlatModel::addCollections( const QList< collection_ptr >& collections qDebug() << Q_FUNC_INFO << "Adding collections!"; foreach( const collection_ptr& col, collections ) { - if( !col->isLoaded() ) - m_loadingCollections << col.data(); - addCollection( col ); } - if( m_loadingCollections.isEmpty() ) - emit doneLoadingCollections(); + // we are waiting for some to load + if( !m_loadingCollections.isEmpty() ) + emit loadingStarted(); } void -CollectionFlatModel::addCollection( const collection_ptr& collection ) +CollectionFlatModel::addCollection( const collection_ptr& collection, bool sendNotifications ) { qDebug() << Q_FUNC_INFO << collection->name() << collection->source()->id() @@ -81,6 +79,8 @@ CollectionFlatModel::addCollection( const collection_ptr& collection ) collection->tracks(); // data will arrive via signals m_loadingCollections << collection.data(); + if( sendNotifications ) + emit loadingStarted(); } if ( collection->source()->isLocal() ) @@ -191,7 +191,7 @@ CollectionFlatModel::onTracksAdded( const QList& tracks ) m_loadingCollections.removeAll( qobject_cast< Collection* >( sender() ) ); } if( m_loadingCollections.isEmpty() ) - emit doneLoadingCollections(); + emit loadingFinished(); bool kickOff = m_tracksToAdd.isEmpty(); m_tracksToAdd << tracks; diff --git a/src/libtomahawk/playlist/collectionflatmodel.h b/src/libtomahawk/playlist/collectionflatmodel.h index 9eeafee52..5c1ffeb34 100644 --- a/src/libtomahawk/playlist/collectionflatmodel.h +++ b/src/libtomahawk/playlist/collectionflatmodel.h @@ -36,7 +36,7 @@ public: void addCollections( const QList< Tomahawk::collection_ptr >& collections ); - void addCollection( const Tomahawk::collection_ptr& collection ); + void addCollection( const Tomahawk::collection_ptr& collection, bool sendNotifications = true ); void removeCollection( const Tomahawk::collection_ptr& collection ); void addFilteredCollection( const Tomahawk::collection_ptr& collection, unsigned int amount, DatabaseCommand_AllTracks::SortOrder order ); @@ -49,8 +49,6 @@ signals: void itemSizeChanged( const QModelIndex& index ); - void doneLoadingCollections(); - private slots: void onDataChanged(); diff --git a/src/libtomahawk/playlist/playlistmanager.cpp b/src/libtomahawk/playlist/playlistmanager.cpp index 9de989a64..faf1308e4 100644 --- a/src/libtomahawk/playlist/playlistmanager.cpp +++ b/src/libtomahawk/playlist/playlistmanager.cpp @@ -27,7 +27,6 @@ #include "widgets/welcomewidget.h" #include "widgets/infowidgets/sourceinfowidget.h" -#include "dynamic/widgets/LoadingSpinner.h" #define FILTER_TIMEOUT 280 @@ -48,7 +47,6 @@ PlaylistManager::PlaylistManager( QObject* parent ) , m_widget( new QWidget() ) , m_welcomeWidget( new WelcomeWidget() ) , m_currentMode( 0 ) - , m_loadingSpinner( 0 ) { s_instance = this; @@ -101,9 +99,6 @@ PlaylistManager::PlaylistManager( QObject* parent ) m_widget->layout()->setMargin( 0 ); m_widget->layout()->setSpacing( 0 ); - m_loadingSpinner = new LoadingSpinner( m_widget ); - connect( m_superCollectionFlatModel, SIGNAL( doneLoadingCollections() ), m_loadingSpinner, SLOT( fadeOut() ) ); - connect( &m_filterTimer, SIGNAL( timeout() ), SLOT( applyFilter() ) ); connect( m_topbar, SIGNAL( filterTextChanged( QString ) ), @@ -256,8 +251,6 @@ PlaylistManager::show( const Tomahawk::collection_ptr& collection ) view->setFrameShape( QFrame::NoFrame ); view->setAttribute( Qt::WA_MacShowFocusRect, 0 ); - m_loadingSpinner->fadeIn(); - connect( model, SIGNAL( doneLoadingCollections() ), m_loadingSpinner, SLOT( fadeOut() ) ); model->addCollection( collection ); m_collectionViews.insert( collection, view ); @@ -339,18 +332,13 @@ PlaylistManager::showSuperCollection() QList< collection_ptr > toAdd; foreach( const Tomahawk::source_ptr& source, SourceList::instance()->sources() ) { - bool addedStuff = false; if ( !m_superCollections.contains( source->collection() ) ) { m_superCollections.append( source->collection() ); toAdd << source->collection(); m_superAlbumModel->addCollection( source->collection() ); - addedStuff = true; } - if ( addedStuff ) - m_loadingSpinner->fadeIn(); - m_superCollectionFlatModel->setTitle( tr( "All available tracks" ) ); m_superAlbumModel->setTitle( tr( "All available albums" ) ); } diff --git a/src/libtomahawk/playlist/playlistmanager.h b/src/libtomahawk/playlist/playlistmanager.h index a491fbd91..4cab2f398 100644 --- a/src/libtomahawk/playlist/playlistmanager.h +++ b/src/libtomahawk/playlist/playlistmanager.h @@ -11,7 +11,6 @@ #include "dllmacro.h" -class LoadingSpinner; class AnimatedSplitter; class AlbumModel; class AlbumView; @@ -144,7 +143,6 @@ private: CollectionFlatModel* m_superCollectionFlatModel; CollectionView* m_superCollectionView; WelcomeWidget* m_welcomeWidget; - LoadingSpinner* m_loadingSpinner; QList< Tomahawk::collection_ptr > m_superCollections; diff --git a/src/libtomahawk/playlist/trackmodel.h b/src/libtomahawk/playlist/trackmodel.h index 2f001d5b4..0cccabab7 100644 --- a/src/libtomahawk/playlist/trackmodel.h +++ b/src/libtomahawk/playlist/trackmodel.h @@ -70,6 +70,8 @@ signals: void trackCountChanged( unsigned int tracks ); + void loadingStarted(); + void loadingFinished(); public slots: virtual void setCurrentItem( const QModelIndex& index ); diff --git a/src/libtomahawk/playlist/trackview.cpp b/src/libtomahawk/playlist/trackview.cpp index bdc139773..696a9be4a 100644 --- a/src/libtomahawk/playlist/trackview.cpp +++ b/src/libtomahawk/playlist/trackview.cpp @@ -8,6 +8,7 @@ #include "audio/audioengine.h" #include "utils/tomahawkutils.h" #include "widgets/overlaywidget.h" +#include "dynamic/widgets/LoadingSpinner.h" #include "trackheader.h" #include "playlistmanager.h" @@ -25,6 +26,7 @@ TrackView::TrackView( QWidget* parent ) , m_delegate( 0 ) , m_header( new TrackHeader( this ) ) , m_overlay( new OverlayWidget( this ) ) + , m_loadingSpinner( new LoadingSpinner( this ) ) , m_resizing( false ) { setSortingEnabled( false ); @@ -92,6 +94,9 @@ TrackView::setModel( TrackModel* model ) } connect( m_model, SIGNAL( itemSizeChanged( QModelIndex ) ), SLOT( onItemResized( QModelIndex ) ) ); + connect( m_model, SIGNAL( loadingStarted() ), m_loadingSpinner, SLOT( fadeIn() ) ); + connect( m_model, SIGNAL( loadingFinished() ), m_loadingSpinner, SLOT( fadeOut() ) ); + connect( m_proxyModel, SIGNAL( filterChanged( QString ) ), SLOT( onFilterChanged( QString ) ) ); setAcceptDrops( true ); diff --git a/src/libtomahawk/playlist/trackview.h b/src/libtomahawk/playlist/trackview.h index 337d004a5..7702fc6aa 100644 --- a/src/libtomahawk/playlist/trackview.h +++ b/src/libtomahawk/playlist/trackview.h @@ -8,6 +8,7 @@ #include "dllmacro.h" +class LoadingSpinner; class PlaylistInterface; class TrackHeader; class TrackModel; @@ -67,6 +68,7 @@ private: PlaylistItemDelegate* m_delegate; TrackHeader* m_header; OverlayWidget* m_overlay; + LoadingSpinner* m_loadingSpinner; bool m_resizing; bool m_dragging; From ae6ec8eba81b7778f621f178dc076eef2729cbdb Mon Sep 17 00:00:00 2001 From: Jeff Mitchell Date: Thu, 17 Mar 2011 21:00:47 -0400 Subject: [PATCH 005/329] As discussed, add a gettomahawk.com link to the end of a tweet --- src/sip/twitter/twitterconfigwidget.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/sip/twitter/twitterconfigwidget.cpp b/src/sip/twitter/twitterconfigwidget.cpp index 6b193aa8a..a3b154989 100644 --- a/src/sip/twitter/twitterconfigwidget.cpp +++ b/src/sip/twitter/twitterconfigwidget.cpp @@ -116,7 +116,7 @@ TwitterConfigWidget::postGotTomahawkStatusAuthVerifyReply( const QTweetUser &use connect( statUpdate, SIGNAL( postedStatus(const QTweetStatus &) ), SLOT( postGotTomahawkStatusUpdateReply(const QTweetStatus &) ) ); connect( statUpdate, SIGNAL( error(QTweetNetBase::ErrorCode, const QString&) ), SLOT( postGotTomahawkStatusUpdateError(QTweetNetBase::ErrorCode, const QString &) ) ); QString uuid = QUuid::createUuid(); - statUpdate->post( QString( "Got Tomahawk? {" ) + Database::instance()->dbid() + QString( "} (" ) + uuid.mid( 1, 8 ) + QString( ")" ) ); + statUpdate->post( QString( "Got Tomahawk? {" ) + Database::instance()->dbid() + QString( "} (" ) + uuid.mid( 1, 8 ) + QString( ")" ) + QString( "http://gettomahawk.com" ) ); } From 18bf3d655cabddd28f9423a42be72576a68ef408 Mon Sep 17 00:00:00 2001 From: Christian Muehlhaeuser Date: Fri, 18 Mar 2011 02:52:00 +0100 Subject: [PATCH 006/329] * Fixed HTTP streaming. --- src/libtomahawk/audio/audioengine.cpp | 20 ++++++++++++++++++++ src/libtomahawk/audio/audioengine.h | 3 +++ 2 files changed, 23 insertions(+) diff --git a/src/libtomahawk/audio/audioengine.cpp b/src/libtomahawk/audio/audioengine.cpp index dcde13d61..2d6bc1465 100644 --- a/src/libtomahawk/audio/audioengine.cpp +++ b/src/libtomahawk/audio/audioengine.cpp @@ -62,6 +62,7 @@ AudioEngine::~AudioEngine() delete m_audio; } + void AudioEngine::playPause() { @@ -72,6 +73,7 @@ AudioEngine::playPause() } + void AudioEngine::play() { @@ -150,12 +152,14 @@ AudioEngine::setVolume( int percentage ) emit volumeChanged( percentage ); } + void AudioEngine::mute() { setVolume( 0 ); } + void AudioEngine::onTrackAboutToClose() { @@ -184,6 +188,13 @@ AudioEngine::loadTrack( const Tomahawk::result_ptr& result ) { setCurrentTrack( result ); io = Servent::instance()->getIODeviceForUrl( m_currentTrack ); + if ( m_currentTrack->url().startsWith( "http://" ) ) + { + m_readReady = false; + connect( io.data(), SIGNAL( downloadProgress( qint64, qint64 ) ), SLOT( onDownloadProgress( qint64, qint64 ) ) ); + } + else + m_readReady = true; if ( !io || io.isNull() ) { @@ -396,6 +407,14 @@ AudioEngine::setCurrentTrack( const Tomahawk::result_ptr& result ) } +void +AudioEngine::onDownloadProgress( qint64 recv, qint64 total ) +{ + if ( ( recv > 1024 * 32 ) || recv > total ) + m_readReady = true; +} + + void AudioEngine::run() { @@ -461,6 +480,7 @@ AudioEngine::loop() // are we cleanly at the end of a track, and ready for the next one? if ( !m_input.isNull() && m_input->atEnd() && + m_readReady && !m_input->bytesAvailable() && !m_audio->haveData() && !m_audio->isPaused() ) diff --git a/src/libtomahawk/audio/audioengine.h b/src/libtomahawk/audio/audioengine.h index 372550e8d..6e1fc045b 100644 --- a/src/libtomahawk/audio/audioengine.h +++ b/src/libtomahawk/audio/audioengine.h @@ -83,6 +83,8 @@ private slots: void loadPreviousTrack(); void loadNextTrack(); + void onDownloadProgress( qint64 recv, qint64 total ); + void setStreamData( long sampleRate, int channels ); void timerTriggered( unsigned int seconds ); @@ -111,6 +113,7 @@ private: PlaylistInterface* m_queue; QMutex m_mutex; + bool m_readReady; unsigned int m_timeElapsed; int m_i; From 25d0cee1f833143996ab0c7acd15fb8c2604519a Mon Sep 17 00:00:00 2001 From: Jeff Mitchell Date: Thu, 17 Mar 2011 22:22:19 -0400 Subject: [PATCH 007/329] oops, forgot a space --- src/sip/twitter/twitterconfigwidget.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/sip/twitter/twitterconfigwidget.cpp b/src/sip/twitter/twitterconfigwidget.cpp index a3b154989..1e468278b 100644 --- a/src/sip/twitter/twitterconfigwidget.cpp +++ b/src/sip/twitter/twitterconfigwidget.cpp @@ -116,7 +116,7 @@ TwitterConfigWidget::postGotTomahawkStatusAuthVerifyReply( const QTweetUser &use connect( statUpdate, SIGNAL( postedStatus(const QTweetStatus &) ), SLOT( postGotTomahawkStatusUpdateReply(const QTweetStatus &) ) ); connect( statUpdate, SIGNAL( error(QTweetNetBase::ErrorCode, const QString&) ), SLOT( postGotTomahawkStatusUpdateError(QTweetNetBase::ErrorCode, const QString &) ) ); QString uuid = QUuid::createUuid(); - statUpdate->post( QString( "Got Tomahawk? {" ) + Database::instance()->dbid() + QString( "} (" ) + uuid.mid( 1, 8 ) + QString( ")" ) + QString( "http://gettomahawk.com" ) ); + statUpdate->post( QString( "Got Tomahawk? {" ) + Database::instance()->dbid() + QString( "} (" ) + uuid.mid( 1, 8 ) + QString( ")" ) + QString( " http://gettomahawk.com" ) ); } From b631a6b3bda8da1978812113ca68f8ffed53acf2 Mon Sep 17 00:00:00 2001 From: Leo Franchi Date: Thu, 17 Mar 2011 22:58:25 -0400 Subject: [PATCH 008/329] remove qtscriptresolvers --- src/resolvers/qtscriptresolver.cpp | 4 ++++ src/resolvers/qtscriptresolver.h | 3 +++ 2 files changed, 7 insertions(+) diff --git a/src/resolvers/qtscriptresolver.cpp b/src/resolvers/qtscriptresolver.cpp index cfa1925d2..1455c77c7 100644 --- a/src/resolvers/qtscriptresolver.cpp +++ b/src/resolvers/qtscriptresolver.cpp @@ -39,12 +39,15 @@ QtScriptResolver::QtScriptResolver( const QString& scriptPath ) m_engine->moveToThread( m_thread ); m_ready = true; Tomahawk::Pipeline::instance()->addResolver( this ); + + connect( this, SIGNAL( destroyed( QObject* ) ), m_thread, SLOT( deleteLater() ) ); } QtScriptResolver::~QtScriptResolver() { Tomahawk::Pipeline::instance()->removeResolver( this ); + delete m_engine; } @@ -109,4 +112,5 @@ void QtScriptResolver::stop() { m_stopped = true; + emit finished(); } diff --git a/src/resolvers/qtscriptresolver.h b/src/resolvers/qtscriptresolver.h index 9df82a1f4..873122e71 100644 --- a/src/resolvers/qtscriptresolver.h +++ b/src/resolvers/qtscriptresolver.h @@ -56,6 +56,9 @@ public slots: virtual void resolve( const Tomahawk::query_ptr& query ); virtual void stop(); +signals: + void finished(); + private slots: private: From c9ca6b3a79cac1c5be6d6926359eb1332221871c Mon Sep 17 00:00:00 2001 From: Christian Muehlhaeuser Date: Fri, 18 Mar 2011 05:51:21 +0100 Subject: [PATCH 009/329] * Don't show offline results in SuperCollection and general collection loading speed-ups. --- .../playlist/collectionflatmodel.cpp | 43 ++++++------------- .../playlist/collectionflatmodel.h | 5 --- src/libtomahawk/playlist/playlistmanager.cpp | 9 ++-- src/libtomahawk/playlist/trackmodel.h | 1 + src/libtomahawk/playlist/trackproxymodel.cpp | 5 ++- src/libtomahawk/playlist/trackproxymodel.h | 4 ++ 6 files changed, 25 insertions(+), 42 deletions(-) diff --git a/src/libtomahawk/playlist/collectionflatmodel.cpp b/src/libtomahawk/playlist/collectionflatmodel.cpp index 0d6b92fdf..02d56a351 100644 --- a/src/libtomahawk/playlist/collectionflatmodel.cpp +++ b/src/libtomahawk/playlist/collectionflatmodel.cpp @@ -24,27 +24,6 @@ CollectionFlatModel::~CollectionFlatModel() } -int -CollectionFlatModel::columnCount( const QModelIndex& parent ) const -{ - return TrackModel::columnCount( parent ); -} - - -QVariant -CollectionFlatModel::data( const QModelIndex& index, int role ) const -{ - return TrackModel::data( index, role ); -} - - -QVariant -CollectionFlatModel::headerData( int section, Qt::Orientation orientation, int role ) const -{ - return TrackModel::headerData( section, orientation, role ); -} - - void CollectionFlatModel::addCollections( const QList< collection_ptr >& collections ) { @@ -53,7 +32,7 @@ CollectionFlatModel::addCollections( const QList< collection_ptr >& collections { addCollection( col ); } - + // we are waiting for some to load if( !m_loadingCollections.isEmpty() ) emit loadingStarted(); @@ -72,17 +51,19 @@ CollectionFlatModel::addCollection( const collection_ptr& collection, bool sendN connect( collection.data(), SIGNAL( tracksRemoved( QList ) ), SLOT( onTracksRemoved( QList ) ) ); + if( sendNotifications ) + emit loadingStarted(); + if ( collection->isLoaded() ) + { onTracksAdded( collection->tracks() ); + } else { collection->tracks(); // data will arrive via signals - m_loadingCollections << collection.data(); - if( sendNotifications ) - emit loadingStarted(); } - + if ( collection->source()->isLocal() ) setTitle( tr( "Your Collection" ) ); else @@ -190,8 +171,6 @@ CollectionFlatModel::onTracksAdded( const QList& tracks ) // we are keeping track and are called as a slot m_loadingCollections.removeAll( qobject_cast< Collection* >( sender() ) ); } - if( m_loadingCollections.isEmpty() ) - emit loadingFinished(); bool kickOff = m_tracksToAdd.isEmpty(); m_tracksToAdd << tracks; @@ -206,7 +185,7 @@ CollectionFlatModel::onTracksAdded( const QList& tracks ) void CollectionFlatModel::processTracksToAdd() { - int chunkSize = 500000; + int chunkSize = 5000; int maxc = qMin( chunkSize, m_tracksToAdd.count() ); int c = rowCount( QModelIndex() ); @@ -218,7 +197,6 @@ CollectionFlatModel::processTracksToAdd() for( int i = 0; i < maxc; ++i ) { - plitem = new PlItem( *iter, m_rootItem ); plitem->index = createIndex( m_rootItem->children.count() - 1, 0, plitem ); @@ -229,6 +207,9 @@ CollectionFlatModel::processTracksToAdd() m_tracksToAdd.erase( m_tracksToAdd.begin(), iter ); + if ( m_tracksToAdd.isEmpty() && m_loadingCollections.isEmpty() ) + emit loadingFinished(); + //endResetModel(); emit endInsertRows(); qDebug() << Q_FUNC_INFO << rowCount( QModelIndex() ); @@ -278,7 +259,7 @@ CollectionFlatModel::onDataChanged() // emit itemSizeChanged( p->index ); if ( p ) - emit dataChanged( p->index, p->index.sibling( p->index.row(), columnCount() - 1 ) ); + emit dataChanged( p->index, p->index.sibling( p->index.row(), columnCount( QModelIndex() ) - 1 ) ); } diff --git a/src/libtomahawk/playlist/collectionflatmodel.h b/src/libtomahawk/playlist/collectionflatmodel.h index 5c1ffeb34..c2cf83d38 100644 --- a/src/libtomahawk/playlist/collectionflatmodel.h +++ b/src/libtomahawk/playlist/collectionflatmodel.h @@ -27,13 +27,8 @@ public: explicit CollectionFlatModel( QObject* parent = 0 ); ~CollectionFlatModel(); - int columnCount( const QModelIndex& parent = QModelIndex() ) const; - virtual int trackCount() const { return rowCount( QModelIndex() ) + m_tracksToAdd.count(); } - QVariant data( const QModelIndex& index, int role ) const; - QVariant headerData( int section, Qt::Orientation orientation, int role ) const; - void addCollections( const QList< Tomahawk::collection_ptr >& collections ); void addCollection( const Tomahawk::collection_ptr& collection, bool sendNotifications = true ); diff --git a/src/libtomahawk/playlist/playlistmanager.cpp b/src/libtomahawk/playlist/playlistmanager.cpp index faf1308e4..8e43d9c49 100644 --- a/src/libtomahawk/playlist/playlistmanager.cpp +++ b/src/libtomahawk/playlist/playlistmanager.cpp @@ -56,7 +56,7 @@ PlaylistManager::PlaylistManager( QObject* parent ) m_topbar = new TopBar(); m_infobar = new InfoBar(); m_stack = new QStackedWidget(); - + QFrame* line = new QFrame(); line->setFrameStyle( QFrame::HLine ); line->setStyleSheet( "border: 1px solid gray;" ); @@ -86,13 +86,14 @@ PlaylistManager::PlaylistManager( QObject* parent ) m_superCollectionView->setModel( m_superCollectionFlatModel ); m_superCollectionView->setFrameShape( QFrame::NoFrame ); m_superCollectionView->setAttribute( Qt::WA_MacShowFocusRect, 0 ); + m_superCollectionView->proxyModel()->setShowOfflineResults( false ); m_superAlbumView = new AlbumView(); m_superAlbumModel = new AlbumModel( m_superAlbumView ); m_superAlbumView->setModel( m_superAlbumModel ); m_superAlbumView->setFrameShape( QFrame::NoFrame ); m_superAlbumView->setAttribute( Qt::WA_MacShowFocusRect, 0 ); - + m_stack->setContentsMargins( 0, 0, 0, 0 ); m_widget->setContentsMargins( 0, 0, 0, 0 ); m_widget->layout()->setContentsMargins( 0, 0, 0, 0 ); @@ -106,10 +107,10 @@ PlaylistManager::PlaylistManager( QObject* parent ) connect( m_topbar, SIGNAL( flatMode() ), SLOT( setTableMode() ) ); - + connect( m_topbar, SIGNAL( artistMode() ), SLOT( setTreeMode() ) ); - + connect( m_topbar, SIGNAL( albumMode() ), SLOT( setAlbumMode() ) ); } diff --git a/src/libtomahawk/playlist/trackmodel.h b/src/libtomahawk/playlist/trackmodel.h index 0cccabab7..45a3533ff 100644 --- a/src/libtomahawk/playlist/trackmodel.h +++ b/src/libtomahawk/playlist/trackmodel.h @@ -72,6 +72,7 @@ signals: void loadingStarted(); void loadingFinished(); + public slots: virtual void setCurrentItem( const QModelIndex& index ); diff --git a/src/libtomahawk/playlist/trackproxymodel.cpp b/src/libtomahawk/playlist/trackproxymodel.cpp index 86ec2376f..46012bfb5 100644 --- a/src/libtomahawk/playlist/trackproxymodel.cpp +++ b/src/libtomahawk/playlist/trackproxymodel.cpp @@ -14,6 +14,7 @@ TrackProxyModel::TrackProxyModel( QObject* parent ) , m_model( 0 ) , m_repeatMode( PlaylistInterface::NoRepeat ) , m_shuffled( false ) + , m_showOfflineResults( true ) { qsrand( QTime( 0, 0, 0 ).secsTo( QTime::currentTime() ) ); @@ -146,8 +147,8 @@ TrackProxyModel::filterAcceptsRow( int sourceRow, const QModelIndex& sourceParen if ( q->numResults() ) r = q->results().first(); -// if ( !r.isNull() && !r->collection()->source()->isOnline() ) -// return false; + if ( !m_showOfflineResults && !r.isNull() && !r->collection()->source()->isOnline() ) + return false; if ( filterRegExp().isEmpty() ) return true; diff --git a/src/libtomahawk/playlist/trackproxymodel.h b/src/libtomahawk/playlist/trackproxymodel.h index 7faab3086..5ced4b802 100644 --- a/src/libtomahawk/playlist/trackproxymodel.h +++ b/src/libtomahawk/playlist/trackproxymodel.h @@ -38,6 +38,9 @@ public: virtual PlaylistInterface::RepeatMode repeatMode() const { return m_repeatMode; } virtual bool shuffled() const { return m_shuffled; } + bool showOfflineResults() const { return m_showOfflineResults; } + void setShowOfflineResults( bool b ) { m_showOfflineResults = b; } + PlItem* itemFromIndex( const QModelIndex& index ) const { return sourceModel()->itemFromIndex( index ); } signals: @@ -60,6 +63,7 @@ private: TrackModel* m_model; RepeatMode m_repeatMode; bool m_shuffled; + bool m_showOfflineResults; }; #endif // TRACKPROXYMODEL_H From 518002dfd178213a46d5ce0752cd0aafaf4aa339 Mon Sep 17 00:00:00 2001 From: Christian Muehlhaeuser Date: Fri, 18 Mar 2011 06:03:29 +0100 Subject: [PATCH 010/329] * Open the SettingsDialog with the first tab. * Don't restart overlay animation if it's already in progress / finished. --- src/libtomahawk/widgets/overlaywidget.cpp | 5 ++--- src/libtomahawk/widgets/overlaywidget.h | 1 + src/settingsdialog.ui | 2 +- 3 files changed, 4 insertions(+), 4 deletions(-) diff --git a/src/libtomahawk/widgets/overlaywidget.cpp b/src/libtomahawk/widgets/overlaywidget.cpp index 780d5c4ce..2abceb272 100644 --- a/src/libtomahawk/widgets/overlaywidget.cpp +++ b/src/libtomahawk/widgets/overlaywidget.cpp @@ -19,7 +19,7 @@ OverlayWidget::OverlayWidget( QWidget* parent ) setAttribute( Qt::WA_TranslucentBackground, true ); setOpacity( m_opacity ); - + m_timer.setSingleShot( true ); connect( &m_timer, SIGNAL( timeout() ), this, SLOT( hide() ) ); } @@ -63,10 +63,9 @@ OverlayWidget::show( int timeoutSecs ) QPropertyAnimation* animation = new QPropertyAnimation( this, "opacity" ); animation->setDuration( FADING_DURATION ); - animation->setStartValue( 0.00 ); animation->setEndValue( OPACITY ); animation->start(); - + if( timeoutSecs > 0 ) m_timer.start( timeoutSecs * 1000 ); } diff --git a/src/libtomahawk/widgets/overlaywidget.h b/src/libtomahawk/widgets/overlaywidget.h index 5eeecdde9..7efe77af8 100644 --- a/src/libtomahawk/widgets/overlaywidget.h +++ b/src/libtomahawk/widgets/overlaywidget.h @@ -23,6 +23,7 @@ public: void setText( const QString& text ); bool shown() const; + public slots: void show( int timeoutSecs = 0 ); void hide(); diff --git a/src/settingsdialog.ui b/src/settingsdialog.ui index 282665429..3a07d4254 100644 --- a/src/settingsdialog.ui +++ b/src/settingsdialog.ui @@ -23,7 +23,7 @@ - 1 + 0 From 6c946fa130d3b8f86461ffc79d378c475794ceb7 Mon Sep 17 00:00:00 2001 From: Christian Muehlhaeuser Date: Fri, 18 Mar 2011 18:36:46 +0100 Subject: [PATCH 011/329] * Build win32 executable properly, so no more console/debug window. --- src/CMakeLists.txt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/CMakeLists.txt b/src/CMakeLists.txt index 184dfe414..8514f7f8d 100644 --- a/src/CMakeLists.txt +++ b/src/CMakeLists.txt @@ -197,7 +197,7 @@ IF( APPLE ) ) ENDIF( APPLE ) IF( WIN32 ) - ADD_EXECUTABLE( tomahawk ${final_src} ) + ADD_EXECUTABLE( tomahawk WIN32 ${final_src} ) ENDIF( WIN32 ) MESSAGE( STATUS "OS_SPECIFIC_LINK_LIBRARIES: ${OS_SPECIFIC_LINK_LIBRARIES}" ) From 69e9cda5e6a8d1a15ed906b2331e6a84344f5abc Mon Sep 17 00:00:00 2001 From: Leo Franchi Date: Fri, 18 Mar 2011 14:45:42 -0400 Subject: [PATCH 012/329] fix some cmake stuff. add uninstall support change plugin names, load changed names, don't install static libs, etc --- CMakeLists.txt | 12 ++++++++ cmake_uninstall.cmake.in | 21 ++++++++++++++ src/CMakeLists.txt | 2 ++ src/config.h.in | 1 + src/libtomahawk/CMakeLists.txt | 1 + src/sip/SipHandler.cpp | 11 ++++---- src/sip/jabber/CMakeLists.txt | 6 ++-- src/sip/jreen/CMakeLists.txt | 6 ++-- src/sip/twitter/CMakeLists.txt | 6 ++-- src/sip/zeroconf/CMakeLists.txt | 6 ++-- src/tomahawkapp.cpp | 41 ++-------------------------- thirdparty/libportfwd/CMakeLists.txt | 2 +- thirdparty/rtaudio/CMakeLists.txt | 2 ++ 13 files changed, 59 insertions(+), 58 deletions(-) create mode 100644 cmake_uninstall.cmake.in diff --git a/CMakeLists.txt b/CMakeLists.txt index f77ffc6d9..a9077b33c 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -85,6 +85,18 @@ MESSAGE("add checks for libmad, libvorbis and libflac. Make sure they are instal MESSAGE("") MESSAGE("-----------------------------------------------------------------------------") +SET( INSTALL_PREFIX "${CMAKE_INSTALL_PREFIX}" ) + +# make uninstall support +CONFIGURE_FILE( + "${CMAKE_CURRENT_SOURCE_DIR}/cmake_uninstall.cmake.in" + "${CMAKE_CURRENT_BINARY_DIR}/cmake_uninstall.cmake" + IMMEDIATE @ONLY) + +ADD_CUSTOM_TARGET(uninstall + "${CMAKE_COMMAND}" -P "${CMAKE_CURRENT_BINARY_DIR}/cmake_uninstall.cmake") + + IF( NOT APPLE ) # Make linking as strict on linux as it is on osx. Then we don't break linking on mac so often SET( CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -Wl,--no-undefined" ) diff --git a/cmake_uninstall.cmake.in b/cmake_uninstall.cmake.in new file mode 100644 index 000000000..df95fb9d8 --- /dev/null +++ b/cmake_uninstall.cmake.in @@ -0,0 +1,21 @@ +IF(NOT EXISTS "@CMAKE_CURRENT_BINARY_DIR@/install_manifest.txt") + MESSAGE(FATAL_ERROR "Cannot find install manifest: \"@CMAKE_CURRENT_BINARY_DIR@/install_manifest.txt\"") +ENDIF(NOT EXISTS "@CMAKE_CURRENT_BINARY_DIR@/install_manifest.txt") + +FILE(READ "@CMAKE_CURRENT_BINARY_DIR@/install_manifest.txt" files) +STRING(REGEX REPLACE "\n" ";" files "${files}") +FOREACH(file ${files}) + MESSAGE(STATUS "Uninstalling \"$ENV{DESTDIR}${file}\"") + IF(EXISTS "$ENV{DESTDIR}${file}") + EXEC_PROGRAM( + "@CMAKE_COMMAND@" ARGS "-E remove \"$ENV{DESTDIR}${file}\"" + OUTPUT_VARIABLE rm_out + RETURN_VALUE rm_retval + ) + IF(NOT "${rm_retval}" STREQUAL 0) + MESSAGE(FATAL_ERROR "Problem when removing \"$ENV{DESTDIR}${file}\"") + ENDIF(NOT "${rm_retval}" STREQUAL 0) + ELSE(EXISTS "$ENV{DESTDIR}${file}") + MESSAGE(STATUS "File \"$ENV{DESTDIR}${file}\" does not exist.") + ENDIF(EXISTS "$ENV{DESTDIR}${file}") +ENDFOREACH(file) diff --git a/src/CMakeLists.txt b/src/CMakeLists.txt index 8514f7f8d..b3acb3f05 100644 --- a/src/CMakeLists.txt +++ b/src/CMakeLists.txt @@ -232,6 +232,8 @@ IF( APPLE ) INSTALL(DIRECTORY "${SPARKLE}/Versions/Current/Resources" DESTINATION "${CMAKE_BINARY_DIR}/tomahawk.app/Contents/Frameworks/Sparkle.framework") ENDIF(HAVE_SPARKLE) +ELSEIF( APPLE ) + install( TARGETS tomahawk RUNTIME DESTINATION bin ) ENDIF( APPLE ) INCLUDE( "CPack.txt" ) diff --git a/src/config.h.in b/src/config.h.in index f8246a54d..aece46720 100644 --- a/src/config.h.in +++ b/src/config.h.in @@ -17,4 +17,5 @@ #cmakedefine LIBLASTFM_FOUND #cmakedefine GLOOX_FOUND +#cmakedefine INSTALL_PREFIX #endif // CONFIG_H_IN diff --git a/src/libtomahawk/CMakeLists.txt b/src/libtomahawk/CMakeLists.txt index 2104605f0..bdf495524 100644 --- a/src/libtomahawk/CMakeLists.txt +++ b/src/libtomahawk/CMakeLists.txt @@ -305,6 +305,7 @@ set( libUI ${libUI} ) include_directories( . ${CMAKE_CURRENT_BINARY_DIR} ${CMAKE_CURRENT_BINARY_DIR}/.. .. + ${CMAKE_CURRENT_SOURCE_DIR} ${QT_INCLUDE_DIR} ${LIBECHONEST_INCLUDE_DIR} ${LIBECHONEST_INCLUDE_DIR}/.. diff --git a/src/sip/SipHandler.cpp b/src/sip/SipHandler.cpp index b4f5297a8..c72076746 100644 --- a/src/sip/SipHandler.cpp +++ b/src/sip/SipHandler.cpp @@ -11,6 +11,7 @@ #include "sourcelist.h" #include "tomahawksettings.h" +#include "config.h" SipHandler::SipHandler( QObject* parent ) : QObject( parent ) @@ -60,10 +61,8 @@ SipHandler::findPlugins() } #endif - QDir libDir( appDir ); - libDir.cdUp(); - libDir.cd( "lib" ); - + QDir libDir( CMAKE_INSTALL_PREFIX "/lib" ); + QDir lib64Dir( appDir ); lib64Dir.cdUp(); lib64Dir.cd( "lib64" ); @@ -72,9 +71,9 @@ SipHandler::findPlugins() foreach ( const QDir& pluginDir, pluginDirs ) { qDebug() << "Checking directory for plugins:" << pluginDir; - foreach ( QString fileName, pluginDir.entryList( QDir::Files ) ) + foreach ( QString fileName, pluginDir.entryList( QStringList() << "*tomahawk_sip*.so" << "*tomahawk_sip*.dylib" << "*tomahawk_sip*.dll", QDir::Files ) ) { - if ( fileName.startsWith( "libsip_" ) ) + if ( fileName.startsWith( "libtomahawk_sip" ) ) { const QString path = pluginDir.absoluteFilePath( fileName ); if ( !paths.contains( path ) ) diff --git a/src/sip/jabber/CMakeLists.txt b/src/sip/jabber/CMakeLists.txt index d064a3aeb..1b487cb08 100644 --- a/src/sip/jabber/CMakeLists.txt +++ b/src/sip/jabber/CMakeLists.txt @@ -21,7 +21,7 @@ include_directories( . ${CMAKE_CURRENT_BINARY_DIR} .. ) qt4_wrap_cpp( jabberMoc ${jabberHeaders} ) -add_library( sip_jabber SHARED ${jabberSources} ${jabberMoc} ) +add_library( tomahawk_sipjabber SHARED ${jabberSources} ${jabberMoc} ) IF( WIN32 ) SET( OS_SPECIFIC_LINK_LIBRARIES @@ -32,7 +32,7 @@ SET( OS_SPECIFIC_LINK_LIBRARIES ) ENDIF( WIN32 ) -target_link_libraries( sip_jabber +target_link_libraries( tomahawk_sipjabber ${QT_LIBRARIES} ${GLOOX_LIBRARIES} ${OS_SPECIFIC_LINK_LIBRARIES} @@ -43,4 +43,4 @@ IF( APPLE ) # SET( CMAKE_SHARED_LINKER_FLAGS ${CMAKE_SHARED_LINKER_FLAGS} "-undefined dynamic_lookup" ) ENDIF( APPLE ) -install( TARGETS sip_jabber DESTINATION lib ) +install( TARGETS tomahawk_sipjabber LIBRARY DESTINATION lib ) diff --git a/src/sip/jreen/CMakeLists.txt b/src/sip/jreen/CMakeLists.txt index 177bb6025..3432152e4 100644 --- a/src/sip/jreen/CMakeLists.txt +++ b/src/sip/jreen/CMakeLists.txt @@ -22,7 +22,7 @@ include_directories( . ${CMAKE_CURRENT_BINARY_DIR} .. ) qt4_wrap_cpp( jabberMoc ${jabberHeaders} ) -add_library( sip_jreen SHARED ${jabberSources} ${jabberMoc} ) +add_library( tomahawk_sipjreen SHARED ${jabberSources} ${jabberMoc} ) IF( WIN32 ) SET( OS_SPECIFIC_LINK_LIBRARIES @@ -33,7 +33,7 @@ SET( OS_SPECIFIC_LINK_LIBRARIES ) ENDIF( WIN32 ) -target_link_libraries( sip_jreen +target_link_libraries( tomahawk_sipjreen ${QT_LIBRARIES} ${LIBJREEN_LIBRARY} ${OS_SPECIFIC_LINK_LIBRARIES} @@ -43,4 +43,4 @@ IF( APPLE ) # SET( CMAKE_SHARED_LINKER_FLAGS ${CMAKE_SHARED_LINKER_FLAGS} "-undefined dynamic_lookup" ) ENDIF( APPLE ) -install( TARGETS sip_jreen DESTINATION lib ) +install( TARGETS tomahawk_sipjreen LIBRARY DESTINATION lib ) diff --git a/src/sip/twitter/CMakeLists.txt b/src/sip/twitter/CMakeLists.txt index f3bc32bd5..82109f0db 100644 --- a/src/sip/twitter/CMakeLists.txt +++ b/src/sip/twitter/CMakeLists.txt @@ -28,7 +28,7 @@ include_directories( . ${CMAKE_CURRENT_BINARY_DIR} .. qt4_wrap_cpp( twitterMoc ${twitterHeaders} ) qt4_wrap_ui( twitterUI_H ${twitterUI} ) -add_library( sip_twitter SHARED ${twitterUI_H} ${twitterSources} ${twitterMoc} ) +add_library( tomahawk_siptwitter SHARED ${twitterUI_H} ${twitterSources} ${twitterMoc} ) IF( WIN32 ) SET( OS_SPECIFIC_LINK_LIBRARIES @@ -40,7 +40,7 @@ SET( OS_SPECIFIC_LINK_LIBRARIES ) ENDIF( WIN32 ) -target_link_libraries( sip_twitter +target_link_libraries( tomahawk_siptwitter ${QT_LIBRARIES} ${OS_SPECIFIC_LINK_LIBRARIES} tomahawklib @@ -50,4 +50,4 @@ IF( APPLE ) SET( CMAKE_SHARED_LINKER_FLAGS ${CMAKE_SHARED_LINKER_FLAGS} "-undefined dynamic_lookup" ) ENDIF( APPLE ) -install( TARGETS sip_twitter DESTINATION lib ) +install( TARGETS tomahawk_siptwitter LIBRARY DESTINATION lib ) diff --git a/src/sip/zeroconf/CMakeLists.txt b/src/sip/zeroconf/CMakeLists.txt index e203901cf..e8b7814ad 100644 --- a/src/sip/zeroconf/CMakeLists.txt +++ b/src/sip/zeroconf/CMakeLists.txt @@ -20,7 +20,7 @@ include_directories( . ${CMAKE_CURRENT_BINARY_DIR} .. ) qt4_wrap_cpp( zeroconfMoc ${zeroconfHeaders} ) -add_library( sip_zeroconf SHARED ${zeroconfSources} ${zeroconfMoc} ) +add_library( tomahawk_sipzeroconf SHARED ${zeroconfSources} ${zeroconfMoc} ) IF( WIN32 ) SET( OS_SPECIFIC_LINK_LIBRARIES @@ -31,7 +31,7 @@ SET( OS_SPECIFIC_LINK_LIBRARIES ) ENDIF( WIN32 ) -target_link_libraries( sip_zeroconf +target_link_libraries( tomahawk_sipzeroconf ${QT_LIBRARIES} ${OS_SPECIFIC_LINK_LIBRARIES} tomahawklib @@ -41,4 +41,4 @@ IF( APPLE ) # SET( CMAKE_SHARED_LINKER_FLAGS ${CMAKE_SHARED_LINKER_FLAGS} "-undefined dynamic_lookup" ) ENDIF( APPLE ) -install( TARGETS sip_zeroconf DESTINATION lib ) +install( TARGETS tomahawk_sipzeroconf LIBRARY DESTINATION lib ) diff --git a/src/tomahawkapp.cpp b/src/tomahawkapp.cpp index 4aaafc324..bedf2c0fc 100644 --- a/src/tomahawkapp.cpp +++ b/src/tomahawkapp.cpp @@ -34,6 +34,8 @@ #include "audio/audioengine.h" #include "utils/xspfloader.h" +#include "config.h" + #ifndef TOMAHAWK_HEADLESS #include "tomahawkwindow.h" #include "settingsdialog.h" @@ -253,7 +255,6 @@ TomahawkApp::TomahawkApp( int& argc, char *argv[] ) setupPipeline(); qDebug() << "Init Servent."; startServent(); - //loadPlugins(); if( arguments().contains( "--http" ) || TomahawkSettings::instance()->value( "network/http", true ).toBool() ) { @@ -454,44 +455,6 @@ TomahawkApp::startServent() } } - -void -TomahawkApp::loadPlugins() -{ - // look in same dir as executable for plugins - QDir dir( TomahawkApp::instance()->applicationDirPath() ); - QStringList filters; - filters << "*.so" << "*.dll" << "*.dylib"; - - QStringList files = dir.entryList( filters ); - foreach( const QString& filename, files ) - { - qDebug() << "Attempting to load" << QString( "%1/%2" ).arg( dir.absolutePath() ).arg( filename ); - - QPluginLoader loader( dir.absoluteFilePath( filename ) ); - if ( QObject* inst = loader.instance() ) - { - TomahawkPlugin* pluginst = qobject_cast(inst); - if ( !pluginst ) - continue; - - PluginAPI* api = new PluginAPI( Pipeline::instance() ); - TomahawkPlugin* plugin = pluginst->factory( api ); - qDebug() << "Loaded Plugin:" << plugin->name(); - qDebug() << plugin->description(); - m_plugins.append( plugin ); - - // plugins responsibility to register itself as a resolver/collection - // all we need to do is create an instance of it. - } - else - { - qDebug() << "PluginLoader failed to create instance:" << filename << " Err:" << loader.errorString(); - } - } -} - - void TomahawkApp::setupSIP() { diff --git a/thirdparty/libportfwd/CMakeLists.txt b/thirdparty/libportfwd/CMakeLists.txt index ad0799f9f..ac1d881ce 100644 --- a/thirdparty/libportfwd/CMakeLists.txt +++ b/thirdparty/libportfwd/CMakeLists.txt @@ -46,6 +46,6 @@ ENDIF() # ) #TARGET_LINK_LIBRARIES(portfwd-demo portfwd) -INSTALL(TARGETS portfwd ARCHIVE DESTINATION lib) +# INSTALL(TARGETS portfwd ARCHIVE DESTINATION lib) #INSTALL(TARGETS portfwd-demo RUNTIME DESTINATION bin) #INSTALL(DIRECTORY include/portfwd DESTINATION include PATTERN "*~" EXCLUDE) diff --git a/thirdparty/rtaudio/CMakeLists.txt b/thirdparty/rtaudio/CMakeLists.txt index 73bfe84d3..4791afbc9 100644 --- a/thirdparty/rtaudio/CMakeLists.txt +++ b/thirdparty/rtaudio/CMakeLists.txt @@ -38,4 +38,6 @@ target_link_libraries( rtaudio ${AUDIO_LIBS} ) +IF(WIN32) INSTALL(TARGETS rtaudio ARCHIVE DESTINATION lib) +ENDIF() \ No newline at end of file From 5d9f1eea90fb20a5f0b88808d8e77ec0712c8b8c Mon Sep 17 00:00:00 2001 From: Leo Franchi Date: Fri, 18 Mar 2011 14:49:23 -0400 Subject: [PATCH 013/329] don't need this, oops --- src/config.h.in | 1 - 1 file changed, 1 deletion(-) diff --git a/src/config.h.in b/src/config.h.in index aece46720..f8246a54d 100644 --- a/src/config.h.in +++ b/src/config.h.in @@ -17,5 +17,4 @@ #cmakedefine LIBLASTFM_FOUND #cmakedefine GLOOX_FOUND -#cmakedefine INSTALL_PREFIX #endif // CONFIG_H_IN From a18c0eed7a698bb1b1b8182af99a97d0505a2cee Mon Sep 17 00:00:00 2001 From: Christian Muehlhaeuser Date: Fri, 18 Mar 2011 21:33:20 +0100 Subject: [PATCH 014/329] * Fixed GPL headers. --- src/audiocontrols.cpp | 18 ++++++++++ src/audiocontrols.h | 18 ++++++++++ src/headlesscheck.h | 18 ++++++++++ src/infosystem/infoplugins/echonestplugin.cpp | 18 ++++++++++ src/infosystem/infoplugins/echonestplugin.h | 18 ++++++++++ .../infoplugins/musixmatchplugin.cpp | 18 ++++++++++ src/infosystem/infoplugins/musixmatchplugin.h | 18 ++++++++++ src/infosystem/infosystem.cpp | 18 ++++++++++ src/junk/remoteioconnection.cpp | 18 ++++++++++ src/junk/remoteioconnection.h | 18 ++++++++++ src/junk/remoteiodevice.cpp | 18 ++++++++++ src/junk/remoteiodevice.h | 18 ++++++++++ src/libtomahawk/album.cpp | 18 ++++++++++ src/libtomahawk/album.h | 18 ++++++++++ src/libtomahawk/artist.cpp | 18 ++++++++++ src/libtomahawk/artist.h | 18 ++++++++++ src/libtomahawk/audio/audioengine.cpp | 18 ++++++++++ src/libtomahawk/audio/audioengine.h | 18 ++++++++++ src/libtomahawk/audio/flactranscode.cpp | 18 ++++++++++ src/libtomahawk/audio/flactranscode.h | 18 ++++++++++ src/libtomahawk/audio/madtranscode.cpp | 18 ++++++++++ src/libtomahawk/audio/madtranscode.h | 18 ++++++++++ src/libtomahawk/audio/rtaudiooutput.cpp | 18 ++++++++++ src/libtomahawk/audio/rtaudiooutput.h | 18 ++++++++++ src/libtomahawk/audio/transcodeinterface.h | 18 ++++++++++ src/libtomahawk/audio/vorbistranscode.cpp | 18 ++++++++++ src/libtomahawk/audio/vorbistranscode.h | 18 ++++++++++ src/libtomahawk/collection.cpp | 18 ++++++++++ src/libtomahawk/collection.h | 18 ++++++++++ src/libtomahawk/database/database.cpp | 18 ++++++++++ src/libtomahawk/database/database.h | 18 ++++++++++ .../database/databasecollection.cpp | 18 ++++++++++ src/libtomahawk/database/databasecollection.h | 18 ++++++++++ src/libtomahawk/database/databasecommand.cpp | 18 ++++++++++ src/libtomahawk/database/databasecommand.h | 18 ++++++++++ .../databasecommand_addclientauth.cpp | 32 +++++++++--------- .../database/databasecommand_addclientauth.h | 32 +++++++++--------- .../database/databasecommand_addfiles.cpp | 18 ++++++++++ .../database/databasecommand_addfiles.h | 18 ++++++++++ .../database/databasecommand_addsource.cpp | 18 ++++++++++ .../database/databasecommand_addsource.h | 18 ++++++++++ .../database/databasecommand_allalbums.cpp | 18 ++++++++++ .../database/databasecommand_allalbums.h | 18 ++++++++++ .../database/databasecommand_alltracks.cpp | 18 ++++++++++ .../database/databasecommand_alltracks.h | 18 ++++++++++ .../databasecommand_clientauthvalid.cpp | 32 +++++++++--------- .../databasecommand_clientauthvalid.h | 32 +++++++++--------- .../databasecommand_collectionstats.cpp | 18 ++++++++++ .../databasecommand_collectionstats.h | 18 ++++++++++ .../databasecommand_createdynamicplaylist.cpp | 18 ++++++++++ .../databasecommand_createdynamicplaylist.h | 18 ++++++++++ .../databasecommand_createplaylist.cpp | 18 ++++++++++ .../database/databasecommand_createplaylist.h | 18 ++++++++++ .../databasecommand_deletedynamicplaylist.cpp | 33 ++++++++++--------- .../databasecommand_deletedynamicplaylist.h | 32 +++++++++--------- .../database/databasecommand_deletefiles.cpp | 18 ++++++++++ .../database/databasecommand_deletefiles.h | 18 ++++++++++ .../databasecommand_deleteplaylist.cpp | 18 ++++++++++ .../database/databasecommand_deleteplaylist.h | 18 ++++++++++ .../database/databasecommand_dirmtimes.cpp | 18 ++++++++++ .../database/databasecommand_dirmtimes.h | 18 ++++++++++ .../databasecommand_importplaylist.cpp | 18 ++++++++++ .../database/databasecommand_importplaylist.h | 18 ++++++++++ ...atabasecommand_loadalldynamicplaylists.cpp | 33 ++++++++++--------- .../databasecommand_loadalldynamicplaylists.h | 33 ++++++++++--------- .../databasecommand_loadallplaylists.cpp | 18 ++++++++++ .../databasecommand_loadallplaylists.h | 18 ++++++++++ .../databasecommand_loadallsources.cpp | 18 ++++++++++ .../database/databasecommand_loadallsources.h | 18 ++++++++++ .../databasecommand_loaddynamicplaylist.cpp | 18 ++++++++++ .../databasecommand_loaddynamicplaylist.h | 18 ++++++++++ .../database/databasecommand_loadfile.cpp | 18 ++++++++++ .../database/databasecommand_loadfile.h | 18 ++++++++++ .../database/databasecommand_loadops.cpp | 18 ++++++++++ .../database/databasecommand_loadops.h | 18 ++++++++++ .../databasecommand_loadplaylistentries.cpp | 18 ++++++++++ .../databasecommand_loadplaylistentries.h | 18 ++++++++++ .../database/databasecommand_logplayback.cpp | 18 ++++++++++ .../database/databasecommand_logplayback.h | 18 ++++++++++ .../databasecommand_modifyplaylist.cpp | 18 ++++++++++ .../database/databasecommand_modifyplaylist.h | 18 ++++++++++ .../databasecommand_playbackhistory.cpp | 18 ++++++++++ .../databasecommand_playbackhistory.h | 18 ++++++++++ .../databasecommand_renameplaylist.cpp | 18 ++++++++++ .../database/databasecommand_renameplaylist.h | 18 ++++++++++ .../database/databasecommand_resolve.cpp | 18 ++++++++++ .../database/databasecommand_resolve.h | 18 ++++++++++ ...basecommand_setdynamicplaylistrevision.cpp | 18 ++++++++++ ...tabasecommand_setdynamicplaylistrevision.h | 18 ++++++++++ .../databasecommand_setplaylistrevision.cpp | 18 ++++++++++ .../databasecommand_setplaylistrevision.h | 18 ++++++++++ .../databasecommand_sourceoffline.cpp | 18 ++++++++++ .../database/databasecommand_sourceoffline.h | 18 ++++++++++ .../databasecommand_updatesearchindex.cpp | 18 ++++++++++ .../databasecommand_updatesearchindex.h | 18 ++++++++++ .../database/databasecommandloggable.cpp | 18 ++++++++++ .../database/databasecommandloggable.h | 18 ++++++++++ src/libtomahawk/database/databaseimpl.cpp | 18 ++++++++++ src/libtomahawk/database/databaseimpl.h | 18 ++++++++++ src/libtomahawk/database/databaseresolver.cpp | 18 ++++++++++ src/libtomahawk/database/databaseresolver.h | 18 ++++++++++ src/libtomahawk/database/databaseworker.cpp | 18 ++++++++++ src/libtomahawk/database/databaseworker.h | 18 ++++++++++ src/libtomahawk/database/fuzzyindex.cpp | 18 ++++++++++ src/libtomahawk/database/fuzzyindex.h | 18 ++++++++++ src/libtomahawk/database/op.h | 18 ++++++++++ src/libtomahawk/database/tomahawksqlquery.h | 18 ++++++++++ src/libtomahawk/dllmacro.h | 18 ++++++++++ src/libtomahawk/functimeout.h | 18 ++++++++++ src/libtomahawk/network/bufferiodevice.cpp | 18 ++++++++++ src/libtomahawk/network/bufferiodevice.h | 18 ++++++++++ src/libtomahawk/network/connection.cpp | 18 ++++++++++ src/libtomahawk/network/connection.h | 18 ++++++++++ src/libtomahawk/network/controlconnection.cpp | 18 ++++++++++ src/libtomahawk/network/controlconnection.h | 18 ++++++++++ src/libtomahawk/network/dbsyncconnection.cpp | 18 ++++++++++ src/libtomahawk/network/dbsyncconnection.h | 18 ++++++++++ .../network/filetransferconnection.cpp | 18 ++++++++++ .../network/filetransferconnection.h | 17 ++++++++++ src/libtomahawk/network/msg.h | 18 ++++++++++ src/libtomahawk/network/msgprocessor.cpp | 18 ++++++++++ src/libtomahawk/network/msgprocessor.h | 18 ++++++++++ src/libtomahawk/network/portfwdthread.cpp | 18 ++++++++++ src/libtomahawk/network/portfwdthread.h | 18 ++++++++++ src/libtomahawk/network/remotecollection.cpp | 18 ++++++++++ src/libtomahawk/network/remotecollection.h | 18 ++++++++++ src/libtomahawk/network/servent.cpp | 18 ++++++++++ src/libtomahawk/network/servent.h | 18 ++++++++++ src/libtomahawk/pipeline.cpp | 18 ++++++++++ src/libtomahawk/pipeline.h | 18 ++++++++++ src/libtomahawk/playlist.cpp | 18 ++++++++++ src/libtomahawk/playlist.h | 18 ++++++++++ src/libtomahawk/playlist/albumitem.cpp | 18 ++++++++++ src/libtomahawk/playlist/albumitem.h | 18 ++++++++++ .../playlist/albumitemdelegate.cpp | 18 ++++++++++ src/libtomahawk/playlist/albumitemdelegate.h | 18 ++++++++++ src/libtomahawk/playlist/albummodel.cpp | 18 ++++++++++ src/libtomahawk/playlist/albummodel.h | 18 ++++++++++ src/libtomahawk/playlist/albumproxymodel.cpp | 18 ++++++++++ src/libtomahawk/playlist/albumproxymodel.h | 18 ++++++++++ src/libtomahawk/playlist/albumview.cpp | 18 ++++++++++ src/libtomahawk/playlist/albumview.h | 18 ++++++++++ .../playlist/collectionflatmodel.cpp | 18 ++++++++++ .../playlist/collectionflatmodel.h | 18 ++++++++++ src/libtomahawk/playlist/collectionmodel.cpp | 18 ++++++++++ src/libtomahawk/playlist/collectionmodel.h | 18 ++++++++++ .../playlist/collectionproxymodel.cpp | 18 ++++++++++ .../playlist/collectionproxymodel.h | 18 ++++++++++ src/libtomahawk/playlist/collectionview.cpp | 18 ++++++++++ src/libtomahawk/playlist/collectionview.h | 18 ++++++++++ .../playlist/dynamic/DynamicControl.cpp | 32 +++++++++--------- .../playlist/dynamic/DynamicControl.h | 32 +++++++++--------- .../playlist/dynamic/DynamicModel.cpp | 32 +++++++++--------- .../playlist/dynamic/DynamicModel.h | 32 +++++++++--------- .../playlist/dynamic/DynamicPlaylist.cpp | 32 +++++++++--------- .../playlist/dynamic/DynamicPlaylist.h | 32 +++++++++--------- .../playlist/dynamic/DynamicView.cpp | 32 +++++++++--------- .../playlist/dynamic/DynamicView.h | 32 +++++++++--------- .../playlist/dynamic/GeneratorFactory.cpp | 18 ++++++++++ .../playlist/dynamic/GeneratorFactory.h | 18 ++++++++++ .../playlist/dynamic/GeneratorInterface.cpp | 32 +++++++++--------- .../playlist/dynamic/GeneratorInterface.h | 32 +++++++++--------- .../dynamic/echonest/EchonestControl.cpp | 32 +++++++++--------- .../dynamic/echonest/EchonestControl.h | 32 +++++++++--------- .../dynamic/echonest/EchonestGenerator.cpp | 32 +++++++++--------- .../dynamic/echonest/EchonestGenerator.h | 32 +++++++++--------- .../dynamic/echonest/EchonestSteerer.cpp | 32 +++++++++--------- .../dynamic/echonest/EchonestSteerer.h | 32 +++++++++--------- .../dynamic/widgets/CollapsibleControls.cpp | 32 +++++++++--------- .../dynamic/widgets/CollapsibleControls.h | 32 +++++++++--------- .../dynamic/widgets/DynamicControlList.cpp | 32 +++++++++--------- .../dynamic/widgets/DynamicControlList.h | 32 +++++++++--------- .../dynamic/widgets/DynamicControlWrapper.cpp | 32 +++++++++--------- .../dynamic/widgets/DynamicControlWrapper.h | 32 +++++++++--------- .../dynamic/widgets/DynamicSetupWidget.cpp | 32 +++++++++--------- .../dynamic/widgets/DynamicSetupWidget.h | 32 +++++++++--------- .../dynamic/widgets/DynamicWidget.cpp | 32 +++++++++--------- .../playlist/dynamic/widgets/DynamicWidget.h | 32 +++++++++--------- .../dynamic/widgets/LoadingSpinner.cpp | 32 +++++++++--------- .../playlist/dynamic/widgets/LoadingSpinner.h | 32 +++++++++--------- .../dynamic/widgets/MiscControlWidgets.cpp | 32 +++++++++--------- .../dynamic/widgets/MiscControlWidgets.h | 32 +++++++++--------- .../dynamic/widgets/ReadOrWriteWidget.cpp | 32 +++++++++--------- .../dynamic/widgets/ReadOrWriteWidget.h | 32 +++++++++--------- src/libtomahawk/playlist/infobar/infobar.cpp | 18 ++++++++++ src/libtomahawk/playlist/infobar/infobar.h | 18 ++++++++++ .../playlist/playlistitemdelegate.cpp | 18 ++++++++++ .../playlist/playlistitemdelegate.h | 18 ++++++++++ src/libtomahawk/playlist/playlistmanager.cpp | 18 ++++++++++ src/libtomahawk/playlist/playlistmanager.h | 18 ++++++++++ src/libtomahawk/playlist/playlistmodel.cpp | 18 ++++++++++ src/libtomahawk/playlist/playlistmodel.h | 18 ++++++++++ .../playlist/playlistproxymodel.cpp | 18 ++++++++++ src/libtomahawk/playlist/playlistproxymodel.h | 18 ++++++++++ src/libtomahawk/playlist/playlistview.cpp | 18 ++++++++++ src/libtomahawk/playlist/playlistview.h | 18 ++++++++++ src/libtomahawk/playlist/plitem.cpp | 18 ++++++++++ src/libtomahawk/playlist/plitem.h | 18 ++++++++++ src/libtomahawk/playlist/queueproxymodel.cpp | 18 ++++++++++ src/libtomahawk/playlist/queueproxymodel.h | 18 ++++++++++ src/libtomahawk/playlist/queueview.cpp | 18 ++++++++++ src/libtomahawk/playlist/queueview.h | 18 ++++++++++ src/libtomahawk/playlist/topbar/topbar.cpp | 18 ++++++++++ src/libtomahawk/playlist/topbar/topbar.h | 18 ++++++++++ src/libtomahawk/playlist/trackheader.cpp | 18 ++++++++++ src/libtomahawk/playlist/trackheader.h | 18 ++++++++++ src/libtomahawk/playlist/trackmodel.cpp | 18 ++++++++++ src/libtomahawk/playlist/trackmodel.h | 18 ++++++++++ src/libtomahawk/playlist/trackproxymodel.cpp | 18 ++++++++++ src/libtomahawk/playlist/trackproxymodel.h | 18 ++++++++++ src/libtomahawk/playlist/trackview.cpp | 18 ++++++++++ src/libtomahawk/playlist/trackview.h | 18 ++++++++++ src/libtomahawk/playlistinterface.h | 18 ++++++++++ src/libtomahawk/pluginapi.cpp | 18 ++++++++++ src/libtomahawk/pluginapi.h | 18 ++++++++++ src/libtomahawk/query.cpp | 18 ++++++++++ src/libtomahawk/query.h | 18 ++++++++++ src/libtomahawk/resolver.h | 18 ++++++++++ src/libtomahawk/result.cpp | 18 ++++++++++ src/libtomahawk/result.h | 18 ++++++++++ src/libtomahawk/sip/SipPlugin.cpp | 20 +++++++++++ src/libtomahawk/sip/SipPlugin.h | 18 ++++++++++ src/libtomahawk/source.cpp | 18 ++++++++++ src/libtomahawk/source.h | 18 ++++++++++ src/libtomahawk/sourcelist.cpp | 18 ++++++++++ src/libtomahawk/sourcelist.h | 18 ++++++++++ src/libtomahawk/tomahawksettings.cpp | 18 ++++++++++ src/libtomahawk/tomahawksettings.h | 18 ++++++++++ src/libtomahawk/track.h | 18 ++++++++++ src/libtomahawk/typedefs.h | 18 ++++++++++ src/libtomahawk/utils/animatedcounterlabel.h | 18 ++++++++++ src/libtomahawk/utils/animatedsplitter.cpp | 18 ++++++++++ src/libtomahawk/utils/animatedsplitter.h | 18 ++++++++++ src/libtomahawk/utils/elidedlabel.cpp | 18 ++++++++++ src/libtomahawk/utils/elidedlabel.h | 18 ++++++++++ src/libtomahawk/utils/imagebutton.cpp | 18 ++++++++++ src/libtomahawk/utils/imagebutton.h | 18 ++++++++++ src/libtomahawk/utils/progresstreeview.cpp | 18 ++++++++++ src/libtomahawk/utils/progresstreeview.h | 18 ++++++++++ src/libtomahawk/utils/proxystyle.cpp | 18 ++++++++++ src/libtomahawk/utils/proxystyle.h | 18 ++++++++++ src/libtomahawk/utils/querylabel.cpp | 18 ++++++++++ src/libtomahawk/utils/querylabel.h | 18 ++++++++++ src/libtomahawk/utils/tomahawkutils.cpp | 18 ++++++++++ src/libtomahawk/utils/tomahawkutils.h | 18 ++++++++++ src/libtomahawk/utils/widgetdragfilter.cpp | 18 ++++++++++ src/libtomahawk/utils/widgetdragfilter.h | 18 ++++++++++ src/libtomahawk/utils/xspfloader.cpp | 18 ++++++++++ src/libtomahawk/utils/xspfloader.h | 18 ++++++++++ src/libtomahawk/viewpage.cpp | 18 ++++++++++ src/libtomahawk/viewpage.h | 18 ++++++++++ .../widgets/infowidgets/sourceinfowidget.cpp | 18 ++++++++++ .../widgets/infowidgets/sourceinfowidget.h | 18 ++++++++++ src/libtomahawk/widgets/newplaylistwidget.cpp | 18 ++++++++++ src/libtomahawk/widgets/newplaylistwidget.h | 18 ++++++++++ src/libtomahawk/widgets/overlaywidget.cpp | 18 ++++++++++ src/libtomahawk/widgets/overlaywidget.h | 18 ++++++++++ src/libtomahawk/widgets/welcomewidget.cpp | 18 ++++++++++ src/libtomahawk/widgets/welcomewidget.h | 18 ++++++++++ src/mac/macshortcuthandler.cpp | 18 ++++++++++ src/mac/macshortcuthandler.h | 18 ++++++++++ src/mac/tomahawkapp_mac.h | 18 ++++++++++ src/mac/tomahawkapp_mac.mm | 18 ++++++++++ src/main.cpp | 18 ++++++++++ src/musicscanner.cpp | 18 ++++++++++ src/musicscanner.h | 18 ++++++++++ src/plugins/fake/fakecollection.cpp | 18 ++++++++++ src/plugins/fake/fakecollection.h | 18 ++++++++++ src/plugins/fake/fakeplugin.cpp | 18 ++++++++++ src/plugins/fake/fakeplugin.h | 18 ++++++++++ src/resolvers/qtscriptresolver.cpp | 18 ++++++++++ src/resolvers/qtscriptresolver.h | 18 ++++++++++ src/resolvers/scriptresolver.cpp | 18 ++++++++++ src/resolvers/scriptresolver.h | 18 ++++++++++ src/scanmanager.cpp | 18 ++++++++++ src/scanmanager.h | 18 ++++++++++ src/scrobbler.cpp | 18 ++++++++++ src/scrobbler.h | 17 ++++++++++ src/settingsdialog.cpp | 18 ++++++++++ src/settingsdialog.h | 18 ++++++++++ src/shortcuthandler.cpp | 18 ++++++++++ src/shortcuthandler.h | 18 ++++++++++ src/sip/SipHandler.cpp | 18 ++++++++++ src/sip/SipHandler.h | 18 ++++++++++ src/sip/jabber/jabber.cpp | 18 ++++++++++ src/sip/jabber/jabber.h | 18 ++++++++++ src/sip/jabber/jabber_p.cpp | 18 ++++++++++ src/sip/jabber/jabber_p.h | 18 ++++++++++ src/sip/jreen/jabber.cpp | 18 ++++++++++ src/sip/jreen/jabber.h | 18 ++++++++++ src/sip/jreen/jabber_p.cpp | 18 ++++++++++ src/sip/jreen/jabber_p.h | 18 ++++++++++ src/sip/sipdllmacro.h | 18 ++++++++++ src/sip/twitter/twitter.cpp | 18 ++++++++++ src/sip/twitter/twitter.h | 18 ++++++++++ src/sip/twitter/twitterconfigwidget.cpp | 18 ++++++++++ src/sip/twitter/twitterconfigwidget.h | 18 ++++++++++ src/sip/zeroconf/tomahawkzeroconf.h | 18 ++++++++++ src/sip/zeroconf/zeroconf.cpp | 18 ++++++++++ src/sip/zeroconf/zeroconf.h | 18 ++++++++++ src/sourcetree/sourcesmodel.cpp | 18 ++++++++++ src/sourcetree/sourcesmodel.h | 18 ++++++++++ src/sourcetree/sourcesproxymodel.cpp | 18 ++++++++++ src/sourcetree/sourcesproxymodel.h | 18 ++++++++++ src/sourcetree/sourcetreeitem.cpp | 18 ++++++++++ src/sourcetree/sourcetreeitem.h | 18 ++++++++++ src/sourcetree/sourcetreeitemwidget.cpp | 18 ++++++++++ src/sourcetree/sourcetreeitemwidget.h | 18 ++++++++++ src/sourcetree/sourcetreeview.cpp | 18 ++++++++++ src/sourcetree/sourcetreeview.h | 18 ++++++++++ src/tomahawkapp.cpp | 18 ++++++++++ src/tomahawktrayicon.cpp | 18 ++++++++++ src/tomahawktrayicon.h | 18 ++++++++++ src/tomahawkwindow.cpp | 18 ++++++++++ src/tomahawkwindow.h | 18 ++++++++++ src/transferview.cpp | 18 ++++++++++ src/transferview.h | 18 ++++++++++ src/web/api_v1.cpp | 18 ++++++++++ src/web/api_v1.h | 18 ++++++++++ src/xmppbot/xmppbot.cpp | 18 ++++++++++ src/xmppbot/xmppbot.h | 18 ++++++++++ .../libportfwd/include/portfwd/portfwd.h | 17 ++++++++++ thirdparty/libportfwd/src/main.cpp | 18 ++++++++++ thirdparty/libportfwd/src/portfwd.cpp | 17 ++++++++++ 324 files changed, 5793 insertions(+), 600 deletions(-) diff --git a/src/audiocontrols.cpp b/src/audiocontrols.cpp index 1587b06e6..f7b820e7d 100644 --- a/src/audiocontrols.cpp +++ b/src/audiocontrols.cpp @@ -1,3 +1,21 @@ +/* === This file is part of Tomahawk Player - === + * + * Copyright 2010-2011, Christian Muehlhaeuser + * + * Tomahawk is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * Tomahawk is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with Tomahawk. If not, see . + */ + #include "audiocontrols.h" #include "ui_audiocontrols.h" diff --git a/src/audiocontrols.h b/src/audiocontrols.h index eb26bf425..8de6f765c 100644 --- a/src/audiocontrols.h +++ b/src/audiocontrols.h @@ -1,3 +1,21 @@ +/* === This file is part of Tomahawk Player - === + * + * Copyright 2010-2011, Christian Muehlhaeuser + * + * Tomahawk is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * Tomahawk is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with Tomahawk. If not, see . + */ + #ifndef AUDIOCONTROLS_H #define AUDIOCONTROLS_H diff --git a/src/headlesscheck.h b/src/headlesscheck.h index fda2a07a9..189f1127e 100644 --- a/src/headlesscheck.h +++ b/src/headlesscheck.h @@ -1,3 +1,21 @@ +/* === This file is part of Tomahawk Player - === + * + * Copyright 2010-2011, Christian Muehlhaeuser + * + * Tomahawk is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * Tomahawk is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with Tomahawk. If not, see . + */ + #ifndef HEADLESSCHECK #define HEADLESSCHECK diff --git a/src/infosystem/infoplugins/echonestplugin.cpp b/src/infosystem/infoplugins/echonestplugin.cpp index cc9f08a9d..fab7c9bc4 100644 --- a/src/infosystem/infoplugins/echonestplugin.cpp +++ b/src/infosystem/infoplugins/echonestplugin.cpp @@ -1,3 +1,21 @@ +/* === This file is part of Tomahawk Player - === + * + * Copyright 2010-2011, Christian Muehlhaeuser + * + * Tomahawk is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * Tomahawk is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with Tomahawk. If not, see . + */ + #include "tomahawk/infosystem.h" #include "tomahawk/tomahawkapp.h" #include "echonestplugin.h" diff --git a/src/infosystem/infoplugins/echonestplugin.h b/src/infosystem/infoplugins/echonestplugin.h index 89d4a15ce..734ceecd2 100644 --- a/src/infosystem/infoplugins/echonestplugin.h +++ b/src/infosystem/infoplugins/echonestplugin.h @@ -1,3 +1,21 @@ +/* === This file is part of Tomahawk Player - === + * + * Copyright 2010-2011, Christian Muehlhaeuser + * + * Tomahawk is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * Tomahawk is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with Tomahawk. If not, see . + */ + #ifndef ECHONESTPLUGIN_H #define ECHONESTPLUGIN_H diff --git a/src/infosystem/infoplugins/musixmatchplugin.cpp b/src/infosystem/infoplugins/musixmatchplugin.cpp index 8cae2c6fe..08795c713 100644 --- a/src/infosystem/infoplugins/musixmatchplugin.cpp +++ b/src/infosystem/infoplugins/musixmatchplugin.cpp @@ -1,3 +1,21 @@ +/* === This file is part of Tomahawk Player - === + * + * Copyright 2010-2011, Christian Muehlhaeuser + * + * Tomahawk is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * Tomahawk is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with Tomahawk. If not, see . + */ + #include "tomahawk/infosystem.h" #include "tomahawk/tomahawkapp.h" #include "musixmatchplugin.h" diff --git a/src/infosystem/infoplugins/musixmatchplugin.h b/src/infosystem/infoplugins/musixmatchplugin.h index 63301fa87..284c81515 100644 --- a/src/infosystem/infoplugins/musixmatchplugin.h +++ b/src/infosystem/infoplugins/musixmatchplugin.h @@ -1,3 +1,21 @@ +/* === This file is part of Tomahawk Player - === + * + * Copyright 2010-2011, Christian Muehlhaeuser + * + * Tomahawk is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * Tomahawk is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with Tomahawk. If not, see . + */ + #ifndef MUSIXMATCHPLUGIN_H #define MUSIXMATCHPLUGIN_H #include "tomahawk/infosystem.h" diff --git a/src/infosystem/infosystem.cpp b/src/infosystem/infosystem.cpp index 66b107d5e..5df0a2a01 100644 --- a/src/infosystem/infosystem.cpp +++ b/src/infosystem/infosystem.cpp @@ -1,3 +1,21 @@ +/* === This file is part of Tomahawk Player - === + * + * Copyright 2010-2011, Christian Muehlhaeuser + * + * Tomahawk is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * Tomahawk is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with Tomahawk. If not, see . + */ + #include "tomahawk/infosystem.h" #include "infoplugins/echonestplugin.h" #include "infoplugins/musixmatchplugin.h" diff --git a/src/junk/remoteioconnection.cpp b/src/junk/remoteioconnection.cpp index 9bc7cc655..2c4fa30f5 100644 --- a/src/junk/remoteioconnection.cpp +++ b/src/junk/remoteioconnection.cpp @@ -1,3 +1,21 @@ +/* === This file is part of Tomahawk Player - === + * + * Copyright 2010-2011, Christian Muehlhaeuser + * + * Tomahawk is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * Tomahawk is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with Tomahawk. If not, see . + */ + #include "remoteioconnection.h" #include diff --git a/src/junk/remoteioconnection.h b/src/junk/remoteioconnection.h index 5874f8a4f..62ac4bd98 100644 --- a/src/junk/remoteioconnection.h +++ b/src/junk/remoteioconnection.h @@ -1,3 +1,21 @@ +/* === This file is part of Tomahawk Player - === + * + * Copyright 2010-2011, Christian Muehlhaeuser + * + * Tomahawk is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * Tomahawk is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with Tomahawk. If not, see . + */ + #ifndef REMOTEIOCONNECTION_H #define REMOTEIOCONNECTION_H diff --git a/src/junk/remoteiodevice.cpp b/src/junk/remoteiodevice.cpp index 92cefbfe9..6b8a63191 100644 --- a/src/junk/remoteiodevice.cpp +++ b/src/junk/remoteiodevice.cpp @@ -1,3 +1,21 @@ +/* === This file is part of Tomahawk Player - === + * + * Copyright 2010-2011, Christian Muehlhaeuser + * + * Tomahawk is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * Tomahawk is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with Tomahawk. If not, see . + */ + #include "remoteiodevice.h" RemoteIODevice::RemoteIODevice(RemoteIOConnection * c) diff --git a/src/junk/remoteiodevice.h b/src/junk/remoteiodevice.h index e8ee34fd4..277bb547f 100644 --- a/src/junk/remoteiodevice.h +++ b/src/junk/remoteiodevice.h @@ -1,3 +1,21 @@ +/* === This file is part of Tomahawk Player - === + * + * Copyright 2010-2011, Christian Muehlhaeuser + * + * Tomahawk is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * Tomahawk is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with Tomahawk. If not, see . + */ + #ifndef REMOTEIODEVICE_H #define REMOTEIODEVICE_H #include diff --git a/src/libtomahawk/album.cpp b/src/libtomahawk/album.cpp index c4c382b42..5fede95c5 100644 --- a/src/libtomahawk/album.cpp +++ b/src/libtomahawk/album.cpp @@ -1,3 +1,21 @@ +/* === This file is part of Tomahawk Player - === + * + * Copyright 2010-2011, Christian Muehlhaeuser + * + * Tomahawk is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * Tomahawk is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with Tomahawk. If not, see . + */ + #include "album.h" #include diff --git a/src/libtomahawk/album.h b/src/libtomahawk/album.h index 35f4b48e2..df8009b27 100644 --- a/src/libtomahawk/album.h +++ b/src/libtomahawk/album.h @@ -1,3 +1,21 @@ +/* === This file is part of Tomahawk Player - === + * + * Copyright 2010-2011, Christian Muehlhaeuser + * + * Tomahawk is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * Tomahawk is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with Tomahawk. If not, see . + */ + #ifndef TOMAHAWKALBUM_H #define TOMAHAWKALBUM_H diff --git a/src/libtomahawk/artist.cpp b/src/libtomahawk/artist.cpp index ec84884e6..bc130e55f 100644 --- a/src/libtomahawk/artist.cpp +++ b/src/libtomahawk/artist.cpp @@ -1,3 +1,21 @@ +/* === This file is part of Tomahawk Player - === + * + * Copyright 2010-2011, Christian Muehlhaeuser + * + * Tomahawk is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * Tomahawk is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with Tomahawk. If not, see . + */ + #include "artist.h" #include diff --git a/src/libtomahawk/artist.h b/src/libtomahawk/artist.h index 455b09918..d7f5ac7ef 100644 --- a/src/libtomahawk/artist.h +++ b/src/libtomahawk/artist.h @@ -1,3 +1,21 @@ +/* === This file is part of Tomahawk Player - === + * + * Copyright 2010-2011, Christian Muehlhaeuser + * + * Tomahawk is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * Tomahawk is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with Tomahawk. If not, see . + */ + #ifndef TOMAHAWKARTIST_H #define TOMAHAWKARTIST_H diff --git a/src/libtomahawk/audio/audioengine.cpp b/src/libtomahawk/audio/audioengine.cpp index 2d6bc1465..07c5c3cc1 100644 --- a/src/libtomahawk/audio/audioengine.cpp +++ b/src/libtomahawk/audio/audioengine.cpp @@ -1,3 +1,21 @@ +/* === This file is part of Tomahawk Player - === + * + * Copyright 2010-2011, Christian Muehlhaeuser + * + * Tomahawk is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * Tomahawk is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with Tomahawk. If not, see . + */ + #include "audioengine.h" #include diff --git a/src/libtomahawk/audio/audioengine.h b/src/libtomahawk/audio/audioengine.h index 6e1fc045b..810eb7e95 100644 --- a/src/libtomahawk/audio/audioengine.h +++ b/src/libtomahawk/audio/audioengine.h @@ -1,3 +1,21 @@ +/* === This file is part of Tomahawk Player - === + * + * Copyright 2010-2011, Christian Muehlhaeuser + * + * Tomahawk is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * Tomahawk is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with Tomahawk. If not, see . + */ + #ifndef AUDIOENGINE_H #define AUDIOENGINE_H diff --git a/src/libtomahawk/audio/flactranscode.cpp b/src/libtomahawk/audio/flactranscode.cpp index bb5e889f4..aee7299d1 100644 --- a/src/libtomahawk/audio/flactranscode.cpp +++ b/src/libtomahawk/audio/flactranscode.cpp @@ -1,3 +1,21 @@ +/* === This file is part of Tomahawk Player - === + * + * Copyright 2010-2011, Christian Muehlhaeuser + * + * Tomahawk is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * Tomahawk is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with Tomahawk. If not, see . + */ + #include "flactranscode.h" diff --git a/src/libtomahawk/audio/flactranscode.h b/src/libtomahawk/audio/flactranscode.h index 38711e07a..2491df3de 100644 --- a/src/libtomahawk/audio/flactranscode.h +++ b/src/libtomahawk/audio/flactranscode.h @@ -1,3 +1,21 @@ +/* === This file is part of Tomahawk Player - === + * + * Copyright 2010-2011, Christian Muehlhaeuser + * + * 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 . + */ + /*! \class FLACTranscode \brief Transcoding plugin for FLAC streams. */ diff --git a/src/libtomahawk/audio/madtranscode.cpp b/src/libtomahawk/audio/madtranscode.cpp index 2f4f79d81..cbd732a28 100644 --- a/src/libtomahawk/audio/madtranscode.cpp +++ b/src/libtomahawk/audio/madtranscode.cpp @@ -1,3 +1,21 @@ +/* === This file is part of Tomahawk Player - === + * + * Copyright 2010-2011, Christian Muehlhaeuser + * + * Tomahawk is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * Tomahawk is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with Tomahawk. If not, see . + */ + #include "madtranscode.h" #include diff --git a/src/libtomahawk/audio/madtranscode.h b/src/libtomahawk/audio/madtranscode.h index cef9aeb71..e90d40ef4 100644 --- a/src/libtomahawk/audio/madtranscode.h +++ b/src/libtomahawk/audio/madtranscode.h @@ -1,3 +1,21 @@ +/* === This file is part of Tomahawk Player - === + * + * Copyright 2010-2011, Christian Muehlhaeuser + * + * 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 . + */ + /*! \class MadTranscode \brief Transcoding plugin for MP3 streams, using libmad. */ diff --git a/src/libtomahawk/audio/rtaudiooutput.cpp b/src/libtomahawk/audio/rtaudiooutput.cpp index b750460a3..667c2613e 100644 --- a/src/libtomahawk/audio/rtaudiooutput.cpp +++ b/src/libtomahawk/audio/rtaudiooutput.cpp @@ -1,3 +1,21 @@ +/* === This file is part of Tomahawk Player - === + * + * Copyright 2010-2011, Christian Muehlhaeuser + * + * Tomahawk is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * Tomahawk is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with Tomahawk. If not, see . + */ + #include #include #include diff --git a/src/libtomahawk/audio/rtaudiooutput.h b/src/libtomahawk/audio/rtaudiooutput.h index 04b246e11..e13cec279 100644 --- a/src/libtomahawk/audio/rtaudiooutput.h +++ b/src/libtomahawk/audio/rtaudiooutput.h @@ -1,3 +1,21 @@ +/* === This file is part of Tomahawk Player - === + * + * Copyright 2010-2011, Christian Muehlhaeuser + * + * Tomahawk is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * Tomahawk is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with Tomahawk. If not, see . + */ + #ifndef RTAUDIOPLAYBACK_H #define RTAUDIOPLAYBACK_H diff --git a/src/libtomahawk/audio/transcodeinterface.h b/src/libtomahawk/audio/transcodeinterface.h index 3ff4dffd1..48ba434df 100644 --- a/src/libtomahawk/audio/transcodeinterface.h +++ b/src/libtomahawk/audio/transcodeinterface.h @@ -1,3 +1,21 @@ +/* === This file is part of Tomahawk Player - === + * + * Copyright 2010-2011, Christian Muehlhaeuser + * + * Tomahawk is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * Tomahawk is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with Tomahawk. If not, see . + */ + #ifndef TRANSCODEINTERFACE_H #define TRANSCODEINTERFACE_H diff --git a/src/libtomahawk/audio/vorbistranscode.cpp b/src/libtomahawk/audio/vorbistranscode.cpp index c53b13afb..826c0f272 100644 --- a/src/libtomahawk/audio/vorbistranscode.cpp +++ b/src/libtomahawk/audio/vorbistranscode.cpp @@ -1,3 +1,21 @@ +/* === This file is part of Tomahawk Player - === + * + * Copyright 2010-2011, Christian Muehlhaeuser + * + * Tomahawk is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * Tomahawk is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with Tomahawk. If not, see . + */ + #include "vorbistranscode.h" diff --git a/src/libtomahawk/audio/vorbistranscode.h b/src/libtomahawk/audio/vorbistranscode.h index c7a1dd024..107dfbad3 100644 --- a/src/libtomahawk/audio/vorbistranscode.h +++ b/src/libtomahawk/audio/vorbistranscode.h @@ -1,3 +1,21 @@ +/* === This file is part of Tomahawk Player - === + * + * Copyright 2010-2011, Christian Muehlhaeuser + * + * 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 . + */ + /*! \class VorbisTranscode \brief Transcoding plugin for OGG/Vorbis streams. */ diff --git a/src/libtomahawk/collection.cpp b/src/libtomahawk/collection.cpp index 85e7262e4..a89385fdf 100644 --- a/src/libtomahawk/collection.cpp +++ b/src/libtomahawk/collection.cpp @@ -1,3 +1,21 @@ +/* === This file is part of Tomahawk Player - === + * + * Copyright 2010-2011, Christian Muehlhaeuser + * + * Tomahawk is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * Tomahawk is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with Tomahawk. If not, see . + */ + #include "collection.h" #include diff --git a/src/libtomahawk/collection.h b/src/libtomahawk/collection.h index 76ab19897..3ed499f84 100644 --- a/src/libtomahawk/collection.h +++ b/src/libtomahawk/collection.h @@ -1,3 +1,21 @@ +/* === This file is part of Tomahawk Player - === + * + * Copyright 2010-2011, Christian Muehlhaeuser + * + * 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 . + */ + /* The collection - acts as container for someones music library load() -> async populate by calling addArtists etc, diff --git a/src/libtomahawk/database/database.cpp b/src/libtomahawk/database/database.cpp index 23ae975b4..46e61badb 100644 --- a/src/libtomahawk/database/database.cpp +++ b/src/libtomahawk/database/database.cpp @@ -1,3 +1,21 @@ +/* === This file is part of Tomahawk Player - === + * + * Copyright 2010-2011, Christian Muehlhaeuser + * + * Tomahawk is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * Tomahawk is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with Tomahawk. If not, see . + */ + #include "database.h" #define WORKER_THREADS 5 diff --git a/src/libtomahawk/database/database.h b/src/libtomahawk/database/database.h index 07e55be78..498f5239b 100644 --- a/src/libtomahawk/database/database.h +++ b/src/libtomahawk/database/database.h @@ -1,3 +1,21 @@ +/* === This file is part of Tomahawk Player - === + * + * Copyright 2010-2011, Christian Muehlhaeuser + * + * Tomahawk is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * Tomahawk is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with Tomahawk. If not, see . + */ + #ifndef DATABASE_H #define DATABASE_H diff --git a/src/libtomahawk/database/databasecollection.cpp b/src/libtomahawk/database/databasecollection.cpp index 6383098b1..c90da627c 100644 --- a/src/libtomahawk/database/databasecollection.cpp +++ b/src/libtomahawk/database/databasecollection.cpp @@ -1,3 +1,21 @@ +/* === This file is part of Tomahawk Player - === + * + * Copyright 2010-2011, Christian Muehlhaeuser + * + * Tomahawk is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * Tomahawk is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with Tomahawk. If not, see . + */ + #include "databasecollection.h" #include "database/database.h" diff --git a/src/libtomahawk/database/databasecollection.h b/src/libtomahawk/database/databasecollection.h index 5c2ce4832..74edcad77 100644 --- a/src/libtomahawk/database/databasecollection.h +++ b/src/libtomahawk/database/databasecollection.h @@ -1,3 +1,21 @@ +/* === This file is part of Tomahawk Player - === + * + * Copyright 2010-2011, Christian Muehlhaeuser + * + * Tomahawk is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * Tomahawk is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with Tomahawk. If not, see . + */ + #ifndef DATABASECOLLECTION_H #define DATABASECOLLECTION_H diff --git a/src/libtomahawk/database/databasecommand.cpp b/src/libtomahawk/database/databasecommand.cpp index 82e0c5cad..41824c87c 100644 --- a/src/libtomahawk/database/databasecommand.cpp +++ b/src/libtomahawk/database/databasecommand.cpp @@ -1,3 +1,21 @@ +/* === This file is part of Tomahawk Player - === + * + * Copyright 2010-2011, Christian Muehlhaeuser + * + * Tomahawk is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * Tomahawk is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with Tomahawk. If not, see . + */ + #include "databasecommand.h" #include diff --git a/src/libtomahawk/database/databasecommand.h b/src/libtomahawk/database/databasecommand.h index b8a59fc7f..f18440ca7 100644 --- a/src/libtomahawk/database/databasecommand.h +++ b/src/libtomahawk/database/databasecommand.h @@ -1,3 +1,21 @@ +/* === This file is part of Tomahawk Player - === + * + * Copyright 2010-2011, Christian Muehlhaeuser + * + * Tomahawk is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * Tomahawk is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with Tomahawk. If not, see . + */ + #ifndef DATABASECOMMAND_H #define DATABASECOMMAND_H diff --git a/src/libtomahawk/database/databasecommand_addclientauth.cpp b/src/libtomahawk/database/databasecommand_addclientauth.cpp index d8e705952..a1aacc48f 100644 --- a/src/libtomahawk/database/databasecommand_addclientauth.cpp +++ b/src/libtomahawk/database/databasecommand_addclientauth.cpp @@ -1,18 +1,20 @@ -/**************************************************************************************** - * Copyright (c) 2011 Leo Franchi * - * * - * 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, see . * - ****************************************************************************************/ +/* === This file is part of Tomahawk Player - === + * + * Copyright 2010-2011, Christian Muehlhaeuser + * + * Tomahawk is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * Tomahawk is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with Tomahawk. If not, see . + */ #include "databasecommand_addclientauth.h" diff --git a/src/libtomahawk/database/databasecommand_addclientauth.h b/src/libtomahawk/database/databasecommand_addclientauth.h index fccec5947..b562ef65a 100644 --- a/src/libtomahawk/database/databasecommand_addclientauth.h +++ b/src/libtomahawk/database/databasecommand_addclientauth.h @@ -1,18 +1,20 @@ -/**************************************************************************************** - * Copyright (c) 2011 Leo Franchi * - * * - * 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, see . * - ****************************************************************************************/ +/* === This file is part of Tomahawk Player - === + * + * Copyright 2010-2011, Christian Muehlhaeuser + * + * Tomahawk is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * Tomahawk is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with Tomahawk. If not, see . + */ #ifndef DATABASECOMMAND_ADDCLIENTAUTH_H #define DATABASECOMMAND_ADDCLIENTAUTH_H diff --git a/src/libtomahawk/database/databasecommand_addfiles.cpp b/src/libtomahawk/database/databasecommand_addfiles.cpp index 10c1ee429..64d96f079 100644 --- a/src/libtomahawk/database/databasecommand_addfiles.cpp +++ b/src/libtomahawk/database/databasecommand_addfiles.cpp @@ -1,3 +1,21 @@ +/* === This file is part of Tomahawk Player - === + * + * Copyright 2010-2011, Christian Muehlhaeuser + * + * Tomahawk is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * Tomahawk is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with Tomahawk. If not, see . + */ + #include "databasecommand_addfiles.h" #include diff --git a/src/libtomahawk/database/databasecommand_addfiles.h b/src/libtomahawk/database/databasecommand_addfiles.h index 8e8b5e11c..bb09d72b5 100644 --- a/src/libtomahawk/database/databasecommand_addfiles.h +++ b/src/libtomahawk/database/databasecommand_addfiles.h @@ -1,3 +1,21 @@ +/* === This file is part of Tomahawk Player - === + * + * Copyright 2010-2011, Christian Muehlhaeuser + * + * Tomahawk is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * Tomahawk is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with Tomahawk. If not, see . + */ + #ifndef DATABASECOMMAND_ADDFILES_H #define DATABASECOMMAND_ADDFILES_H diff --git a/src/libtomahawk/database/databasecommand_addsource.cpp b/src/libtomahawk/database/databasecommand_addsource.cpp index 765e715d3..5af922f5e 100644 --- a/src/libtomahawk/database/databasecommand_addsource.cpp +++ b/src/libtomahawk/database/databasecommand_addsource.cpp @@ -1,3 +1,21 @@ +/* === This file is part of Tomahawk Player - === + * + * Copyright 2010-2011, Christian Muehlhaeuser + * + * Tomahawk is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * Tomahawk is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with Tomahawk. If not, see . + */ + #include #include "databasecommand_addsource.h" #include "databaseimpl.h" diff --git a/src/libtomahawk/database/databasecommand_addsource.h b/src/libtomahawk/database/databasecommand_addsource.h index aa657aae8..375467ea1 100644 --- a/src/libtomahawk/database/databasecommand_addsource.h +++ b/src/libtomahawk/database/databasecommand_addsource.h @@ -1,3 +1,21 @@ +/* === This file is part of Tomahawk Player - === + * + * Copyright 2010-2011, Christian Muehlhaeuser + * + * Tomahawk is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * Tomahawk is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with Tomahawk. If not, see . + */ + #ifndef DATABASECOMMAND_ADDSOURCE_H #define DATABASECOMMAND_ADDSOURCE_H diff --git a/src/libtomahawk/database/databasecommand_allalbums.cpp b/src/libtomahawk/database/databasecommand_allalbums.cpp index 56aba5672..99af75aff 100644 --- a/src/libtomahawk/database/databasecommand_allalbums.cpp +++ b/src/libtomahawk/database/databasecommand_allalbums.cpp @@ -1,3 +1,21 @@ +/* === This file is part of Tomahawk Player - === + * + * Copyright 2010-2011, Christian Muehlhaeuser + * + * Tomahawk is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * Tomahawk is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with Tomahawk. If not, see . + */ + #include "databasecommand_allalbums.h" #include diff --git a/src/libtomahawk/database/databasecommand_allalbums.h b/src/libtomahawk/database/databasecommand_allalbums.h index 37b6d0db2..501e9ae5e 100644 --- a/src/libtomahawk/database/databasecommand_allalbums.h +++ b/src/libtomahawk/database/databasecommand_allalbums.h @@ -1,3 +1,21 @@ +/* === This file is part of Tomahawk Player - === + * + * Copyright 2010-2011, Christian Muehlhaeuser + * + * Tomahawk is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * Tomahawk is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with Tomahawk. If not, see . + */ + #ifndef DATABASECOMMAND_ALLALBUMS_H #define DATABASECOMMAND_ALLALBUMS_H diff --git a/src/libtomahawk/database/databasecommand_alltracks.cpp b/src/libtomahawk/database/databasecommand_alltracks.cpp index cb89505d3..5259ca64c 100644 --- a/src/libtomahawk/database/databasecommand_alltracks.cpp +++ b/src/libtomahawk/database/databasecommand_alltracks.cpp @@ -1,3 +1,21 @@ +/* === This file is part of Tomahawk Player - === + * + * Copyright 2010-2011, Christian Muehlhaeuser + * + * Tomahawk is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * Tomahawk is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with Tomahawk. If not, see . + */ + #include "databasecommand_alltracks.h" #include diff --git a/src/libtomahawk/database/databasecommand_alltracks.h b/src/libtomahawk/database/databasecommand_alltracks.h index 5eea1c55c..5b8981aed 100644 --- a/src/libtomahawk/database/databasecommand_alltracks.h +++ b/src/libtomahawk/database/databasecommand_alltracks.h @@ -1,3 +1,21 @@ +/* === This file is part of Tomahawk Player - === + * + * Copyright 2010-2011, Christian Muehlhaeuser + * + * Tomahawk is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * Tomahawk is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with Tomahawk. If not, see . + */ + #ifndef DATABASECOMMAND_ALLTRACKS_H #define DATABASECOMMAND_ALLTRACKS_H diff --git a/src/libtomahawk/database/databasecommand_clientauthvalid.cpp b/src/libtomahawk/database/databasecommand_clientauthvalid.cpp index e751748bf..be0e4d495 100644 --- a/src/libtomahawk/database/databasecommand_clientauthvalid.cpp +++ b/src/libtomahawk/database/databasecommand_clientauthvalid.cpp @@ -1,18 +1,20 @@ -/**************************************************************************************** - * Copyright (c) 2011 Leo Franchi * - * * - * 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, see . * - ****************************************************************************************/ +/* === This file is part of Tomahawk Player - === + * + * Copyright 2010-2011, Christian Muehlhaeuser + * + * Tomahawk is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * Tomahawk is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with Tomahawk. If not, see . + */ #include "databasecommand_clientauthvalid.h" diff --git a/src/libtomahawk/database/databasecommand_clientauthvalid.h b/src/libtomahawk/database/databasecommand_clientauthvalid.h index efb3c7bb1..94191200e 100644 --- a/src/libtomahawk/database/databasecommand_clientauthvalid.h +++ b/src/libtomahawk/database/databasecommand_clientauthvalid.h @@ -1,18 +1,20 @@ -/**************************************************************************************** - * Copyright (c) 2011 Leo Franchi * - * * - * 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, see . * - ****************************************************************************************/ +/* === This file is part of Tomahawk Player - === + * + * Copyright 2010-2011, Christian Muehlhaeuser + * + * Tomahawk is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * Tomahawk is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with Tomahawk. If not, see . + */ #ifndef DATABASECOMMAND_CLIENTAUTHVALID_H #define DATABASECOMMAND_CLIENTAUTHVALID_H diff --git a/src/libtomahawk/database/databasecommand_collectionstats.cpp b/src/libtomahawk/database/databasecommand_collectionstats.cpp index 7236981ff..081b24ade 100644 --- a/src/libtomahawk/database/databasecommand_collectionstats.cpp +++ b/src/libtomahawk/database/databasecommand_collectionstats.cpp @@ -1,3 +1,21 @@ +/* === This file is part of Tomahawk Player - === + * + * Copyright 2010-2011, Christian Muehlhaeuser + * + * Tomahawk is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * Tomahawk is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with Tomahawk. If not, see . + */ + #include "databasecommand_collectionstats.h" #include "databaseimpl.h" diff --git a/src/libtomahawk/database/databasecommand_collectionstats.h b/src/libtomahawk/database/databasecommand_collectionstats.h index 1c14a3eb4..58b55aefc 100644 --- a/src/libtomahawk/database/databasecommand_collectionstats.h +++ b/src/libtomahawk/database/databasecommand_collectionstats.h @@ -1,3 +1,21 @@ +/* === This file is part of Tomahawk Player - === + * + * Copyright 2010-2011, Christian Muehlhaeuser + * + * Tomahawk is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * Tomahawk is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with Tomahawk. If not, see . + */ + #ifndef DATABASECOMMAND_COLLECTIONSTATS_H #define DATABASECOMMAND_COLLECTIONSTATS_H diff --git a/src/libtomahawk/database/databasecommand_createdynamicplaylist.cpp b/src/libtomahawk/database/databasecommand_createdynamicplaylist.cpp index b906e3077..26a2d44fc 100644 --- a/src/libtomahawk/database/databasecommand_createdynamicplaylist.cpp +++ b/src/libtomahawk/database/databasecommand_createdynamicplaylist.cpp @@ -1,3 +1,21 @@ +/* === This file is part of Tomahawk Player - === + * + * Copyright 2010-2011, Christian Muehlhaeuser + * + * Tomahawk is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * Tomahawk is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with Tomahawk. If not, see . + */ + #include "databasecommand_createdynamicplaylist.h" #include diff --git a/src/libtomahawk/database/databasecommand_createdynamicplaylist.h b/src/libtomahawk/database/databasecommand_createdynamicplaylist.h index cdf180155..2c2183b61 100644 --- a/src/libtomahawk/database/databasecommand_createdynamicplaylist.h +++ b/src/libtomahawk/database/databasecommand_createdynamicplaylist.h @@ -1,3 +1,21 @@ +/* === This file is part of Tomahawk Player - === + * + * Copyright 2010-2011, Christian Muehlhaeuser + * + * Tomahawk is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * Tomahawk is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with Tomahawk. If not, see . + */ + #ifndef DATABASECOMMAND_CREATEDYNAMICPLAYLIST_H #define DATABASECOMMAND_CREATEDYNAMICPLAYLIST_H diff --git a/src/libtomahawk/database/databasecommand_createplaylist.cpp b/src/libtomahawk/database/databasecommand_createplaylist.cpp index 7d703b668..0d75ae32f 100644 --- a/src/libtomahawk/database/databasecommand_createplaylist.cpp +++ b/src/libtomahawk/database/databasecommand_createplaylist.cpp @@ -1,3 +1,21 @@ +/* === This file is part of Tomahawk Player - === + * + * Copyright 2010-2011, Christian Muehlhaeuser + * + * Tomahawk is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * Tomahawk is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with Tomahawk. If not, see . + */ + #include "databasecommand_createplaylist.h" #include diff --git a/src/libtomahawk/database/databasecommand_createplaylist.h b/src/libtomahawk/database/databasecommand_createplaylist.h index 40b2c3de0..ec3aa14a3 100644 --- a/src/libtomahawk/database/databasecommand_createplaylist.h +++ b/src/libtomahawk/database/databasecommand_createplaylist.h @@ -1,3 +1,21 @@ +/* === This file is part of Tomahawk Player - === + * + * Copyright 2010-2011, Christian Muehlhaeuser + * + * Tomahawk is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * Tomahawk is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with Tomahawk. If not, see . + */ + #ifndef DATABASECOMMAND_CREATEPLAYLIST_H #define DATABASECOMMAND_CREATEPLAYLIST_H diff --git a/src/libtomahawk/database/databasecommand_deletedynamicplaylist.cpp b/src/libtomahawk/database/databasecommand_deletedynamicplaylist.cpp index e59e55ff9..34c5defbd 100644 --- a/src/libtomahawk/database/databasecommand_deletedynamicplaylist.cpp +++ b/src/libtomahawk/database/databasecommand_deletedynamicplaylist.cpp @@ -1,18 +1,21 @@ -/**************************************************************************************** - * Copyright (c) 2010 Leo Franchi * - * * - * 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, see . * - ****************************************************************************************/ +/* === This file is part of Tomahawk Player - === + * + * Copyright 2010-2011, Christian Muehlhaeuser + * + * Tomahawk is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * Tomahawk is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with Tomahawk. If not, see . + */ + #include "databasecommand_deletedynamicplaylist.h" #include diff --git a/src/libtomahawk/database/databasecommand_deletedynamicplaylist.h b/src/libtomahawk/database/databasecommand_deletedynamicplaylist.h index b9fcf905f..be0ba5df1 100644 --- a/src/libtomahawk/database/databasecommand_deletedynamicplaylist.h +++ b/src/libtomahawk/database/databasecommand_deletedynamicplaylist.h @@ -1,18 +1,20 @@ -/**************************************************************************************** - * Copyright (c) 2010 Leo Franchi * - * * - * 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, see . * - ****************************************************************************************/ +/* === This file is part of Tomahawk Player - === + * + * Copyright 2010-2011, Christian Muehlhaeuser + * + * Tomahawk is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * Tomahawk is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with Tomahawk. If not, see . + */ #ifndef DATABASECOMMAND_DELETEDYNAMICPLAYLIST_H #define DATABASECOMMAND_DELETEDYNAMICPLAYLIST_H diff --git a/src/libtomahawk/database/databasecommand_deletefiles.cpp b/src/libtomahawk/database/databasecommand_deletefiles.cpp index 436bffcfc..03b4b1399 100644 --- a/src/libtomahawk/database/databasecommand_deletefiles.cpp +++ b/src/libtomahawk/database/databasecommand_deletefiles.cpp @@ -1,3 +1,21 @@ +/* === This file is part of Tomahawk Player - === + * + * Copyright 2010-2011, Christian Muehlhaeuser + * + * Tomahawk is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * Tomahawk is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with Tomahawk. If not, see . + */ + #include "databasecommand_deletefiles.h" #include diff --git a/src/libtomahawk/database/databasecommand_deletefiles.h b/src/libtomahawk/database/databasecommand_deletefiles.h index d10fb98c7..a51e0a34a 100644 --- a/src/libtomahawk/database/databasecommand_deletefiles.h +++ b/src/libtomahawk/database/databasecommand_deletefiles.h @@ -1,3 +1,21 @@ +/* === This file is part of Tomahawk Player - === + * + * Copyright 2010-2011, Christian Muehlhaeuser + * + * Tomahawk is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * Tomahawk is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with Tomahawk. If not, see . + */ + #ifndef DATABASECOMMAND_DELETEFILES_H #define DATABASECOMMAND_DELETEFILES_H diff --git a/src/libtomahawk/database/databasecommand_deleteplaylist.cpp b/src/libtomahawk/database/databasecommand_deleteplaylist.cpp index 424c90df8..d87699594 100644 --- a/src/libtomahawk/database/databasecommand_deleteplaylist.cpp +++ b/src/libtomahawk/database/databasecommand_deleteplaylist.cpp @@ -1,3 +1,21 @@ +/* === This file is part of Tomahawk Player - === + * + * Copyright 2010-2011, Christian Muehlhaeuser + * + * Tomahawk is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * Tomahawk is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with Tomahawk. If not, see . + */ + #include "databasecommand_deleteplaylist.h" #include diff --git a/src/libtomahawk/database/databasecommand_deleteplaylist.h b/src/libtomahawk/database/databasecommand_deleteplaylist.h index 5e6917f07..995e0efcf 100644 --- a/src/libtomahawk/database/databasecommand_deleteplaylist.h +++ b/src/libtomahawk/database/databasecommand_deleteplaylist.h @@ -1,3 +1,21 @@ +/* === This file is part of Tomahawk Player - === + * + * Copyright 2010-2011, Christian Muehlhaeuser + * + * Tomahawk is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * Tomahawk is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with Tomahawk. If not, see . + */ + #ifndef DATABASECOMMAND_DELETEPLAYLIST_H #define DATABASECOMMAND_DELETEPLAYLIST_H diff --git a/src/libtomahawk/database/databasecommand_dirmtimes.cpp b/src/libtomahawk/database/databasecommand_dirmtimes.cpp index 42c7c7084..829a609c8 100644 --- a/src/libtomahawk/database/databasecommand_dirmtimes.cpp +++ b/src/libtomahawk/database/databasecommand_dirmtimes.cpp @@ -1,3 +1,21 @@ +/* === This file is part of Tomahawk Player - === + * + * Copyright 2010-2011, Christian Muehlhaeuser + * + * Tomahawk is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * Tomahawk is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with Tomahawk. If not, see . + */ + #include "databasecommand_dirmtimes.h" #include diff --git a/src/libtomahawk/database/databasecommand_dirmtimes.h b/src/libtomahawk/database/databasecommand_dirmtimes.h index 9071599e0..f35a78437 100644 --- a/src/libtomahawk/database/databasecommand_dirmtimes.h +++ b/src/libtomahawk/database/databasecommand_dirmtimes.h @@ -1,3 +1,21 @@ +/* === This file is part of Tomahawk Player - === + * + * Copyright 2010-2011, Christian Muehlhaeuser + * + * Tomahawk is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * Tomahawk is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with Tomahawk. If not, see . + */ + #ifndef DATABASECOMMAND_DIRMTIMES_H #define DATABASECOMMAND_DIRMTIMES_H diff --git a/src/libtomahawk/database/databasecommand_importplaylist.cpp b/src/libtomahawk/database/databasecommand_importplaylist.cpp index b0e84c07d..2d5fdddc2 100644 --- a/src/libtomahawk/database/databasecommand_importplaylist.cpp +++ b/src/libtomahawk/database/databasecommand_importplaylist.cpp @@ -1,3 +1,21 @@ +/* === This file is part of Tomahawk Player - === + * + * Copyright 2010-2011, Christian Muehlhaeuser + * + * Tomahawk is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * Tomahawk is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with Tomahawk. If not, see . + */ + #include "databasecommand_importplaylist.h" #include diff --git a/src/libtomahawk/database/databasecommand_importplaylist.h b/src/libtomahawk/database/databasecommand_importplaylist.h index 62e3a5d3f..90629f1ba 100644 --- a/src/libtomahawk/database/databasecommand_importplaylist.h +++ b/src/libtomahawk/database/databasecommand_importplaylist.h @@ -1,3 +1,21 @@ +/* === This file is part of Tomahawk Player - === + * + * Copyright 2010-2011, Christian Muehlhaeuser + * + * Tomahawk is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * Tomahawk is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with Tomahawk. If not, see . + */ + #ifndef DATABASECOMMAND_IMPORTPLAYLIST_H #define DATABASECOMMAND_IMPORTPLAYLIST_H diff --git a/src/libtomahawk/database/databasecommand_loadalldynamicplaylists.cpp b/src/libtomahawk/database/databasecommand_loadalldynamicplaylists.cpp index b7c0659a5..333b596fd 100644 --- a/src/libtomahawk/database/databasecommand_loadalldynamicplaylists.cpp +++ b/src/libtomahawk/database/databasecommand_loadalldynamicplaylists.cpp @@ -1,18 +1,21 @@ -/**************************************************************************************** - * Copyright (c) 2010 Leo Franchi * - * * - * 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, see . * - ****************************************************************************************/ +/* === This file is part of Tomahawk Player - === + * + * Copyright 2010-2011, Christian Muehlhaeuser + * + * Tomahawk is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * Tomahawk is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with Tomahawk. If not, see . + */ + #include "databasecommand_loadalldynamicplaylists.h" #include diff --git a/src/libtomahawk/database/databasecommand_loadalldynamicplaylists.h b/src/libtomahawk/database/databasecommand_loadalldynamicplaylists.h index 759735dc1..9b2834c3f 100644 --- a/src/libtomahawk/database/databasecommand_loadalldynamicplaylists.h +++ b/src/libtomahawk/database/databasecommand_loadalldynamicplaylists.h @@ -1,18 +1,21 @@ -/**************************************************************************************** - * Copyright (c) 2010 Leo Franchi * - * * - * 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, see . * - ****************************************************************************************/ +/* === This file is part of Tomahawk Player - === + * + * Copyright 2010-2011, Christian Muehlhaeuser + * + * Tomahawk is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * Tomahawk is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with Tomahawk. If not, see . + */ + #ifndef DATABASECOMMAND_LOADALLDYNAMICPLAYLISTS_H #define DATABASECOMMAND_LOADALLDYNAMICPLAYLISTS_H diff --git a/src/libtomahawk/database/databasecommand_loadallplaylists.cpp b/src/libtomahawk/database/databasecommand_loadallplaylists.cpp index d742c6c91..db7865767 100644 --- a/src/libtomahawk/database/databasecommand_loadallplaylists.cpp +++ b/src/libtomahawk/database/databasecommand_loadallplaylists.cpp @@ -1,3 +1,21 @@ +/* === This file is part of Tomahawk Player - === + * + * Copyright 2010-2011, Christian Muehlhaeuser + * + * Tomahawk is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * Tomahawk is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with Tomahawk. If not, see . + */ + #include "databasecommand_loadallplaylists.h" #include diff --git a/src/libtomahawk/database/databasecommand_loadallplaylists.h b/src/libtomahawk/database/databasecommand_loadallplaylists.h index 53ee5e3c0..4a2c6f66d 100644 --- a/src/libtomahawk/database/databasecommand_loadallplaylists.h +++ b/src/libtomahawk/database/databasecommand_loadallplaylists.h @@ -1,3 +1,21 @@ +/* === This file is part of Tomahawk Player - === + * + * Copyright 2010-2011, Christian Muehlhaeuser + * + * Tomahawk is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * Tomahawk is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with Tomahawk. If not, see . + */ + #ifndef DATABASECOMMAND_LOADALLPLAYLIST_H #define DATABASECOMMAND_LOADALLPLAYLIST_H diff --git a/src/libtomahawk/database/databasecommand_loadallsources.cpp b/src/libtomahawk/database/databasecommand_loadallsources.cpp index 58e6744d2..79e09387c 100644 --- a/src/libtomahawk/database/databasecommand_loadallsources.cpp +++ b/src/libtomahawk/database/databasecommand_loadallsources.cpp @@ -1,3 +1,21 @@ +/* === This file is part of Tomahawk Player - === + * + * Copyright 2010-2011, Christian Muehlhaeuser + * + * Tomahawk is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * Tomahawk is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with Tomahawk. If not, see . + */ + #include "databasecommand_loadallsources.h" #include diff --git a/src/libtomahawk/database/databasecommand_loadallsources.h b/src/libtomahawk/database/databasecommand_loadallsources.h index fbd99597d..46ef0ec2c 100644 --- a/src/libtomahawk/database/databasecommand_loadallsources.h +++ b/src/libtomahawk/database/databasecommand_loadallsources.h @@ -1,3 +1,21 @@ +/* === This file is part of Tomahawk Player - === + * + * Copyright 2010-2011, Christian Muehlhaeuser + * + * Tomahawk is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * Tomahawk is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with Tomahawk. If not, see . + */ + #ifndef DATABASECOMMAND_LOADALLSOURCES_H #define DATABASECOMMAND_LOADALLSOURCES_H diff --git a/src/libtomahawk/database/databasecommand_loaddynamicplaylist.cpp b/src/libtomahawk/database/databasecommand_loaddynamicplaylist.cpp index eb0436e7f..eb80312b0 100644 --- a/src/libtomahawk/database/databasecommand_loaddynamicplaylist.cpp +++ b/src/libtomahawk/database/databasecommand_loaddynamicplaylist.cpp @@ -1,3 +1,21 @@ +/* === This file is part of Tomahawk Player - === + * + * Copyright 2010-2011, Christian Muehlhaeuser + * + * Tomahawk is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * Tomahawk is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with Tomahawk. If not, see . + */ + #include "databasecommand_loaddynamicplaylist.h" #include diff --git a/src/libtomahawk/database/databasecommand_loaddynamicplaylist.h b/src/libtomahawk/database/databasecommand_loaddynamicplaylist.h index 8b7205f73..e797e1cec 100644 --- a/src/libtomahawk/database/databasecommand_loaddynamicplaylist.h +++ b/src/libtomahawk/database/databasecommand_loaddynamicplaylist.h @@ -1,3 +1,21 @@ +/* === This file is part of Tomahawk Player - === + * + * Copyright 2010-2011, Christian Muehlhaeuser + * + * Tomahawk is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * Tomahawk is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with Tomahawk. If not, see . + */ + #ifndef DATABASECOMMAND_LOADDYNAMICPLAYLIST_H #define DATABASECOMMAND_LOADDYNAMICPLAYLIST_H diff --git a/src/libtomahawk/database/databasecommand_loadfile.cpp b/src/libtomahawk/database/databasecommand_loadfile.cpp index b54c601b2..8890b3ee5 100644 --- a/src/libtomahawk/database/databasecommand_loadfile.cpp +++ b/src/libtomahawk/database/databasecommand_loadfile.cpp @@ -1,3 +1,21 @@ +/* === This file is part of Tomahawk Player - === + * + * Copyright 2010-2011, Christian Muehlhaeuser + * + * Tomahawk is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * Tomahawk is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with Tomahawk. If not, see . + */ + #include "databasecommand_loadfile.h" #include "databaseimpl.h" diff --git a/src/libtomahawk/database/databasecommand_loadfile.h b/src/libtomahawk/database/databasecommand_loadfile.h index 1bc04b6bc..7d40c281c 100644 --- a/src/libtomahawk/database/databasecommand_loadfile.h +++ b/src/libtomahawk/database/databasecommand_loadfile.h @@ -1,3 +1,21 @@ +/* === This file is part of Tomahawk Player - === + * + * Copyright 2010-2011, Christian Muehlhaeuser + * + * Tomahawk is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * Tomahawk is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with Tomahawk. If not, see . + */ + #ifndef DATABASECOMMAND_LOADFILE_H #define DATABASECOMMAND_LOADFILE_H diff --git a/src/libtomahawk/database/databasecommand_loadops.cpp b/src/libtomahawk/database/databasecommand_loadops.cpp index 11a9516e0..afc131da4 100644 --- a/src/libtomahawk/database/databasecommand_loadops.cpp +++ b/src/libtomahawk/database/databasecommand_loadops.cpp @@ -1,3 +1,21 @@ +/* === This file is part of Tomahawk Player - === + * + * Copyright 2010-2011, Christian Muehlhaeuser + * + * Tomahawk is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * Tomahawk is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with Tomahawk. If not, see . + */ + #include "databasecommand_loadops.h" diff --git a/src/libtomahawk/database/databasecommand_loadops.h b/src/libtomahawk/database/databasecommand_loadops.h index e846ea95c..6a3580f42 100644 --- a/src/libtomahawk/database/databasecommand_loadops.h +++ b/src/libtomahawk/database/databasecommand_loadops.h @@ -1,3 +1,21 @@ +/* === This file is part of Tomahawk Player - === + * + * Copyright 2010-2011, Christian Muehlhaeuser + * + * Tomahawk is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * Tomahawk is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with Tomahawk. If not, see . + */ + #ifndef DATABASECOMMAND_LOADOPS_H #define DATABASECOMMAND_LOADOPS_H diff --git a/src/libtomahawk/database/databasecommand_loadplaylistentries.cpp b/src/libtomahawk/database/databasecommand_loadplaylistentries.cpp index 0e47ee26a..20143dd53 100644 --- a/src/libtomahawk/database/databasecommand_loadplaylistentries.cpp +++ b/src/libtomahawk/database/databasecommand_loadplaylistentries.cpp @@ -1,3 +1,21 @@ +/* === This file is part of Tomahawk Player - === + * + * Copyright 2010-2011, Christian Muehlhaeuser + * + * Tomahawk is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * Tomahawk is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with Tomahawk. If not, see . + */ + #include "databasecommand_loadplaylistentries.h" #include diff --git a/src/libtomahawk/database/databasecommand_loadplaylistentries.h b/src/libtomahawk/database/databasecommand_loadplaylistentries.h index 5f672749e..ec597ef67 100644 --- a/src/libtomahawk/database/databasecommand_loadplaylistentries.h +++ b/src/libtomahawk/database/databasecommand_loadplaylistentries.h @@ -1,3 +1,21 @@ +/* === This file is part of Tomahawk Player - === + * + * Copyright 2010-2011, Christian Muehlhaeuser + * + * Tomahawk is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * Tomahawk is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with Tomahawk. If not, see . + */ + #ifndef DATABASECOMMAND_LOADPLAYLIST_H #define DATABASECOMMAND_LOADPLAYLIST_H diff --git a/src/libtomahawk/database/databasecommand_logplayback.cpp b/src/libtomahawk/database/databasecommand_logplayback.cpp index e08f071d5..9254ea979 100644 --- a/src/libtomahawk/database/databasecommand_logplayback.cpp +++ b/src/libtomahawk/database/databasecommand_logplayback.cpp @@ -1,3 +1,21 @@ +/* === This file is part of Tomahawk Player - === + * + * Copyright 2010-2011, Christian Muehlhaeuser + * + * Tomahawk is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * Tomahawk is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with Tomahawk. If not, see . + */ + #include "databasecommand_logplayback.h" #include diff --git a/src/libtomahawk/database/databasecommand_logplayback.h b/src/libtomahawk/database/databasecommand_logplayback.h index f7f654780..ab1f7b8ed 100644 --- a/src/libtomahawk/database/databasecommand_logplayback.h +++ b/src/libtomahawk/database/databasecommand_logplayback.h @@ -1,3 +1,21 @@ +/* === This file is part of Tomahawk Player - === + * + * Copyright 2010-2011, Christian Muehlhaeuser + * + * Tomahawk is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * Tomahawk is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with Tomahawk. If not, see . + */ + #ifndef DATABASECOMMAND_LOGPLAYBACK_H #define DATABASECOMMAND_LOGPLAYBACK_H diff --git a/src/libtomahawk/database/databasecommand_modifyplaylist.cpp b/src/libtomahawk/database/databasecommand_modifyplaylist.cpp index 91a2ae9ce..640fe8a3c 100644 --- a/src/libtomahawk/database/databasecommand_modifyplaylist.cpp +++ b/src/libtomahawk/database/databasecommand_modifyplaylist.cpp @@ -1,3 +1,21 @@ +/* === This file is part of Tomahawk Player - === + * + * Copyright 2010-2011, Christian Muehlhaeuser + * + * Tomahawk is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * Tomahawk is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with Tomahawk. If not, see . + */ + #include "databasecommand_modifyplaylist.h" using namespace Tomahawk; diff --git a/src/libtomahawk/database/databasecommand_modifyplaylist.h b/src/libtomahawk/database/databasecommand_modifyplaylist.h index e967bdb29..fd2f6e7d9 100644 --- a/src/libtomahawk/database/databasecommand_modifyplaylist.h +++ b/src/libtomahawk/database/databasecommand_modifyplaylist.h @@ -1,3 +1,21 @@ +/* === This file is part of Tomahawk Player - === + * + * Copyright 2010-2011, Christian Muehlhaeuser + * + * Tomahawk is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * Tomahawk is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with Tomahawk. If not, see . + */ + #ifndef DATABASECOMMAND_MODIFYPLAYLIST_H #define DATABASECOMMAND_MODIFYPLAYLIST_H diff --git a/src/libtomahawk/database/databasecommand_playbackhistory.cpp b/src/libtomahawk/database/databasecommand_playbackhistory.cpp index d3356b841..853bc2a03 100644 --- a/src/libtomahawk/database/databasecommand_playbackhistory.cpp +++ b/src/libtomahawk/database/databasecommand_playbackhistory.cpp @@ -1,3 +1,21 @@ +/* === This file is part of Tomahawk Player - === + * + * Copyright 2010-2011, Christian Muehlhaeuser + * + * Tomahawk is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * Tomahawk is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with Tomahawk. If not, see . + */ + #include "databasecommand_playbackhistory.h" #include diff --git a/src/libtomahawk/database/databasecommand_playbackhistory.h b/src/libtomahawk/database/databasecommand_playbackhistory.h index da57e07df..662e66ff2 100644 --- a/src/libtomahawk/database/databasecommand_playbackhistory.h +++ b/src/libtomahawk/database/databasecommand_playbackhistory.h @@ -1,3 +1,21 @@ +/* === This file is part of Tomahawk Player - === + * + * Copyright 2010-2011, Christian Muehlhaeuser + * + * Tomahawk is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * Tomahawk is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with Tomahawk. If not, see . + */ + #ifndef DATABASECOMMAND_PLAYBACKHISTORY_H #define DATABASECOMMAND_PLAYBACKHISTORY_H diff --git a/src/libtomahawk/database/databasecommand_renameplaylist.cpp b/src/libtomahawk/database/databasecommand_renameplaylist.cpp index 7363d823e..4187ae27e 100644 --- a/src/libtomahawk/database/databasecommand_renameplaylist.cpp +++ b/src/libtomahawk/database/databasecommand_renameplaylist.cpp @@ -1,3 +1,21 @@ +/* === This file is part of Tomahawk Player - === + * + * Copyright 2010-2011, Christian Muehlhaeuser + * + * Tomahawk is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * Tomahawk is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with Tomahawk. If not, see . + */ + #include "databasecommand_renameplaylist.h" #include diff --git a/src/libtomahawk/database/databasecommand_renameplaylist.h b/src/libtomahawk/database/databasecommand_renameplaylist.h index c6692dcff..9b0015b70 100644 --- a/src/libtomahawk/database/databasecommand_renameplaylist.h +++ b/src/libtomahawk/database/databasecommand_renameplaylist.h @@ -1,3 +1,21 @@ +/* === This file is part of Tomahawk Player - === + * + * Copyright 2010-2011, Christian Muehlhaeuser + * + * Tomahawk is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * Tomahawk is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with Tomahawk. If not, see . + */ + #ifndef DATABASECOMMAND_RENAMEPLAYLIST_H #define DATABASECOMMAND_RENAMEPLAYLIST_H diff --git a/src/libtomahawk/database/databasecommand_resolve.cpp b/src/libtomahawk/database/databasecommand_resolve.cpp index 74c2742e4..f6f2b1c42 100644 --- a/src/libtomahawk/database/databasecommand_resolve.cpp +++ b/src/libtomahawk/database/databasecommand_resolve.cpp @@ -1,3 +1,21 @@ +/* === This file is part of Tomahawk Player - === + * + * Copyright 2010-2011, Christian Muehlhaeuser + * + * Tomahawk is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * Tomahawk is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with Tomahawk. If not, see . + */ + #include "databasecommand_resolve.h" #include "album.h" diff --git a/src/libtomahawk/database/databasecommand_resolve.h b/src/libtomahawk/database/databasecommand_resolve.h index d09ad70af..8c194451e 100644 --- a/src/libtomahawk/database/databasecommand_resolve.h +++ b/src/libtomahawk/database/databasecommand_resolve.h @@ -1,3 +1,21 @@ +/* === This file is part of Tomahawk Player - === + * + * Copyright 2010-2011, Christian Muehlhaeuser + * + * Tomahawk is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * Tomahawk is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with Tomahawk. If not, see . + */ + #ifndef DATABASECOMMAND_RESOLVE_H #define DATABASECOMMAND_RESOLVE_H diff --git a/src/libtomahawk/database/databasecommand_setdynamicplaylistrevision.cpp b/src/libtomahawk/database/databasecommand_setdynamicplaylistrevision.cpp index b8a203109..39494f3fd 100644 --- a/src/libtomahawk/database/databasecommand_setdynamicplaylistrevision.cpp +++ b/src/libtomahawk/database/databasecommand_setdynamicplaylistrevision.cpp @@ -1,3 +1,21 @@ +/* === This file is part of Tomahawk Player - === + * + * Copyright 2010-2011, Christian Muehlhaeuser + * + * Tomahawk is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * Tomahawk is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with Tomahawk. If not, see . + */ + #include "databasecommand_setdynamicplaylistrevision.h" #include diff --git a/src/libtomahawk/database/databasecommand_setdynamicplaylistrevision.h b/src/libtomahawk/database/databasecommand_setdynamicplaylistrevision.h index fb381ba2f..238aa163f 100644 --- a/src/libtomahawk/database/databasecommand_setdynamicplaylistrevision.h +++ b/src/libtomahawk/database/databasecommand_setdynamicplaylistrevision.h @@ -1,3 +1,21 @@ +/* === This file is part of Tomahawk Player - === + * + * Copyright 2010-2011, Christian Muehlhaeuser + * + * Tomahawk is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * Tomahawk is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with Tomahawk. If not, see . + */ + #ifndef DATABASECOMMAND_SETDYNAMICPLAYLISTREVISION_H #define DATABASECOMMAND_SETDYNAMICPLAYLISTREVISION_H diff --git a/src/libtomahawk/database/databasecommand_setplaylistrevision.cpp b/src/libtomahawk/database/databasecommand_setplaylistrevision.cpp index 4f2ee99ed..62eec9d6e 100644 --- a/src/libtomahawk/database/databasecommand_setplaylistrevision.cpp +++ b/src/libtomahawk/database/databasecommand_setplaylistrevision.cpp @@ -1,3 +1,21 @@ +/* === This file is part of Tomahawk Player - === + * + * Copyright 2010-2011, Christian Muehlhaeuser + * + * Tomahawk is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * Tomahawk is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with Tomahawk. If not, see . + */ + #include "databasecommand_setplaylistrevision.h" #include diff --git a/src/libtomahawk/database/databasecommand_setplaylistrevision.h b/src/libtomahawk/database/databasecommand_setplaylistrevision.h index 292ee0aa3..e71c1f7e1 100644 --- a/src/libtomahawk/database/databasecommand_setplaylistrevision.h +++ b/src/libtomahawk/database/databasecommand_setplaylistrevision.h @@ -1,3 +1,21 @@ +/* === This file is part of Tomahawk Player - === + * + * Copyright 2010-2011, Christian Muehlhaeuser + * + * Tomahawk is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * Tomahawk is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with Tomahawk. If not, see . + */ + #ifndef DATABASECOMMAND_SETPLAYLISTREVISION_H #define DATABASECOMMAND_SETPLAYLISTREVISION_H diff --git a/src/libtomahawk/database/databasecommand_sourceoffline.cpp b/src/libtomahawk/database/databasecommand_sourceoffline.cpp index e31e7861e..e09186ddc 100644 --- a/src/libtomahawk/database/databasecommand_sourceoffline.cpp +++ b/src/libtomahawk/database/databasecommand_sourceoffline.cpp @@ -1,3 +1,21 @@ +/* === This file is part of Tomahawk Player - === + * + * Copyright 2010-2011, Christian Muehlhaeuser + * + * Tomahawk is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * Tomahawk is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with Tomahawk. If not, see . + */ + #include "databasecommand_sourceoffline.h" diff --git a/src/libtomahawk/database/databasecommand_sourceoffline.h b/src/libtomahawk/database/databasecommand_sourceoffline.h index 02ccc1571..7f50578e1 100644 --- a/src/libtomahawk/database/databasecommand_sourceoffline.h +++ b/src/libtomahawk/database/databasecommand_sourceoffline.h @@ -1,3 +1,21 @@ +/* === This file is part of Tomahawk Player - === + * + * Copyright 2010-2011, Christian Muehlhaeuser + * + * Tomahawk is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * Tomahawk is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with Tomahawk. If not, see . + */ + #ifndef DATABASECOMMAND_SOURCEOFFLINE_H #define DATABASECOMMAND_SOURCEOFFLINE_H diff --git a/src/libtomahawk/database/databasecommand_updatesearchindex.cpp b/src/libtomahawk/database/databasecommand_updatesearchindex.cpp index 3e461dd2c..8362c9749 100644 --- a/src/libtomahawk/database/databasecommand_updatesearchindex.cpp +++ b/src/libtomahawk/database/databasecommand_updatesearchindex.cpp @@ -1,3 +1,21 @@ +/* === This file is part of Tomahawk Player - === + * + * Copyright 2010-2011, Christian Muehlhaeuser + * + * Tomahawk is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * Tomahawk is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with Tomahawk. If not, see . + */ + #include "databasecommand_updatesearchindex.h" diff --git a/src/libtomahawk/database/databasecommand_updatesearchindex.h b/src/libtomahawk/database/databasecommand_updatesearchindex.h index d8d74ed02..8a0fc95a8 100644 --- a/src/libtomahawk/database/databasecommand_updatesearchindex.h +++ b/src/libtomahawk/database/databasecommand_updatesearchindex.h @@ -1,3 +1,21 @@ +/* === This file is part of Tomahawk Player - === + * + * Copyright 2010-2011, Christian Muehlhaeuser + * + * Tomahawk is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * Tomahawk is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with Tomahawk. If not, see . + */ + #ifndef DATABASECOMMAND_UPDATESEARCHINDEX_H #define DATABASECOMMAND_UPDATESEARCHINDEX_H diff --git a/src/libtomahawk/database/databasecommandloggable.cpp b/src/libtomahawk/database/databasecommandloggable.cpp index 404490944..9c193b454 100644 --- a/src/libtomahawk/database/databasecommandloggable.cpp +++ b/src/libtomahawk/database/databasecommandloggable.cpp @@ -1,3 +1,21 @@ +/* === This file is part of Tomahawk Player - === + * + * Copyright 2010-2011, Christian Muehlhaeuser + * + * Tomahawk is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * Tomahawk is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with Tomahawk. If not, see . + */ + #include "databasecommandloggable.h" diff --git a/src/libtomahawk/database/databasecommandloggable.h b/src/libtomahawk/database/databasecommandloggable.h index 20cfb032e..be400621b 100644 --- a/src/libtomahawk/database/databasecommandloggable.h +++ b/src/libtomahawk/database/databasecommandloggable.h @@ -1,3 +1,21 @@ +/* === This file is part of Tomahawk Player - === + * + * Copyright 2010-2011, Christian Muehlhaeuser + * + * Tomahawk is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * Tomahawk is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with Tomahawk. If not, see . + */ + #ifndef DATABASECOMMANDLOGGABLE_H #define DATABASECOMMANDLOGGABLE_H diff --git a/src/libtomahawk/database/databaseimpl.cpp b/src/libtomahawk/database/databaseimpl.cpp index 30002466b..485c7a5ef 100644 --- a/src/libtomahawk/database/databaseimpl.cpp +++ b/src/libtomahawk/database/databaseimpl.cpp @@ -1,3 +1,21 @@ +/* === This file is part of Tomahawk Player - === + * + * Copyright 2010-2011, Christian Muehlhaeuser + * + * Tomahawk is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * Tomahawk is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with Tomahawk. If not, see . + */ + #include "databaseimpl.h" #include diff --git a/src/libtomahawk/database/databaseimpl.h b/src/libtomahawk/database/databaseimpl.h index b40ee4cf6..579850b7e 100644 --- a/src/libtomahawk/database/databaseimpl.h +++ b/src/libtomahawk/database/databaseimpl.h @@ -1,3 +1,21 @@ +/* === This file is part of Tomahawk Player - === + * + * Copyright 2010-2011, Christian Muehlhaeuser + * + * Tomahawk is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * Tomahawk is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with Tomahawk. If not, see . + */ + #ifndef DATABASEIMPL_H #define DATABASEIMPL_H diff --git a/src/libtomahawk/database/databaseresolver.cpp b/src/libtomahawk/database/databaseresolver.cpp index 799803176..e5da896af 100644 --- a/src/libtomahawk/database/databaseresolver.cpp +++ b/src/libtomahawk/database/databaseresolver.cpp @@ -1,3 +1,21 @@ +/* === This file is part of Tomahawk Player - === + * + * Copyright 2010-2011, Christian Muehlhaeuser + * + * Tomahawk is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * Tomahawk is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with Tomahawk. If not, see . + */ + #include "databaseresolver.h" #include "network/servent.h" diff --git a/src/libtomahawk/database/databaseresolver.h b/src/libtomahawk/database/databaseresolver.h index d2877c0c5..11abb80a0 100644 --- a/src/libtomahawk/database/databaseresolver.h +++ b/src/libtomahawk/database/databaseresolver.h @@ -1,3 +1,21 @@ +/* === This file is part of Tomahawk Player - === + * + * Copyright 2010-2011, Christian Muehlhaeuser + * + * Tomahawk is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * Tomahawk is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with Tomahawk. If not, see . + */ + #ifndef DATABASERESOLVER_H #define DATABASERESOLVER_H diff --git a/src/libtomahawk/database/databaseworker.cpp b/src/libtomahawk/database/databaseworker.cpp index e8efedf91..791888e80 100644 --- a/src/libtomahawk/database/databaseworker.cpp +++ b/src/libtomahawk/database/databaseworker.cpp @@ -1,3 +1,21 @@ +/* === This file is part of Tomahawk Player - === + * + * Copyright 2010-2011, Christian Muehlhaeuser + * + * Tomahawk is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * Tomahawk is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with Tomahawk. If not, see . + */ + #include "databaseworker.h" #include diff --git a/src/libtomahawk/database/databaseworker.h b/src/libtomahawk/database/databaseworker.h index 87073e9df..ea45cec0f 100644 --- a/src/libtomahawk/database/databaseworker.h +++ b/src/libtomahawk/database/databaseworker.h @@ -1,3 +1,21 @@ +/* === This file is part of Tomahawk Player - === + * + * Copyright 2010-2011, Christian Muehlhaeuser + * + * Tomahawk is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * Tomahawk is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with Tomahawk. If not, see . + */ + #ifndef DATABASEWORKER_H #define DATABASEWORKER_H diff --git a/src/libtomahawk/database/fuzzyindex.cpp b/src/libtomahawk/database/fuzzyindex.cpp index 710848b45..25719317c 100644 --- a/src/libtomahawk/database/fuzzyindex.cpp +++ b/src/libtomahawk/database/fuzzyindex.cpp @@ -1,3 +1,21 @@ +/* === This file is part of Tomahawk Player - === + * + * Copyright 2010-2011, Christian Muehlhaeuser + * + * Tomahawk is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * Tomahawk is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with Tomahawk. If not, see . + */ + #include "fuzzyindex.h" #include "databaseimpl.h" diff --git a/src/libtomahawk/database/fuzzyindex.h b/src/libtomahawk/database/fuzzyindex.h index bc294705c..3e93fbb23 100644 --- a/src/libtomahawk/database/fuzzyindex.h +++ b/src/libtomahawk/database/fuzzyindex.h @@ -1,3 +1,21 @@ +/* === This file is part of Tomahawk Player - === + * + * Copyright 2010-2011, Christian Muehlhaeuser + * + * Tomahawk is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * Tomahawk is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with Tomahawk. If not, see . + */ + #ifndef FUZZYINDEX_H #define FUZZYINDEX_H diff --git a/src/libtomahawk/database/op.h b/src/libtomahawk/database/op.h index 775a0a958..9c58e9c56 100644 --- a/src/libtomahawk/database/op.h +++ b/src/libtomahawk/database/op.h @@ -1,3 +1,21 @@ +/* === This file is part of Tomahawk Player - === + * + * Copyright 2010-2011, Christian Muehlhaeuser + * + * Tomahawk is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * Tomahawk is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with Tomahawk. If not, see . + */ + #ifndef OP_H #define OP_H #include diff --git a/src/libtomahawk/database/tomahawksqlquery.h b/src/libtomahawk/database/tomahawksqlquery.h index 5b21a03aa..76f65605c 100644 --- a/src/libtomahawk/database/tomahawksqlquery.h +++ b/src/libtomahawk/database/tomahawksqlquery.h @@ -1,3 +1,21 @@ +/* === This file is part of Tomahawk Player - === + * + * Copyright 2010-2011, Christian Muehlhaeuser + * + * Tomahawk is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * Tomahawk is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with Tomahawk. If not, see . + */ + #ifndef TOMAHAWKSQLQUERY_H #define TOMAHAWKSQLQUERY_H // subclass QSqlQuery so that it prints the error msg if a query fails diff --git a/src/libtomahawk/dllmacro.h b/src/libtomahawk/dllmacro.h index b4b199505..550047539 100644 --- a/src/libtomahawk/dllmacro.h +++ b/src/libtomahawk/dllmacro.h @@ -1,3 +1,21 @@ +/* === This file is part of Tomahawk Player - === + * + * Copyright 2010-2011, Christian Muehlhaeuser + * + * Tomahawk is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * Tomahawk is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with Tomahawk. If not, see . + */ + #ifndef DLLMACRO_H #define DLLMACRO_H diff --git a/src/libtomahawk/functimeout.h b/src/libtomahawk/functimeout.h index 39bad1950..9a6b97d24 100644 --- a/src/libtomahawk/functimeout.h +++ b/src/libtomahawk/functimeout.h @@ -1,3 +1,21 @@ +/* === This file is part of Tomahawk Player - === + * + * Copyright 2010-2011, Christian Muehlhaeuser + * + * Tomahawk is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * Tomahawk is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with Tomahawk. If not, see . + */ + #ifndef FUNCTIMEOUT_H #define FUNCTIMEOUT_H diff --git a/src/libtomahawk/network/bufferiodevice.cpp b/src/libtomahawk/network/bufferiodevice.cpp index 073f7dda7..435a2b2cd 100644 --- a/src/libtomahawk/network/bufferiodevice.cpp +++ b/src/libtomahawk/network/bufferiodevice.cpp @@ -1,3 +1,21 @@ +/* === This file is part of Tomahawk Player - === + * + * Copyright 2010-2011, Christian Muehlhaeuser + * + * Tomahawk is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * Tomahawk is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with Tomahawk. If not, see . + */ + #include "bufferiodevice.h" #include diff --git a/src/libtomahawk/network/bufferiodevice.h b/src/libtomahawk/network/bufferiodevice.h index 1474deb74..4e0244991 100644 --- a/src/libtomahawk/network/bufferiodevice.h +++ b/src/libtomahawk/network/bufferiodevice.h @@ -1,3 +1,21 @@ +/* === This file is part of Tomahawk Player - === + * + * Copyright 2010-2011, Christian Muehlhaeuser + * + * Tomahawk is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * Tomahawk is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with Tomahawk. If not, see . + */ + #ifndef BUFFERIODEVICE_H #define BUFFERIODEVICE_H diff --git a/src/libtomahawk/network/connection.cpp b/src/libtomahawk/network/connection.cpp index 42c78bd74..afa009713 100644 --- a/src/libtomahawk/network/connection.cpp +++ b/src/libtomahawk/network/connection.cpp @@ -1,3 +1,21 @@ +/* === This file is part of Tomahawk Player - === + * + * Copyright 2010-2011, Christian Muehlhaeuser + * + * Tomahawk is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * Tomahawk is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with Tomahawk. If not, see . + */ + #include "connection.h" #include diff --git a/src/libtomahawk/network/connection.h b/src/libtomahawk/network/connection.h index 4bcefe214..95c94980f 100644 --- a/src/libtomahawk/network/connection.h +++ b/src/libtomahawk/network/connection.h @@ -1,3 +1,21 @@ +/* === This file is part of Tomahawk Player - === + * + * Copyright 2010-2011, Christian Muehlhaeuser + * + * Tomahawk is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * Tomahawk is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with Tomahawk. If not, see . + */ + #ifndef CONNECTION_H #define CONNECTION_H diff --git a/src/libtomahawk/network/controlconnection.cpp b/src/libtomahawk/network/controlconnection.cpp index 0973b59ed..d75395fbd 100644 --- a/src/libtomahawk/network/controlconnection.cpp +++ b/src/libtomahawk/network/controlconnection.cpp @@ -1,3 +1,21 @@ +/* === This file is part of Tomahawk Player - === + * + * Copyright 2010-2011, Christian Muehlhaeuser + * + * Tomahawk is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * Tomahawk is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with Tomahawk. If not, see . + */ + #include "controlconnection.h" #include "filetransferconnection.h" diff --git a/src/libtomahawk/network/controlconnection.h b/src/libtomahawk/network/controlconnection.h index 77261ffd3..17e060579 100644 --- a/src/libtomahawk/network/controlconnection.h +++ b/src/libtomahawk/network/controlconnection.h @@ -1,3 +1,21 @@ +/* === This file is part of Tomahawk Player - === + * + * Copyright 2010-2011, Christian Muehlhaeuser + * + * 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 . + */ + /* One ControlConnection always remains open to each peer. diff --git a/src/libtomahawk/network/dbsyncconnection.cpp b/src/libtomahawk/network/dbsyncconnection.cpp index 334fe86d9..a25d8ab8f 100644 --- a/src/libtomahawk/network/dbsyncconnection.cpp +++ b/src/libtomahawk/network/dbsyncconnection.cpp @@ -1,3 +1,21 @@ +/* === This file is part of Tomahawk Player - === + * + * Copyright 2010-2011, Christian Muehlhaeuser + * + * 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 . + */ + /* Database syncing using the oplog table. ======================================= diff --git a/src/libtomahawk/network/dbsyncconnection.h b/src/libtomahawk/network/dbsyncconnection.h index fdb34fffa..f97c633d5 100644 --- a/src/libtomahawk/network/dbsyncconnection.h +++ b/src/libtomahawk/network/dbsyncconnection.h @@ -1,3 +1,21 @@ +/* === This file is part of Tomahawk Player - === + * + * Copyright 2010-2011, Christian Muehlhaeuser + * + * Tomahawk is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * Tomahawk is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with Tomahawk. If not, see . + */ + #ifndef DBSYNCCONNECTION_H #define DBSYNCCONNECTION_H diff --git a/src/libtomahawk/network/filetransferconnection.cpp b/src/libtomahawk/network/filetransferconnection.cpp index 5a372f45c..d357a2f33 100644 --- a/src/libtomahawk/network/filetransferconnection.cpp +++ b/src/libtomahawk/network/filetransferconnection.cpp @@ -1,3 +1,21 @@ +/* === This file is part of Tomahawk Player - === + * + * Copyright 2010-2011, Christian Muehlhaeuser + * + * Tomahawk is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * Tomahawk is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with Tomahawk. If not, see . + */ + #include "filetransferconnection.h" #include diff --git a/src/libtomahawk/network/filetransferconnection.h b/src/libtomahawk/network/filetransferconnection.h index 660fc8507..330a6863a 100644 --- a/src/libtomahawk/network/filetransferconnection.h +++ b/src/libtomahawk/network/filetransferconnection.h @@ -1,3 +1,20 @@ +/* === This file is part of Tomahawk Player - === + * + * Copyright 2010-2011, Christian Muehlhaeuser + * + * Tomahawk is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * Tomahawk is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with Tomahawk. If not, see . + */ #ifndef FILETRANSFERCONNECTION_H #define FILETRANSFERCONNECTION_H diff --git a/src/libtomahawk/network/msg.h b/src/libtomahawk/network/msg.h index d91044ba4..d79fd0982 100644 --- a/src/libtomahawk/network/msg.h +++ b/src/libtomahawk/network/msg.h @@ -1,3 +1,21 @@ +/* === This file is part of Tomahawk Player - === + * + * Copyright 2010-2011, Christian Muehlhaeuser + * + * 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 . + */ + /* Msg is a wire msg used by p2p connections. Msgs have a 5-byte header: diff --git a/src/libtomahawk/network/msgprocessor.cpp b/src/libtomahawk/network/msgprocessor.cpp index c84107a22..85bdcd26c 100644 --- a/src/libtomahawk/network/msgprocessor.cpp +++ b/src/libtomahawk/network/msgprocessor.cpp @@ -1,3 +1,21 @@ +/* === This file is part of Tomahawk Player - === + * + * Copyright 2010-2011, Christian Muehlhaeuser + * + * Tomahawk is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * Tomahawk is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with Tomahawk. If not, see . + */ + #include "msgprocessor.h" #include "network/servent.h" diff --git a/src/libtomahawk/network/msgprocessor.h b/src/libtomahawk/network/msgprocessor.h index 9ecf1e58d..b8dc7ef7d 100644 --- a/src/libtomahawk/network/msgprocessor.h +++ b/src/libtomahawk/network/msgprocessor.h @@ -1,3 +1,21 @@ +/* === This file is part of Tomahawk Player - === + * + * Copyright 2010-2011, Christian Muehlhaeuser + * + * 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 . + */ + /* MsgProcessor is a FIFO queue of msg_ptr, you .add() a msg_ptr, and it emits done(msg_ptr) for each msg, preserving the order. diff --git a/src/libtomahawk/network/portfwdthread.cpp b/src/libtomahawk/network/portfwdthread.cpp index 5b6a0a142..36cdd8088 100644 --- a/src/libtomahawk/network/portfwdthread.cpp +++ b/src/libtomahawk/network/portfwdthread.cpp @@ -1,3 +1,21 @@ +/* === This file is part of Tomahawk Player - === + * + * Copyright 2010-2011, Christian Muehlhaeuser + * + * Tomahawk is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * Tomahawk is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with Tomahawk. If not, see . + */ + #include "portfwdthread.h" #include diff --git a/src/libtomahawk/network/portfwdthread.h b/src/libtomahawk/network/portfwdthread.h index d18c699e4..85eb98023 100644 --- a/src/libtomahawk/network/portfwdthread.h +++ b/src/libtomahawk/network/portfwdthread.h @@ -1,3 +1,21 @@ +/* === This file is part of Tomahawk Player - === + * + * Copyright 2010-2011, Christian Muehlhaeuser + * + * Tomahawk is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * Tomahawk is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with Tomahawk. If not, see . + */ + #ifndef PORTFWDTHREAD_H #define PORTFWDTHREAD_H diff --git a/src/libtomahawk/network/remotecollection.cpp b/src/libtomahawk/network/remotecollection.cpp index 757164456..8bfdfd3a2 100644 --- a/src/libtomahawk/network/remotecollection.cpp +++ b/src/libtomahawk/network/remotecollection.cpp @@ -1,3 +1,21 @@ +/* === This file is part of Tomahawk Player - === + * + * Copyright 2010-2011, Christian Muehlhaeuser + * + * Tomahawk is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * Tomahawk is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with Tomahawk. If not, see . + */ + #include "remotecollection.h" using namespace Tomahawk; diff --git a/src/libtomahawk/network/remotecollection.h b/src/libtomahawk/network/remotecollection.h index 86d334f50..ef28be853 100644 --- a/src/libtomahawk/network/remotecollection.h +++ b/src/libtomahawk/network/remotecollection.h @@ -1,3 +1,21 @@ +/* === This file is part of Tomahawk Player - === + * + * Copyright 2010-2011, Christian Muehlhaeuser + * + * Tomahawk is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * Tomahawk is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with Tomahawk. If not, see . + */ + #ifndef REMOTECOLLECTION_H #define REMOTECOLLECTION_H diff --git a/src/libtomahawk/network/servent.cpp b/src/libtomahawk/network/servent.cpp index e85de3456..6079394f9 100644 --- a/src/libtomahawk/network/servent.cpp +++ b/src/libtomahawk/network/servent.cpp @@ -1,3 +1,21 @@ +/* === This file is part of Tomahawk Player - === + * + * Copyright 2010-2011, Christian Muehlhaeuser + * + * Tomahawk is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * Tomahawk is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with Tomahawk. If not, see . + */ + #include "servent.h" #include diff --git a/src/libtomahawk/network/servent.h b/src/libtomahawk/network/servent.h index d67fedd2a..f88581445 100644 --- a/src/libtomahawk/network/servent.h +++ b/src/libtomahawk/network/servent.h @@ -1,3 +1,21 @@ +/* === This file is part of Tomahawk Player - === + * + * Copyright 2010-2011, Christian Muehlhaeuser + * + * Tomahawk is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * Tomahawk is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with Tomahawk. If not, see . + */ + #ifndef SERVENT_H #define SERVENT_H diff --git a/src/libtomahawk/pipeline.cpp b/src/libtomahawk/pipeline.cpp index fcb535723..d6f3e8891 100644 --- a/src/libtomahawk/pipeline.cpp +++ b/src/libtomahawk/pipeline.cpp @@ -1,3 +1,21 @@ +/* === This file is part of Tomahawk Player - === + * + * Copyright 2010-2011, Christian Muehlhaeuser + * + * Tomahawk is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * Tomahawk is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with Tomahawk. If not, see . + */ + #include "pipeline.h" #include diff --git a/src/libtomahawk/pipeline.h b/src/libtomahawk/pipeline.h index 7035535da..8ed0456bb 100644 --- a/src/libtomahawk/pipeline.h +++ b/src/libtomahawk/pipeline.h @@ -1,3 +1,21 @@ +/* === This file is part of Tomahawk Player - === + * + * Copyright 2010-2011, Christian Muehlhaeuser + * + * Tomahawk is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * Tomahawk is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with Tomahawk. If not, see . + */ + #ifndef PIPELINE_H #define PIPELINE_H diff --git a/src/libtomahawk/playlist.cpp b/src/libtomahawk/playlist.cpp index 13128baa8..f27e592e0 100644 --- a/src/libtomahawk/playlist.cpp +++ b/src/libtomahawk/playlist.cpp @@ -1,3 +1,21 @@ +/* === This file is part of Tomahawk Player - === + * + * Copyright 2010-2011, Christian Muehlhaeuser + * + * Tomahawk is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * Tomahawk is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with Tomahawk. If not, see . + */ + #include "playlist.h" #include diff --git a/src/libtomahawk/playlist.h b/src/libtomahawk/playlist.h index c3ae79175..f973927cc 100644 --- a/src/libtomahawk/playlist.h +++ b/src/libtomahawk/playlist.h @@ -1,3 +1,21 @@ +/* === This file is part of Tomahawk Player - === + * + * Copyright 2010-2011, Christian Muehlhaeuser + * + * Tomahawk is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * Tomahawk is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with Tomahawk. If not, see . + */ + #ifndef PLAYLIST_H #define PLAYLIST_H diff --git a/src/libtomahawk/playlist/albumitem.cpp b/src/libtomahawk/playlist/albumitem.cpp index c2bc9de5a..398703e68 100644 --- a/src/libtomahawk/playlist/albumitem.cpp +++ b/src/libtomahawk/playlist/albumitem.cpp @@ -1,3 +1,21 @@ +/* === This file is part of Tomahawk Player - === + * + * Copyright 2010-2011, Christian Muehlhaeuser + * + * Tomahawk is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * Tomahawk is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with Tomahawk. If not, see . + */ + #include "albumitem.h" #include "utils/tomahawkutils.h" diff --git a/src/libtomahawk/playlist/albumitem.h b/src/libtomahawk/playlist/albumitem.h index 16dc81b70..ece235c3e 100644 --- a/src/libtomahawk/playlist/albumitem.h +++ b/src/libtomahawk/playlist/albumitem.h @@ -1,3 +1,21 @@ +/* === This file is part of Tomahawk Player - === + * + * Copyright 2010-2011, Christian Muehlhaeuser + * + * Tomahawk is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * Tomahawk is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with Tomahawk. If not, see . + */ + #ifndef ALBUMITEM_H #define ALBUMITEM_H diff --git a/src/libtomahawk/playlist/albumitemdelegate.cpp b/src/libtomahawk/playlist/albumitemdelegate.cpp index 631ac6bf1..a0b4ec2ae 100644 --- a/src/libtomahawk/playlist/albumitemdelegate.cpp +++ b/src/libtomahawk/playlist/albumitemdelegate.cpp @@ -1,3 +1,21 @@ +/* === This file is part of Tomahawk Player - === + * + * Copyright 2010-2011, Christian Muehlhaeuser + * + * Tomahawk is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * Tomahawk is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with Tomahawk. If not, see . + */ + #include "albumitemdelegate.h" #include diff --git a/src/libtomahawk/playlist/albumitemdelegate.h b/src/libtomahawk/playlist/albumitemdelegate.h index 708eec70d..279f4a569 100644 --- a/src/libtomahawk/playlist/albumitemdelegate.h +++ b/src/libtomahawk/playlist/albumitemdelegate.h @@ -1,3 +1,21 @@ +/* === This file is part of Tomahawk Player - === + * + * Copyright 2010-2011, Christian Muehlhaeuser + * + * Tomahawk is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * Tomahawk is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with Tomahawk. If not, see . + */ + #ifndef ALBUMITEMDELEGATE_H #define ALBUMITEMDELEGATE_H diff --git a/src/libtomahawk/playlist/albummodel.cpp b/src/libtomahawk/playlist/albummodel.cpp index fbbad77bd..a60610e36 100644 --- a/src/libtomahawk/playlist/albummodel.cpp +++ b/src/libtomahawk/playlist/albummodel.cpp @@ -1,3 +1,21 @@ +/* === This file is part of Tomahawk Player - === + * + * Copyright 2010-2011, Christian Muehlhaeuser + * + * Tomahawk is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * Tomahawk is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with Tomahawk. If not, see . + */ + #include "albummodel.h" #include diff --git a/src/libtomahawk/playlist/albummodel.h b/src/libtomahawk/playlist/albummodel.h index f25724ad4..dc080a84c 100644 --- a/src/libtomahawk/playlist/albummodel.h +++ b/src/libtomahawk/playlist/albummodel.h @@ -1,3 +1,21 @@ +/* === This file is part of Tomahawk Player - === + * + * Copyright 2010-2011, Christian Muehlhaeuser + * + * Tomahawk is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * Tomahawk is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with Tomahawk. If not, see . + */ + #ifndef ALBUMMODEL_H #define ALBUMMODEL_H diff --git a/src/libtomahawk/playlist/albumproxymodel.cpp b/src/libtomahawk/playlist/albumproxymodel.cpp index 36c470fe7..8a9f66493 100644 --- a/src/libtomahawk/playlist/albumproxymodel.cpp +++ b/src/libtomahawk/playlist/albumproxymodel.cpp @@ -1,3 +1,21 @@ +/* === This file is part of Tomahawk Player - === + * + * Copyright 2010-2011, Christian Muehlhaeuser + * + * Tomahawk is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * Tomahawk is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with Tomahawk. If not, see . + */ + #include "albumproxymodel.h" #include diff --git a/src/libtomahawk/playlist/albumproxymodel.h b/src/libtomahawk/playlist/albumproxymodel.h index 3e694e2a6..151ba51c8 100644 --- a/src/libtomahawk/playlist/albumproxymodel.h +++ b/src/libtomahawk/playlist/albumproxymodel.h @@ -1,3 +1,21 @@ +/* === This file is part of Tomahawk Player - === + * + * Copyright 2010-2011, Christian Muehlhaeuser + * + * Tomahawk is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * Tomahawk is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with Tomahawk. If not, see . + */ + #ifndef ALBUMPROXYMODEL_H #define ALBUMPROXYMODEL_H diff --git a/src/libtomahawk/playlist/albumview.cpp b/src/libtomahawk/playlist/albumview.cpp index 83ecb28b9..1f8affea7 100644 --- a/src/libtomahawk/playlist/albumview.cpp +++ b/src/libtomahawk/playlist/albumview.cpp @@ -1,3 +1,21 @@ +/* === This file is part of Tomahawk Player - === + * + * Copyright 2010-2011, Christian Muehlhaeuser + * + * Tomahawk is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * Tomahawk is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with Tomahawk. If not, see . + */ + #include "albumview.h" #include diff --git a/src/libtomahawk/playlist/albumview.h b/src/libtomahawk/playlist/albumview.h index d4616cd3c..953863ab2 100644 --- a/src/libtomahawk/playlist/albumview.h +++ b/src/libtomahawk/playlist/albumview.h @@ -1,3 +1,21 @@ +/* === This file is part of Tomahawk Player - === + * + * Copyright 2010-2011, Christian Muehlhaeuser + * + * Tomahawk is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * Tomahawk is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with Tomahawk. If not, see . + */ + #ifndef ALBUMVIEW_H #define ALBUMVIEW_H diff --git a/src/libtomahawk/playlist/collectionflatmodel.cpp b/src/libtomahawk/playlist/collectionflatmodel.cpp index 02d56a351..769ef337c 100644 --- a/src/libtomahawk/playlist/collectionflatmodel.cpp +++ b/src/libtomahawk/playlist/collectionflatmodel.cpp @@ -1,3 +1,21 @@ +/* === This file is part of Tomahawk Player - === + * + * Copyright 2010-2011, Christian Muehlhaeuser + * + * Tomahawk is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * Tomahawk is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with Tomahawk. If not, see . + */ + #include "collectionflatmodel.h" #include diff --git a/src/libtomahawk/playlist/collectionflatmodel.h b/src/libtomahawk/playlist/collectionflatmodel.h index c2cf83d38..cbfee0d36 100644 --- a/src/libtomahawk/playlist/collectionflatmodel.h +++ b/src/libtomahawk/playlist/collectionflatmodel.h @@ -1,3 +1,21 @@ +/* === This file is part of Tomahawk Player - === + * + * Copyright 2010-2011, Christian Muehlhaeuser + * + * Tomahawk is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * Tomahawk is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with Tomahawk. If not, see . + */ + #ifndef COLLECTIONFLATMODEL_H #define COLLECTIONFLATMODEL_H diff --git a/src/libtomahawk/playlist/collectionmodel.cpp b/src/libtomahawk/playlist/collectionmodel.cpp index 5aaf21d57..4ba38d861 100644 --- a/src/libtomahawk/playlist/collectionmodel.cpp +++ b/src/libtomahawk/playlist/collectionmodel.cpp @@ -1,3 +1,21 @@ +/* === This file is part of Tomahawk Player - === + * + * Copyright 2010-2011, Christian Muehlhaeuser + * + * Tomahawk is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * Tomahawk is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with Tomahawk. If not, see . + */ + #include "collectionmodel.h" #include diff --git a/src/libtomahawk/playlist/collectionmodel.h b/src/libtomahawk/playlist/collectionmodel.h index 31f1ab779..346133013 100644 --- a/src/libtomahawk/playlist/collectionmodel.h +++ b/src/libtomahawk/playlist/collectionmodel.h @@ -1,3 +1,21 @@ +/* === This file is part of Tomahawk Player - === + * + * Copyright 2010-2011, Christian Muehlhaeuser + * + * Tomahawk is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * Tomahawk is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with Tomahawk. If not, see . + */ + #ifndef COLLECTIONMODEL_H #define COLLECTIONMODEL_H diff --git a/src/libtomahawk/playlist/collectionproxymodel.cpp b/src/libtomahawk/playlist/collectionproxymodel.cpp index b477d2d17..f25532d1f 100644 --- a/src/libtomahawk/playlist/collectionproxymodel.cpp +++ b/src/libtomahawk/playlist/collectionproxymodel.cpp @@ -1,3 +1,21 @@ +/* === This file is part of Tomahawk Player - === + * + * Copyright 2010-2011, Christian Muehlhaeuser + * + * Tomahawk is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * Tomahawk is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with Tomahawk. If not, see . + */ + #include "collectionproxymodel.h" #include diff --git a/src/libtomahawk/playlist/collectionproxymodel.h b/src/libtomahawk/playlist/collectionproxymodel.h index af46be8c4..63c615f40 100644 --- a/src/libtomahawk/playlist/collectionproxymodel.h +++ b/src/libtomahawk/playlist/collectionproxymodel.h @@ -1,3 +1,21 @@ +/* === This file is part of Tomahawk Player - === + * + * Copyright 2010-2011, Christian Muehlhaeuser + * + * Tomahawk is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * Tomahawk is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with Tomahawk. If not, see . + */ + #ifndef COLLECTIONPROXYMODEL_H #define COLLECTIONPROXYMODEL_H diff --git a/src/libtomahawk/playlist/collectionview.cpp b/src/libtomahawk/playlist/collectionview.cpp index 09b557611..6c16f4330 100644 --- a/src/libtomahawk/playlist/collectionview.cpp +++ b/src/libtomahawk/playlist/collectionview.cpp @@ -1,3 +1,21 @@ +/* === This file is part of Tomahawk Player - === + * + * Copyright 2010-2011, Christian Muehlhaeuser + * + * Tomahawk is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * Tomahawk is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with Tomahawk. If not, see . + */ + #include "collectionview.h" #include diff --git a/src/libtomahawk/playlist/collectionview.h b/src/libtomahawk/playlist/collectionview.h index cddd1e98c..158e1a327 100644 --- a/src/libtomahawk/playlist/collectionview.h +++ b/src/libtomahawk/playlist/collectionview.h @@ -1,3 +1,21 @@ +/* === This file is part of Tomahawk Player - === + * + * Copyright 2010-2011, Christian Muehlhaeuser + * + * Tomahawk is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * Tomahawk is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with Tomahawk. If not, see . + */ + #ifndef COLLECTIONVIEW_H #define COLLECTIONVIEW_H diff --git a/src/libtomahawk/playlist/dynamic/DynamicControl.cpp b/src/libtomahawk/playlist/dynamic/DynamicControl.cpp index 61f7cd315..ae81f8665 100644 --- a/src/libtomahawk/playlist/dynamic/DynamicControl.cpp +++ b/src/libtomahawk/playlist/dynamic/DynamicControl.cpp @@ -1,18 +1,20 @@ -/**************************************************************************************** - * Copyright (c) 2010 Leo Franchi * - * * - * 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, see . * - ****************************************************************************************/ +/* === This file is part of Tomahawk Player - === + * + * Copyright 2010-2011, Christian Muehlhaeuser + * + * Tomahawk is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * Tomahawk is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with Tomahawk. If not, see . + */ #include "DynamicControl.h" diff --git a/src/libtomahawk/playlist/dynamic/DynamicControl.h b/src/libtomahawk/playlist/dynamic/DynamicControl.h index 28aa126f7..42b62d07d 100644 --- a/src/libtomahawk/playlist/dynamic/DynamicControl.h +++ b/src/libtomahawk/playlist/dynamic/DynamicControl.h @@ -1,18 +1,20 @@ -/**************************************************************************************** - * Copyright (c) 2010 Leo Franchi * - * * - * 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, see . * - ****************************************************************************************/ +/* === This file is part of Tomahawk Player - === + * + * Copyright 2010-2011, Christian Muehlhaeuser + * + * Tomahawk is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * Tomahawk is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with Tomahawk. If not, see . + */ #ifndef DYNAMIC_PLAYLIST_CONTROL #define DYNAMIC_PLAYLIST_CONTROL diff --git a/src/libtomahawk/playlist/dynamic/DynamicModel.cpp b/src/libtomahawk/playlist/dynamic/DynamicModel.cpp index c6e752186..3f8047c06 100644 --- a/src/libtomahawk/playlist/dynamic/DynamicModel.cpp +++ b/src/libtomahawk/playlist/dynamic/DynamicModel.cpp @@ -1,18 +1,20 @@ -/**************************************************************************************** - * Copyright (c) 2011 Leo Franchi * - * * - * 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, see . * - ****************************************************************************************/ +/* === This file is part of Tomahawk Player - === + * + * Copyright 2010-2011, Christian Muehlhaeuser + * + * Tomahawk is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * Tomahawk is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with Tomahawk. If not, see . + */ #include "playlist/dynamic/DynamicModel.h" #include "GeneratorInterface.h" diff --git a/src/libtomahawk/playlist/dynamic/DynamicModel.h b/src/libtomahawk/playlist/dynamic/DynamicModel.h index 780042dee..1b8a0fade 100644 --- a/src/libtomahawk/playlist/dynamic/DynamicModel.h +++ b/src/libtomahawk/playlist/dynamic/DynamicModel.h @@ -1,18 +1,20 @@ -/**************************************************************************************** - * Copyright (c) 2011 Leo Franchi * - * * - * 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, see . * - ****************************************************************************************/ +/* === This file is part of Tomahawk Player - === + * + * Copyright 2010-2011, Christian Muehlhaeuser + * + * Tomahawk is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * Tomahawk is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with Tomahawk. If not, see . + */ #ifndef DYNAMIC_MODEL_H #define DYNAMIC_MODEL_H diff --git a/src/libtomahawk/playlist/dynamic/DynamicPlaylist.cpp b/src/libtomahawk/playlist/dynamic/DynamicPlaylist.cpp index 42a7d81f4..554ff5f98 100644 --- a/src/libtomahawk/playlist/dynamic/DynamicPlaylist.cpp +++ b/src/libtomahawk/playlist/dynamic/DynamicPlaylist.cpp @@ -1,18 +1,20 @@ -/**************************************************************************************** - * Copyright (c) 2010 Leo Franchi * - * * - * 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, see . * - ****************************************************************************************/ +/* === This file is part of Tomahawk Player - === + * + * Copyright 2010-2011, Christian Muehlhaeuser + * + * Tomahawk is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * Tomahawk is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with Tomahawk. If not, see . + */ #include "DynamicPlaylist.h" diff --git a/src/libtomahawk/playlist/dynamic/DynamicPlaylist.h b/src/libtomahawk/playlist/dynamic/DynamicPlaylist.h index e8884cd0d..864d6a9f4 100644 --- a/src/libtomahawk/playlist/dynamic/DynamicPlaylist.h +++ b/src/libtomahawk/playlist/dynamic/DynamicPlaylist.h @@ -1,18 +1,20 @@ -/**************************************************************************************** - * Copyright (c) 2010 Leo Franchi * - * * - * 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, see . * - ****************************************************************************************/ +/* === This file is part of Tomahawk Player - === + * + * Copyright 2010-2011, Christian Muehlhaeuser + * + * Tomahawk is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * Tomahawk is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with Tomahawk. If not, see . + */ #ifndef DYNAMIC_PLAYLIST_H #define DYNAMIC_PLAYLIST_H diff --git a/src/libtomahawk/playlist/dynamic/DynamicView.cpp b/src/libtomahawk/playlist/dynamic/DynamicView.cpp index 0b48ca37f..eb70bb6b8 100644 --- a/src/libtomahawk/playlist/dynamic/DynamicView.cpp +++ b/src/libtomahawk/playlist/dynamic/DynamicView.cpp @@ -1,18 +1,20 @@ -/**************************************************************************************** - * Copyright (c) 2011 Leo Franchi * - * * - * 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, see . * - ****************************************************************************************/ +/* === This file is part of Tomahawk Player - === + * + * Copyright 2010-2011, Christian Muehlhaeuser + * + * Tomahawk is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * Tomahawk is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with Tomahawk. If not, see . + */ #include "DynamicView.h" diff --git a/src/libtomahawk/playlist/dynamic/DynamicView.h b/src/libtomahawk/playlist/dynamic/DynamicView.h index 66ecabeee..3ea371d3a 100644 --- a/src/libtomahawk/playlist/dynamic/DynamicView.h +++ b/src/libtomahawk/playlist/dynamic/DynamicView.h @@ -1,18 +1,20 @@ -/**************************************************************************************** - * Copyright (c) 2011 Leo Franchi * - * * - * 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, see . * - ****************************************************************************************/ +/* === This file is part of Tomahawk Player - === + * + * Copyright 2010-2011, Christian Muehlhaeuser + * + * Tomahawk is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * Tomahawk is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with Tomahawk. If not, see . + */ #ifndef DYNAMIC_VIEW_H #define DYNAMIC_VIEW_H diff --git a/src/libtomahawk/playlist/dynamic/GeneratorFactory.cpp b/src/libtomahawk/playlist/dynamic/GeneratorFactory.cpp index 8b33af936..dd4e44a96 100644 --- a/src/libtomahawk/playlist/dynamic/GeneratorFactory.cpp +++ b/src/libtomahawk/playlist/dynamic/GeneratorFactory.cpp @@ -1,3 +1,21 @@ +/* === This file is part of Tomahawk Player - === + * + * Copyright 2010-2011, Christian Muehlhaeuser + * + * Tomahawk is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * Tomahawk is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with Tomahawk. If not, see . + */ + #include "dynamic/GeneratorFactory.h" #include "dynamic/GeneratorInterface.h" diff --git a/src/libtomahawk/playlist/dynamic/GeneratorFactory.h b/src/libtomahawk/playlist/dynamic/GeneratorFactory.h index 5e2120bfd..5687dbb91 100644 --- a/src/libtomahawk/playlist/dynamic/GeneratorFactory.h +++ b/src/libtomahawk/playlist/dynamic/GeneratorFactory.h @@ -1,3 +1,21 @@ +/* === This file is part of Tomahawk Player - === + * + * Copyright 2010-2011, Christian Muehlhaeuser + * + * Tomahawk is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * Tomahawk is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with Tomahawk. If not, see . + */ + #ifndef GENERATOR_FACTORY_H #define GENERATOR_FACTORY_H diff --git a/src/libtomahawk/playlist/dynamic/GeneratorInterface.cpp b/src/libtomahawk/playlist/dynamic/GeneratorInterface.cpp index 6710c3af5..999d8aa45 100644 --- a/src/libtomahawk/playlist/dynamic/GeneratorInterface.cpp +++ b/src/libtomahawk/playlist/dynamic/GeneratorInterface.cpp @@ -1,18 +1,20 @@ -/**************************************************************************************** - * Copyright (c) 2010 Leo Franchi * - * * - * 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, see . * - ****************************************************************************************/ +/* === This file is part of Tomahawk Player - === + * + * Copyright 2010-2011, Christian Muehlhaeuser + * + * Tomahawk is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * Tomahawk is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with Tomahawk. If not, see . + */ #include "dynamic/GeneratorInterface.h" diff --git a/src/libtomahawk/playlist/dynamic/GeneratorInterface.h b/src/libtomahawk/playlist/dynamic/GeneratorInterface.h index ac73d5bf2..b08634104 100644 --- a/src/libtomahawk/playlist/dynamic/GeneratorInterface.h +++ b/src/libtomahawk/playlist/dynamic/GeneratorInterface.h @@ -1,18 +1,20 @@ -/**************************************************************************************** - * Copyright (c) 2010-2011 Leo Franchi * - * * - * 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, see . * - ****************************************************************************************/ +/* === This file is part of Tomahawk Player - === + * + * Copyright 2010-2011, Christian Muehlhaeuser + * + * Tomahawk is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * Tomahawk is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with Tomahawk. If not, see . + */ #ifndef GENERATOR_INTERFACE_H #define GENERATOR_INTERFACE_H diff --git a/src/libtomahawk/playlist/dynamic/echonest/EchonestControl.cpp b/src/libtomahawk/playlist/dynamic/echonest/EchonestControl.cpp index 787cf3bec..7a4c3fb47 100644 --- a/src/libtomahawk/playlist/dynamic/echonest/EchonestControl.cpp +++ b/src/libtomahawk/playlist/dynamic/echonest/EchonestControl.cpp @@ -1,18 +1,20 @@ -/**************************************************************************************** - * Copyright (c) 2010-2011 Leo Franchi * - * * - * 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, see . * - ****************************************************************************************/ +/* === This file is part of Tomahawk Player - === + * + * Copyright 2010-2011, Christian Muehlhaeuser + * + * Tomahawk is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * Tomahawk is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with Tomahawk. If not, see . + */ #include "dynamic/echonest/EchonestControl.h" diff --git a/src/libtomahawk/playlist/dynamic/echonest/EchonestControl.h b/src/libtomahawk/playlist/dynamic/echonest/EchonestControl.h index 357def005..405bed0c5 100644 --- a/src/libtomahawk/playlist/dynamic/echonest/EchonestControl.h +++ b/src/libtomahawk/playlist/dynamic/echonest/EchonestControl.h @@ -1,18 +1,20 @@ -/**************************************************************************************** - * Copyright (c) 2010 Leo Franchi * - * * - * 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, see . * - ****************************************************************************************/ +/* === This file is part of Tomahawk Player - === + * + * Copyright 2010-2011, Christian Muehlhaeuser + * + * Tomahawk is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * Tomahawk is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with Tomahawk. If not, see . + */ #ifndef ECHONEST_CONTROL_H #define ECHONEST_CONTROL_H diff --git a/src/libtomahawk/playlist/dynamic/echonest/EchonestGenerator.cpp b/src/libtomahawk/playlist/dynamic/echonest/EchonestGenerator.cpp index 32d8448ab..6f07beb1d 100644 --- a/src/libtomahawk/playlist/dynamic/echonest/EchonestGenerator.cpp +++ b/src/libtomahawk/playlist/dynamic/echonest/EchonestGenerator.cpp @@ -1,18 +1,20 @@ -/**************************************************************************************** - * Copyright (c) 2010-2011 Leo Franchi * - * * - * 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, see . * - ****************************************************************************************/ +/* === This file is part of Tomahawk Player - === + * + * Copyright 2010-2011, Christian Muehlhaeuser + * + * Tomahawk is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * Tomahawk is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with Tomahawk. If not, see . + */ #include "dynamic/echonest/EchonestGenerator.h" #include "dynamic/echonest/EchonestControl.h" diff --git a/src/libtomahawk/playlist/dynamic/echonest/EchonestGenerator.h b/src/libtomahawk/playlist/dynamic/echonest/EchonestGenerator.h index dc5bd8420..30fbfba73 100644 --- a/src/libtomahawk/playlist/dynamic/echonest/EchonestGenerator.h +++ b/src/libtomahawk/playlist/dynamic/echonest/EchonestGenerator.h @@ -1,18 +1,20 @@ -/**************************************************************************************** - * Copyright (c) 2010-2011 Leo Franchi * - * * - * 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, see . * - ****************************************************************************************/ +/* === This file is part of Tomahawk Player - === + * + * Copyright 2010-2011, Christian Muehlhaeuser + * + * Tomahawk is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * Tomahawk is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with Tomahawk. If not, see . + */ #ifndef ECHONEST_GENERATOR_H #define ECHONEST_GENERATOR_H diff --git a/src/libtomahawk/playlist/dynamic/echonest/EchonestSteerer.cpp b/src/libtomahawk/playlist/dynamic/echonest/EchonestSteerer.cpp index 6a02d4ccd..b029dcf7d 100644 --- a/src/libtomahawk/playlist/dynamic/echonest/EchonestSteerer.cpp +++ b/src/libtomahawk/playlist/dynamic/echonest/EchonestSteerer.cpp @@ -1,18 +1,20 @@ -/**************************************************************************************** - * Copyright (c) 2011 Leo Franchi * - * * - * 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, see . * - ****************************************************************************************/ +/* === This file is part of Tomahawk Player - === + * + * Copyright 2010-2011, Christian Muehlhaeuser + * + * Tomahawk is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * Tomahawk is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with Tomahawk. If not, see . + */ #include "dynamic/echonest/EchonestSteerer.h" diff --git a/src/libtomahawk/playlist/dynamic/echonest/EchonestSteerer.h b/src/libtomahawk/playlist/dynamic/echonest/EchonestSteerer.h index c1a0400bd..614b12007 100644 --- a/src/libtomahawk/playlist/dynamic/echonest/EchonestSteerer.h +++ b/src/libtomahawk/playlist/dynamic/echonest/EchonestSteerer.h @@ -1,18 +1,20 @@ -/**************************************************************************************** - * Copyright (c) 2011 Leo Franchi * - * * - * 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, see . * - ****************************************************************************************/ +/* === This file is part of Tomahawk Player - === + * + * Copyright 2010-2011, Christian Muehlhaeuser + * + * Tomahawk is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * Tomahawk is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with Tomahawk. If not, see . + */ #ifndef ECHONEST_STEERER_H #define ECHONEST_STEERER_H diff --git a/src/libtomahawk/playlist/dynamic/widgets/CollapsibleControls.cpp b/src/libtomahawk/playlist/dynamic/widgets/CollapsibleControls.cpp index c8d2585d3..1eb22dbfb 100644 --- a/src/libtomahawk/playlist/dynamic/widgets/CollapsibleControls.cpp +++ b/src/libtomahawk/playlist/dynamic/widgets/CollapsibleControls.cpp @@ -1,18 +1,20 @@ -/**************************************************************************************** - * Copyright (c) 2010-2011 Leo Franchi * - * * - * 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, see . * - ****************************************************************************************/ +/* === This file is part of Tomahawk Player - === + * + * Copyright 2010-2011, Christian Muehlhaeuser + * + * Tomahawk is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * Tomahawk is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with Tomahawk. If not, see . + */ #include "CollapsibleControls.h" diff --git a/src/libtomahawk/playlist/dynamic/widgets/CollapsibleControls.h b/src/libtomahawk/playlist/dynamic/widgets/CollapsibleControls.h index 7b1121e30..36818b2dc 100644 --- a/src/libtomahawk/playlist/dynamic/widgets/CollapsibleControls.h +++ b/src/libtomahawk/playlist/dynamic/widgets/CollapsibleControls.h @@ -1,18 +1,20 @@ -/**************************************************************************************** - * Copyright (c) 2010-2011 Leo Franchi * - * * - * 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, see . * - ****************************************************************************************/ +/* === This file is part of Tomahawk Player - === + * + * Copyright 2010-2011, Christian Muehlhaeuser + * + * Tomahawk is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * Tomahawk is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with Tomahawk. If not, see . + */ #ifndef COLLAPSIBLE_CONTROLS_H #define COLLAPSIBLE_CONTROLS_H diff --git a/src/libtomahawk/playlist/dynamic/widgets/DynamicControlList.cpp b/src/libtomahawk/playlist/dynamic/widgets/DynamicControlList.cpp index d8bc65e93..6dcc65ab6 100644 --- a/src/libtomahawk/playlist/dynamic/widgets/DynamicControlList.cpp +++ b/src/libtomahawk/playlist/dynamic/widgets/DynamicControlList.cpp @@ -1,18 +1,20 @@ -/**************************************************************************************** - * Copyright (c) 2010 Leo Franchi * - * * - * 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, see . * - ****************************************************************************************/ +/* === This file is part of Tomahawk Player - === + * + * Copyright 2010-2011, Christian Muehlhaeuser + * + * Tomahawk is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * Tomahawk is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with Tomahawk. If not, see . + */ #include "DynamicControlList.h" diff --git a/src/libtomahawk/playlist/dynamic/widgets/DynamicControlList.h b/src/libtomahawk/playlist/dynamic/widgets/DynamicControlList.h index 4b00f5e65..985a1ccba 100644 --- a/src/libtomahawk/playlist/dynamic/widgets/DynamicControlList.h +++ b/src/libtomahawk/playlist/dynamic/widgets/DynamicControlList.h @@ -1,18 +1,20 @@ -/**************************************************************************************** - * Copyright (c) 2010 Leo Franchi * - * * - * 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, see . * - ****************************************************************************************/ +/* === This file is part of Tomahawk Player - === + * + * Copyright 2010-2011, Christian Muehlhaeuser + * + * Tomahawk is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * Tomahawk is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with Tomahawk. If not, see . + */ #ifndef DYNAMIC_CONTROL_LIST_H #define DYNAMIC_CONTROL_LIST_H diff --git a/src/libtomahawk/playlist/dynamic/widgets/DynamicControlWrapper.cpp b/src/libtomahawk/playlist/dynamic/widgets/DynamicControlWrapper.cpp index ad88959aa..ba3b482b7 100644 --- a/src/libtomahawk/playlist/dynamic/widgets/DynamicControlWrapper.cpp +++ b/src/libtomahawk/playlist/dynamic/widgets/DynamicControlWrapper.cpp @@ -1,18 +1,20 @@ -/**************************************************************************************** - * Copyright (c) 2010-2011 Leo Franchi * - * * - * 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, see . * - ****************************************************************************************/ +/* === This file is part of Tomahawk Player - === + * + * Copyright 2010-2011, Christian Muehlhaeuser + * + * Tomahawk is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * Tomahawk is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with Tomahawk. If not, see . + */ #include "DynamicControlWrapper.h" diff --git a/src/libtomahawk/playlist/dynamic/widgets/DynamicControlWrapper.h b/src/libtomahawk/playlist/dynamic/widgets/DynamicControlWrapper.h index c9c769c21..b0c3df109 100644 --- a/src/libtomahawk/playlist/dynamic/widgets/DynamicControlWrapper.h +++ b/src/libtomahawk/playlist/dynamic/widgets/DynamicControlWrapper.h @@ -1,18 +1,20 @@ -/**************************************************************************************** - * Copyright (c) 2010-2011 Leo Franchi * - * * - * 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, see . * - ****************************************************************************************/ +/* === This file is part of Tomahawk Player - === + * + * Copyright 2010-2011, Christian Muehlhaeuser + * + * Tomahawk is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * Tomahawk is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with Tomahawk. If not, see . + */ #ifndef DYNAMIC_CONTROL_WRAPPER_H #define DYNAMIC_CONTROL_WRAPPER_H diff --git a/src/libtomahawk/playlist/dynamic/widgets/DynamicSetupWidget.cpp b/src/libtomahawk/playlist/dynamic/widgets/DynamicSetupWidget.cpp index a4fed6a6d..062fa6485 100644 --- a/src/libtomahawk/playlist/dynamic/widgets/DynamicSetupWidget.cpp +++ b/src/libtomahawk/playlist/dynamic/widgets/DynamicSetupWidget.cpp @@ -1,18 +1,20 @@ -/**************************************************************************************** - * Copyright (c) 2010-2011 Leo Franchi * - * * - * 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, see . * - ****************************************************************************************/ +/* === This file is part of Tomahawk Player - === + * + * Copyright 2010-2011, Christian Muehlhaeuser + * + * Tomahawk is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * Tomahawk is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with Tomahawk. If not, see . + */ #include "DynamicSetupWidget.h" diff --git a/src/libtomahawk/playlist/dynamic/widgets/DynamicSetupWidget.h b/src/libtomahawk/playlist/dynamic/widgets/DynamicSetupWidget.h index 7bf7cf1c6..78568b19e 100644 --- a/src/libtomahawk/playlist/dynamic/widgets/DynamicSetupWidget.h +++ b/src/libtomahawk/playlist/dynamic/widgets/DynamicSetupWidget.h @@ -1,18 +1,20 @@ -/**************************************************************************************** - * Copyright (c) 2010-2011 Leo Franchi * - * * - * 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, see . * - ****************************************************************************************/ +/* === This file is part of Tomahawk Player - === + * + * Copyright 2010-2011, Christian Muehlhaeuser + * + * Tomahawk is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * Tomahawk is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with Tomahawk. If not, see . + */ #ifndef DYNAMIC_SETUP_WIDGET_H #define DYNAMIC_SETUP_WIDGET_H diff --git a/src/libtomahawk/playlist/dynamic/widgets/DynamicWidget.cpp b/src/libtomahawk/playlist/dynamic/widgets/DynamicWidget.cpp index 5ec9d387d..f7e0dd855 100644 --- a/src/libtomahawk/playlist/dynamic/widgets/DynamicWidget.cpp +++ b/src/libtomahawk/playlist/dynamic/widgets/DynamicWidget.cpp @@ -1,18 +1,20 @@ -/**************************************************************************************** - * Copyright (c) 2010 Leo Franchi * - * * - * 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, see . * - ****************************************************************************************/ +/* === This file is part of Tomahawk Player - === + * + * Copyright 2010-2011, Christian Muehlhaeuser + * + * Tomahawk is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * Tomahawk is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with Tomahawk. If not, see . + */ #include "DynamicWidget.h" diff --git a/src/libtomahawk/playlist/dynamic/widgets/DynamicWidget.h b/src/libtomahawk/playlist/dynamic/widgets/DynamicWidget.h index 2fca33856..7ba3e5ed4 100644 --- a/src/libtomahawk/playlist/dynamic/widgets/DynamicWidget.h +++ b/src/libtomahawk/playlist/dynamic/widgets/DynamicWidget.h @@ -1,18 +1,20 @@ -/**************************************************************************************** - * Copyright (c) 2010 Leo Franchi * - * * - * 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, see . * - ****************************************************************************************/ +/* === This file is part of Tomahawk Player - === + * + * Copyright 2010-2011, Christian Muehlhaeuser + * + * Tomahawk is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * Tomahawk is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with Tomahawk. If not, see . + */ #ifndef DYNAMIC_WIDGET_H #define DYNAMIC_WIDGET_H diff --git a/src/libtomahawk/playlist/dynamic/widgets/LoadingSpinner.cpp b/src/libtomahawk/playlist/dynamic/widgets/LoadingSpinner.cpp index 8507572cb..1e4b75b05 100644 --- a/src/libtomahawk/playlist/dynamic/widgets/LoadingSpinner.cpp +++ b/src/libtomahawk/playlist/dynamic/widgets/LoadingSpinner.cpp @@ -1,18 +1,20 @@ -/**************************************************************************************** - * Copyright (c) 2011 Leo Franchi * - * * - * 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, see . * - ****************************************************************************************/ +/* === This file is part of Tomahawk Player - === + * + * Copyright 2010-2011, Christian Muehlhaeuser + * + * Tomahawk is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * Tomahawk is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with Tomahawk. If not, see . + */ #include "LoadingSpinner.h" diff --git a/src/libtomahawk/playlist/dynamic/widgets/LoadingSpinner.h b/src/libtomahawk/playlist/dynamic/widgets/LoadingSpinner.h index 9c4ade008..0dde3520d 100644 --- a/src/libtomahawk/playlist/dynamic/widgets/LoadingSpinner.h +++ b/src/libtomahawk/playlist/dynamic/widgets/LoadingSpinner.h @@ -1,18 +1,20 @@ -/**************************************************************************************** - * Copyright (c) 2011 Leo Franchi * - * * - * 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, see . * - ****************************************************************************************/ +/* === This file is part of Tomahawk Player - === + * + * Copyright 2010-2011, Christian Muehlhaeuser + * + * Tomahawk is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * Tomahawk is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with Tomahawk. If not, see . + */ #ifndef LOADING_SPINNER_H #define LOADING_SPINNER_H diff --git a/src/libtomahawk/playlist/dynamic/widgets/MiscControlWidgets.cpp b/src/libtomahawk/playlist/dynamic/widgets/MiscControlWidgets.cpp index 5357130e6..7ac128829 100644 --- a/src/libtomahawk/playlist/dynamic/widgets/MiscControlWidgets.cpp +++ b/src/libtomahawk/playlist/dynamic/widgets/MiscControlWidgets.cpp @@ -1,18 +1,20 @@ -/**************************************************************************************** - * Copyright (c) 2011 Leo Franchi * - * * - * 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, see . * - ****************************************************************************************/ +/* === This file is part of Tomahawk Player - === + * + * Copyright 2010-2011, Christian Muehlhaeuser + * + * Tomahawk is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * Tomahawk is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with Tomahawk. If not, see . + */ #include "MiscControlWidgets.h" diff --git a/src/libtomahawk/playlist/dynamic/widgets/MiscControlWidgets.h b/src/libtomahawk/playlist/dynamic/widgets/MiscControlWidgets.h index 1a6c383eb..245868264 100644 --- a/src/libtomahawk/playlist/dynamic/widgets/MiscControlWidgets.h +++ b/src/libtomahawk/playlist/dynamic/widgets/MiscControlWidgets.h @@ -1,18 +1,20 @@ -/**************************************************************************************** - * Copyright (c) 2011 Leo Franchi * - * * - * 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, see . * - ****************************************************************************************/ +/* === This file is part of Tomahawk Player - === + * + * Copyright 2010-2011, Christian Muehlhaeuser + * + * Tomahawk is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * Tomahawk is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with Tomahawk. If not, see . + */ #ifndef MISC_CONTROL_WIDGETS_H #define MISC_CONTROL_WIDGETS_H diff --git a/src/libtomahawk/playlist/dynamic/widgets/ReadOrWriteWidget.cpp b/src/libtomahawk/playlist/dynamic/widgets/ReadOrWriteWidget.cpp index 2c74050ce..2e3da1aad 100644 --- a/src/libtomahawk/playlist/dynamic/widgets/ReadOrWriteWidget.cpp +++ b/src/libtomahawk/playlist/dynamic/widgets/ReadOrWriteWidget.cpp @@ -1,18 +1,20 @@ -/**************************************************************************************** - * Copyright (c) 2011 Leo Franchi * - * * - * 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, see . * - ****************************************************************************************/ +/* === This file is part of Tomahawk Player - === + * + * Copyright 2010-2011, Christian Muehlhaeuser + * + * Tomahawk is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * Tomahawk is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with Tomahawk. If not, see . + */ #include "ReadOrWriteWidget.h" diff --git a/src/libtomahawk/playlist/dynamic/widgets/ReadOrWriteWidget.h b/src/libtomahawk/playlist/dynamic/widgets/ReadOrWriteWidget.h index 25d5a9da8..5abbbca7a 100644 --- a/src/libtomahawk/playlist/dynamic/widgets/ReadOrWriteWidget.h +++ b/src/libtomahawk/playlist/dynamic/widgets/ReadOrWriteWidget.h @@ -1,18 +1,20 @@ -/**************************************************************************************** - * Copyright (c) 2011 Leo Franchi * - * * - * 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, see . * - ****************************************************************************************/ +/* === This file is part of Tomahawk Player - === + * + * Copyright 2010-2011, Christian Muehlhaeuser + * + * Tomahawk is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * Tomahawk is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with Tomahawk. If not, see . + */ #ifndef READ_OR_WRITE_WIDGET_H #define READ_OR_WRITE_WIDGET_H diff --git a/src/libtomahawk/playlist/infobar/infobar.cpp b/src/libtomahawk/playlist/infobar/infobar.cpp index cb442b6ac..488fbf4b4 100644 --- a/src/libtomahawk/playlist/infobar/infobar.cpp +++ b/src/libtomahawk/playlist/infobar/infobar.cpp @@ -1,3 +1,21 @@ +/* === This file is part of Tomahawk Player - === + * + * Copyright 2010-2011, Christian Muehlhaeuser + * + * Tomahawk is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * Tomahawk is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with Tomahawk. If not, see . + */ + #include "infobar.h" #include "ui_infobar.h" diff --git a/src/libtomahawk/playlist/infobar/infobar.h b/src/libtomahawk/playlist/infobar/infobar.h index c46a1201e..4bd677c78 100644 --- a/src/libtomahawk/playlist/infobar/infobar.h +++ b/src/libtomahawk/playlist/infobar/infobar.h @@ -1,3 +1,21 @@ +/* === This file is part of Tomahawk Player - === + * + * Copyright 2010-2011, Christian Muehlhaeuser + * + * Tomahawk is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * Tomahawk is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with Tomahawk. If not, see . + */ + #ifndef INFOBAR_H #define INFOBAR_H diff --git a/src/libtomahawk/playlist/playlistitemdelegate.cpp b/src/libtomahawk/playlist/playlistitemdelegate.cpp index 65476c516..d014691c5 100644 --- a/src/libtomahawk/playlist/playlistitemdelegate.cpp +++ b/src/libtomahawk/playlist/playlistitemdelegate.cpp @@ -1,3 +1,21 @@ +/* === This file is part of Tomahawk Player - === + * + * Copyright 2010-2011, Christian Muehlhaeuser + * + * Tomahawk is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * Tomahawk is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with Tomahawk. If not, see . + */ + #include "playlistitemdelegate.h" #include diff --git a/src/libtomahawk/playlist/playlistitemdelegate.h b/src/libtomahawk/playlist/playlistitemdelegate.h index 858c80a66..5ba66bbd9 100644 --- a/src/libtomahawk/playlist/playlistitemdelegate.h +++ b/src/libtomahawk/playlist/playlistitemdelegate.h @@ -1,3 +1,21 @@ +/* === This file is part of Tomahawk Player - === + * + * Copyright 2010-2011, Christian Muehlhaeuser + * + * Tomahawk is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * Tomahawk is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with Tomahawk. If not, see . + */ + #ifndef PLAYLISTITEMDELEGATE_H #define PLAYLISTITEMDELEGATE_H diff --git a/src/libtomahawk/playlist/playlistmanager.cpp b/src/libtomahawk/playlist/playlistmanager.cpp index 8e43d9c49..ce9a48af3 100644 --- a/src/libtomahawk/playlist/playlistmanager.cpp +++ b/src/libtomahawk/playlist/playlistmanager.cpp @@ -1,3 +1,21 @@ +/* === This file is part of Tomahawk Player - === + * + * Copyright 2010-2011, Christian Muehlhaeuser + * + * Tomahawk is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * Tomahawk is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with Tomahawk. If not, see . + */ + #include "playlistmanager.h" #include diff --git a/src/libtomahawk/playlist/playlistmanager.h b/src/libtomahawk/playlist/playlistmanager.h index 4cab2f398..278037e98 100644 --- a/src/libtomahawk/playlist/playlistmanager.h +++ b/src/libtomahawk/playlist/playlistmanager.h @@ -1,3 +1,21 @@ +/* === This file is part of Tomahawk Player - === + * + * Copyright 2010-2011, Christian Muehlhaeuser + * + * Tomahawk is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * Tomahawk is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with Tomahawk. If not, see . + */ + #ifndef PLAYLISTMANAGER_H #define PLAYLISTMANAGER_H diff --git a/src/libtomahawk/playlist/playlistmodel.cpp b/src/libtomahawk/playlist/playlistmodel.cpp index 2a72cb683..89fd32251 100644 --- a/src/libtomahawk/playlist/playlistmodel.cpp +++ b/src/libtomahawk/playlist/playlistmodel.cpp @@ -1,3 +1,21 @@ +/* === This file is part of Tomahawk Player - === + * + * Copyright 2010-2011, Christian Muehlhaeuser + * + * Tomahawk is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * Tomahawk is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with Tomahawk. If not, see . + */ + #include "playlistmodel.h" #include diff --git a/src/libtomahawk/playlist/playlistmodel.h b/src/libtomahawk/playlist/playlistmodel.h index ad8be01d5..b7657b02f 100644 --- a/src/libtomahawk/playlist/playlistmodel.h +++ b/src/libtomahawk/playlist/playlistmodel.h @@ -1,3 +1,21 @@ +/* === This file is part of Tomahawk Player - === + * + * Copyright 2010-2011, Christian Muehlhaeuser + * + * Tomahawk is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * Tomahawk is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with Tomahawk. If not, see . + */ + #ifndef PLAYLISTMODEL_H #define PLAYLISTMODEL_H diff --git a/src/libtomahawk/playlist/playlistproxymodel.cpp b/src/libtomahawk/playlist/playlistproxymodel.cpp index d536d9fce..272721234 100644 --- a/src/libtomahawk/playlist/playlistproxymodel.cpp +++ b/src/libtomahawk/playlist/playlistproxymodel.cpp @@ -1,3 +1,21 @@ +/* === This file is part of Tomahawk Player - === + * + * Copyright 2010-2011, Christian Muehlhaeuser + * + * Tomahawk is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * Tomahawk is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with Tomahawk. If not, see . + */ + #include "playlistproxymodel.h" diff --git a/src/libtomahawk/playlist/playlistproxymodel.h b/src/libtomahawk/playlist/playlistproxymodel.h index 9149c74b8..f0cddc616 100644 --- a/src/libtomahawk/playlist/playlistproxymodel.h +++ b/src/libtomahawk/playlist/playlistproxymodel.h @@ -1,3 +1,21 @@ +/* === This file is part of Tomahawk Player - === + * + * Copyright 2010-2011, Christian Muehlhaeuser + * + * Tomahawk is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * Tomahawk is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with Tomahawk. If not, see . + */ + #ifndef PLAYLISTPROXYMODEL_H #define PLAYLISTPROXYMODEL_H diff --git a/src/libtomahawk/playlist/playlistview.cpp b/src/libtomahawk/playlist/playlistview.cpp index 18abb0d01..c7fbd5599 100644 --- a/src/libtomahawk/playlist/playlistview.cpp +++ b/src/libtomahawk/playlist/playlistview.cpp @@ -1,3 +1,21 @@ +/* === This file is part of Tomahawk Player - === + * + * Copyright 2010-2011, Christian Muehlhaeuser + * + * Tomahawk is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * Tomahawk is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with Tomahawk. If not, see . + */ + #include "playlistview.h" #include diff --git a/src/libtomahawk/playlist/playlistview.h b/src/libtomahawk/playlist/playlistview.h index 26a543316..04c3fef05 100644 --- a/src/libtomahawk/playlist/playlistview.h +++ b/src/libtomahawk/playlist/playlistview.h @@ -1,3 +1,21 @@ +/* === This file is part of Tomahawk Player - === + * + * Copyright 2010-2011, Christian Muehlhaeuser + * + * Tomahawk is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * Tomahawk is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with Tomahawk. If not, see . + */ + #ifndef PLAYLISTVIEW_H #define PLAYLISTVIEW_H diff --git a/src/libtomahawk/playlist/plitem.cpp b/src/libtomahawk/playlist/plitem.cpp index b9fcdf916..853df520a 100644 --- a/src/libtomahawk/playlist/plitem.cpp +++ b/src/libtomahawk/playlist/plitem.cpp @@ -1,3 +1,21 @@ +/* === This file is part of Tomahawk Player - === + * + * Copyright 2010-2011, Christian Muehlhaeuser + * + * Tomahawk is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * Tomahawk is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with Tomahawk. If not, see . + */ + #include "plitem.h" #include "utils/tomahawkutils.h" diff --git a/src/libtomahawk/playlist/plitem.h b/src/libtomahawk/playlist/plitem.h index 4796e54c1..3d28957fc 100644 --- a/src/libtomahawk/playlist/plitem.h +++ b/src/libtomahawk/playlist/plitem.h @@ -1,3 +1,21 @@ +/* === This file is part of Tomahawk Player - === + * + * Copyright 2010-2011, Christian Muehlhaeuser + * + * Tomahawk is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * Tomahawk is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with Tomahawk. If not, see . + */ + #ifndef PLITEM_H #define PLITEM_H diff --git a/src/libtomahawk/playlist/queueproxymodel.cpp b/src/libtomahawk/playlist/queueproxymodel.cpp index 730cf80c8..88950089e 100644 --- a/src/libtomahawk/playlist/queueproxymodel.cpp +++ b/src/libtomahawk/playlist/queueproxymodel.cpp @@ -1,3 +1,21 @@ +/* === This file is part of Tomahawk Player - === + * + * Copyright 2010-2011, Christian Muehlhaeuser + * + * Tomahawk is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * Tomahawk is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with Tomahawk. If not, see . + */ + #include "queueproxymodel.h" #include diff --git a/src/libtomahawk/playlist/queueproxymodel.h b/src/libtomahawk/playlist/queueproxymodel.h index d74229e75..fd240dfb8 100644 --- a/src/libtomahawk/playlist/queueproxymodel.h +++ b/src/libtomahawk/playlist/queueproxymodel.h @@ -1,3 +1,21 @@ +/* === This file is part of Tomahawk Player - === + * + * Copyright 2010-2011, Christian Muehlhaeuser + * + * Tomahawk is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * Tomahawk is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with Tomahawk. If not, see . + */ + #ifndef QUEUEPROXYMODEL_H #define QUEUEPROXYMODEL_H diff --git a/src/libtomahawk/playlist/queueview.cpp b/src/libtomahawk/playlist/queueview.cpp index 17c2b5f6a..1d060db73 100644 --- a/src/libtomahawk/playlist/queueview.cpp +++ b/src/libtomahawk/playlist/queueview.cpp @@ -1,3 +1,21 @@ +/* === This file is part of Tomahawk Player - === + * + * Copyright 2010-2011, Christian Muehlhaeuser + * + * Tomahawk is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * Tomahawk is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with Tomahawk. If not, see . + */ + #include "queueview.h" #include diff --git a/src/libtomahawk/playlist/queueview.h b/src/libtomahawk/playlist/queueview.h index f43b717fc..f617831f9 100644 --- a/src/libtomahawk/playlist/queueview.h +++ b/src/libtomahawk/playlist/queueview.h @@ -1,3 +1,21 @@ +/* === This file is part of Tomahawk Player - === + * + * Copyright 2010-2011, Christian Muehlhaeuser + * + * Tomahawk is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * Tomahawk is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with Tomahawk. If not, see . + */ + #ifndef QUEUEVIEW_H #define QUEUEVIEW_H diff --git a/src/libtomahawk/playlist/topbar/topbar.cpp b/src/libtomahawk/playlist/topbar/topbar.cpp index 3aa584bf5..5018092f3 100644 --- a/src/libtomahawk/playlist/topbar/topbar.cpp +++ b/src/libtomahawk/playlist/topbar/topbar.cpp @@ -1,3 +1,21 @@ +/* === This file is part of Tomahawk Player - === + * + * Copyright 2010-2011, Christian Muehlhaeuser + * + * Tomahawk is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * Tomahawk is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with Tomahawk. If not, see . + */ + #include "topbar.h" #include "ui_topbar.h" diff --git a/src/libtomahawk/playlist/topbar/topbar.h b/src/libtomahawk/playlist/topbar/topbar.h index 439768029..c29b8b959 100644 --- a/src/libtomahawk/playlist/topbar/topbar.h +++ b/src/libtomahawk/playlist/topbar/topbar.h @@ -1,3 +1,21 @@ +/* === This file is part of Tomahawk Player - === + * + * Copyright 2010-2011, Christian Muehlhaeuser + * + * Tomahawk is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * Tomahawk is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with Tomahawk. If not, see . + */ + #ifndef TOPBAR_H #define TOPBAR_H diff --git a/src/libtomahawk/playlist/trackheader.cpp b/src/libtomahawk/playlist/trackheader.cpp index 42080b98b..750f207d2 100644 --- a/src/libtomahawk/playlist/trackheader.cpp +++ b/src/libtomahawk/playlist/trackheader.cpp @@ -1,3 +1,21 @@ +/* === This file is part of Tomahawk Player - === + * + * Copyright 2010-2011, Christian Muehlhaeuser + * + * Tomahawk is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * Tomahawk is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with Tomahawk. If not, see . + */ + #include "trackheader.h" #include diff --git a/src/libtomahawk/playlist/trackheader.h b/src/libtomahawk/playlist/trackheader.h index 21b9e3243..2d62a1170 100644 --- a/src/libtomahawk/playlist/trackheader.h +++ b/src/libtomahawk/playlist/trackheader.h @@ -1,3 +1,21 @@ +/* === This file is part of Tomahawk Player - === + * + * Copyright 2010-2011, Christian Muehlhaeuser + * + * Tomahawk is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * Tomahawk is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with Tomahawk. If not, see . + */ + #ifndef TRACKHEADER_H #define TRACKHEADER_H diff --git a/src/libtomahawk/playlist/trackmodel.cpp b/src/libtomahawk/playlist/trackmodel.cpp index f071ea92d..eceadd401 100644 --- a/src/libtomahawk/playlist/trackmodel.cpp +++ b/src/libtomahawk/playlist/trackmodel.cpp @@ -1,3 +1,21 @@ +/* === This file is part of Tomahawk Player - === + * + * Copyright 2010-2011, Christian Muehlhaeuser + * + * Tomahawk is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * Tomahawk is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with Tomahawk. If not, see . + */ + #include "trackmodel.h" #include diff --git a/src/libtomahawk/playlist/trackmodel.h b/src/libtomahawk/playlist/trackmodel.h index 45a3533ff..d9cec03d8 100644 --- a/src/libtomahawk/playlist/trackmodel.h +++ b/src/libtomahawk/playlist/trackmodel.h @@ -1,3 +1,21 @@ +/* === This file is part of Tomahawk Player - === + * + * Copyright 2010-2011, Christian Muehlhaeuser + * + * Tomahawk is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * Tomahawk is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with Tomahawk. If not, see . + */ + #ifndef TRACKMODEL_H #define TRACKMODEL_H diff --git a/src/libtomahawk/playlist/trackproxymodel.cpp b/src/libtomahawk/playlist/trackproxymodel.cpp index 46012bfb5..edda2bf36 100644 --- a/src/libtomahawk/playlist/trackproxymodel.cpp +++ b/src/libtomahawk/playlist/trackproxymodel.cpp @@ -1,3 +1,21 @@ +/* === This file is part of Tomahawk Player - === + * + * Copyright 2010-2011, Christian Muehlhaeuser + * + * Tomahawk is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * Tomahawk is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with Tomahawk. If not, see . + */ + #include "trackproxymodel.h" #include diff --git a/src/libtomahawk/playlist/trackproxymodel.h b/src/libtomahawk/playlist/trackproxymodel.h index 5ced4b802..4452ef20d 100644 --- a/src/libtomahawk/playlist/trackproxymodel.h +++ b/src/libtomahawk/playlist/trackproxymodel.h @@ -1,3 +1,21 @@ +/* === This file is part of Tomahawk Player - === + * + * Copyright 2010-2011, Christian Muehlhaeuser + * + * Tomahawk is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * Tomahawk is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with Tomahawk. If not, see . + */ + #ifndef TRACKPROXYMODEL_H #define TRACKPROXYMODEL_H diff --git a/src/libtomahawk/playlist/trackview.cpp b/src/libtomahawk/playlist/trackview.cpp index 696a9be4a..76c685833 100644 --- a/src/libtomahawk/playlist/trackview.cpp +++ b/src/libtomahawk/playlist/trackview.cpp @@ -1,3 +1,21 @@ +/* === This file is part of Tomahawk Player - === + * + * Copyright 2010-2011, Christian Muehlhaeuser + * + * Tomahawk is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * Tomahawk is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with Tomahawk. If not, see . + */ + #include "trackview.h" #include diff --git a/src/libtomahawk/playlist/trackview.h b/src/libtomahawk/playlist/trackview.h index 7702fc6aa..7777e24a3 100644 --- a/src/libtomahawk/playlist/trackview.h +++ b/src/libtomahawk/playlist/trackview.h @@ -1,3 +1,21 @@ +/* === This file is part of Tomahawk Player - === + * + * Copyright 2010-2011, Christian Muehlhaeuser + * + * Tomahawk is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * Tomahawk is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with Tomahawk. If not, see . + */ + #ifndef TRACKVIEW_H #define TRACKVIEW_H diff --git a/src/libtomahawk/playlistinterface.h b/src/libtomahawk/playlistinterface.h index f8a56fa32..2743a7d13 100644 --- a/src/libtomahawk/playlistinterface.h +++ b/src/libtomahawk/playlistinterface.h @@ -1,3 +1,21 @@ +/* === This file is part of Tomahawk Player - === + * + * Copyright 2010-2011, Christian Muehlhaeuser + * + * Tomahawk is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * Tomahawk is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with Tomahawk. If not, see . + */ + #ifndef PLAYLISTINTERFACE_H #define PLAYLISTINTERFACE_H diff --git a/src/libtomahawk/pluginapi.cpp b/src/libtomahawk/pluginapi.cpp index 4bf86d317..d2c2de84b 100644 --- a/src/libtomahawk/pluginapi.cpp +++ b/src/libtomahawk/pluginapi.cpp @@ -1,3 +1,21 @@ +/* === This file is part of Tomahawk Player - === + * + * Copyright 2010-2011, Christian Muehlhaeuser + * + * Tomahawk is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * Tomahawk is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with Tomahawk. If not, see . + */ + #include "pluginapi.h" #include "pipeline.h" diff --git a/src/libtomahawk/pluginapi.h b/src/libtomahawk/pluginapi.h index b824a3204..a895d388c 100644 --- a/src/libtomahawk/pluginapi.h +++ b/src/libtomahawk/pluginapi.h @@ -1,3 +1,21 @@ +/* === This file is part of Tomahawk Player - === + * + * Copyright 2010-2011, Christian Muehlhaeuser + * + * Tomahawk is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * Tomahawk is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with Tomahawk. If not, see . + */ + #ifndef PLUGINAPI_H #define PLUGINAPI_H diff --git a/src/libtomahawk/query.cpp b/src/libtomahawk/query.cpp index 55ef56959..159662be1 100644 --- a/src/libtomahawk/query.cpp +++ b/src/libtomahawk/query.cpp @@ -1,3 +1,21 @@ +/* === This file is part of Tomahawk Player - === + * + * Copyright 2010-2011, Christian Muehlhaeuser + * + * Tomahawk is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * Tomahawk is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with Tomahawk. If not, see . + */ + #include "query.h" #include "collection.h" diff --git a/src/libtomahawk/query.h b/src/libtomahawk/query.h index d82972f10..9f7d53fc4 100644 --- a/src/libtomahawk/query.h +++ b/src/libtomahawk/query.h @@ -1,3 +1,21 @@ +/* === This file is part of Tomahawk Player - === + * + * Copyright 2010-2011, Christian Muehlhaeuser + * + * Tomahawk is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * Tomahawk is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with Tomahawk. If not, see . + */ + #ifndef QUERY_H #define QUERY_H diff --git a/src/libtomahawk/resolver.h b/src/libtomahawk/resolver.h index ce20baa34..c6ff57639 100644 --- a/src/libtomahawk/resolver.h +++ b/src/libtomahawk/resolver.h @@ -1,3 +1,21 @@ +/* === This file is part of Tomahawk Player - === + * + * Copyright 2010-2011, Christian Muehlhaeuser + * + * Tomahawk is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * Tomahawk is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with Tomahawk. If not, see . + */ + #ifndef RESOLVER_H #define RESOLVER_H diff --git a/src/libtomahawk/result.cpp b/src/libtomahawk/result.cpp index 6cdbfb510..0b5f1b989 100644 --- a/src/libtomahawk/result.cpp +++ b/src/libtomahawk/result.cpp @@ -1,3 +1,21 @@ +/* === This file is part of Tomahawk Player - === + * + * Copyright 2010-2011, Christian Muehlhaeuser + * + * Tomahawk is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * Tomahawk is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with Tomahawk. If not, see . + */ + #include "result.h" #include "album.h" diff --git a/src/libtomahawk/result.h b/src/libtomahawk/result.h index 4b62f662f..aff9604ba 100644 --- a/src/libtomahawk/result.h +++ b/src/libtomahawk/result.h @@ -1,3 +1,21 @@ +/* === This file is part of Tomahawk Player - === + * + * Copyright 2010-2011, Christian Muehlhaeuser + * + * Tomahawk is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * Tomahawk is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with Tomahawk. If not, see . + */ + #ifndef RESULT_H #define RESULT_H diff --git a/src/libtomahawk/sip/SipPlugin.cpp b/src/libtomahawk/sip/SipPlugin.cpp index 9e8a799d9..4af179694 100644 --- a/src/libtomahawk/sip/SipPlugin.cpp +++ b/src/libtomahawk/sip/SipPlugin.cpp @@ -1,11 +1,31 @@ +/* === This file is part of Tomahawk Player - === + * + * Copyright 2010-2011, Christian Muehlhaeuser + * + * Tomahawk is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * Tomahawk is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with Tomahawk. If not, see . + */ + #include + QMenu* SipPlugin::menu() { return 0; } + QWidget* SipPlugin::configWidget() { diff --git a/src/libtomahawk/sip/SipPlugin.h b/src/libtomahawk/sip/SipPlugin.h index 0fa150ba6..4c2b17f1e 100644 --- a/src/libtomahawk/sip/SipPlugin.h +++ b/src/libtomahawk/sip/SipPlugin.h @@ -1,3 +1,21 @@ +/* === This file is part of Tomahawk Player - === + * + * Copyright 2010-2011, Christian Muehlhaeuser + * + * Tomahawk is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * Tomahawk is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with Tomahawk. If not, see . + */ + #ifndef SIPPLUGIN_H #define SIPPLUGIN_H diff --git a/src/libtomahawk/source.cpp b/src/libtomahawk/source.cpp index 2f959141c..59ad94688 100644 --- a/src/libtomahawk/source.cpp +++ b/src/libtomahawk/source.cpp @@ -1,3 +1,21 @@ +/* === This file is part of Tomahawk Player - === + * + * Copyright 2010-2011, Christian Muehlhaeuser + * + * Tomahawk is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * Tomahawk is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with Tomahawk. If not, see . + */ + #include "source.h" #include "collection.h" diff --git a/src/libtomahawk/source.h b/src/libtomahawk/source.h index f1719cfce..998804b70 100644 --- a/src/libtomahawk/source.h +++ b/src/libtomahawk/source.h @@ -1,3 +1,21 @@ +/* === This file is part of Tomahawk Player - === + * + * Copyright 2010-2011, Christian Muehlhaeuser + * + * Tomahawk is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * Tomahawk is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with Tomahawk. If not, see . + */ + #ifndef SOURCE_H #define SOURCE_H diff --git a/src/libtomahawk/sourcelist.cpp b/src/libtomahawk/sourcelist.cpp index a97fbd392..917a96c00 100644 --- a/src/libtomahawk/sourcelist.cpp +++ b/src/libtomahawk/sourcelist.cpp @@ -1,3 +1,21 @@ +/* === This file is part of Tomahawk Player - === + * + * Copyright 2010-2011, Christian Muehlhaeuser + * + * Tomahawk is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * Tomahawk is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with Tomahawk. If not, see . + */ + #include "sourcelist.h" #include diff --git a/src/libtomahawk/sourcelist.h b/src/libtomahawk/sourcelist.h index 8b1861476..3ec7304ef 100644 --- a/src/libtomahawk/sourcelist.h +++ b/src/libtomahawk/sourcelist.h @@ -1,3 +1,21 @@ +/* === This file is part of Tomahawk Player - === + * + * Copyright 2010-2011, Christian Muehlhaeuser + * + * Tomahawk is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * Tomahawk is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with Tomahawk. If not, see . + */ + #ifndef SOURCELIST_H #define SOURCELIST_H diff --git a/src/libtomahawk/tomahawksettings.cpp b/src/libtomahawk/tomahawksettings.cpp index 78f9fb569..34609d5ec 100644 --- a/src/libtomahawk/tomahawksettings.cpp +++ b/src/libtomahawk/tomahawksettings.cpp @@ -1,3 +1,21 @@ +/* === This file is part of Tomahawk Player - === + * + * Copyright 2010-2011, Christian Muehlhaeuser + * + * Tomahawk is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * Tomahawk is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with Tomahawk. If not, see . + */ + #include "tomahawksettings.h" #ifndef TOMAHAWK_HEADLESS diff --git a/src/libtomahawk/tomahawksettings.h b/src/libtomahawk/tomahawksettings.h index 5f7946df6..cb7fa9bef 100644 --- a/src/libtomahawk/tomahawksettings.h +++ b/src/libtomahawk/tomahawksettings.h @@ -1,3 +1,21 @@ +/* === This file is part of Tomahawk Player - === + * + * Copyright 2010-2011, Christian Muehlhaeuser + * + * Tomahawk is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * Tomahawk is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with Tomahawk. If not, see . + */ + #ifndef TOMAHAWK_SETTINGS_H #define TOMAHAWK_SETTINGS_h diff --git a/src/libtomahawk/track.h b/src/libtomahawk/track.h index 55ce10404..c379c620a 100644 --- a/src/libtomahawk/track.h +++ b/src/libtomahawk/track.h @@ -1,3 +1,21 @@ +/* === This file is part of Tomahawk Player - === + * + * Copyright 2010-2011, Christian Muehlhaeuser + * + * Tomahawk is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * Tomahawk is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with Tomahawk. If not, see . + */ + #ifndef TOMAHAWKTRACK_H #define TOMAHAWKTRACK_H diff --git a/src/libtomahawk/typedefs.h b/src/libtomahawk/typedefs.h index 1283c7e11..8ae84aed8 100644 --- a/src/libtomahawk/typedefs.h +++ b/src/libtomahawk/typedefs.h @@ -1,3 +1,21 @@ +/* === This file is part of Tomahawk Player - === + * + * Copyright 2010-2011, Christian Muehlhaeuser + * + * Tomahawk is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * Tomahawk is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with Tomahawk. If not, see . + */ + #ifndef TYPEDEFS_H #define TYPEDEFS_H diff --git a/src/libtomahawk/utils/animatedcounterlabel.h b/src/libtomahawk/utils/animatedcounterlabel.h index 2ea8b67ca..ab430397e 100644 --- a/src/libtomahawk/utils/animatedcounterlabel.h +++ b/src/libtomahawk/utils/animatedcounterlabel.h @@ -1,3 +1,21 @@ +/* === This file is part of Tomahawk Player - === + * + * Copyright 2010-2011, Christian Muehlhaeuser + * + * Tomahawk is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * Tomahawk is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with Tomahawk. If not, see . + */ + #ifndef ANIMATEDCOUNTERLABEL_H #define ANIMATEDCOUNTERLABEL_H diff --git a/src/libtomahawk/utils/animatedsplitter.cpp b/src/libtomahawk/utils/animatedsplitter.cpp index 696f76959..3d9607ee3 100644 --- a/src/libtomahawk/utils/animatedsplitter.cpp +++ b/src/libtomahawk/utils/animatedsplitter.cpp @@ -1,3 +1,21 @@ +/* === This file is part of Tomahawk Player - === + * + * Copyright 2010-2011, Christian Muehlhaeuser + * + * Tomahawk is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * Tomahawk is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with Tomahawk. If not, see . + */ + #include "animatedsplitter.h" #define ANIMATION_TIME 400 diff --git a/src/libtomahawk/utils/animatedsplitter.h b/src/libtomahawk/utils/animatedsplitter.h index 1744a144b..ba1671f4e 100644 --- a/src/libtomahawk/utils/animatedsplitter.h +++ b/src/libtomahawk/utils/animatedsplitter.h @@ -1,3 +1,21 @@ +/* === This file is part of Tomahawk Player - === + * + * Copyright 2010-2011, Christian Muehlhaeuser + * + * Tomahawk is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * Tomahawk is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with Tomahawk. If not, see . + */ + #ifndef ANIMATEDSPLITTER_H #define ANIMATEDSPLITTER_H diff --git a/src/libtomahawk/utils/elidedlabel.cpp b/src/libtomahawk/utils/elidedlabel.cpp index a17f31dca..c490c7c85 100644 --- a/src/libtomahawk/utils/elidedlabel.cpp +++ b/src/libtomahawk/utils/elidedlabel.cpp @@ -1,3 +1,21 @@ +/* === This file is part of Tomahawk Player - === + * + * Copyright 2010-2011, Christian Muehlhaeuser + * + * Tomahawk is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * Tomahawk is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with Tomahawk. If not, see . + */ + #include "elidedlabel.h" #include diff --git a/src/libtomahawk/utils/elidedlabel.h b/src/libtomahawk/utils/elidedlabel.h index 2756ac73e..e78355ec0 100644 --- a/src/libtomahawk/utils/elidedlabel.h +++ b/src/libtomahawk/utils/elidedlabel.h @@ -1,3 +1,21 @@ +/* === This file is part of Tomahawk Player - === + * + * Copyright 2010-2011, Christian Muehlhaeuser + * + * Tomahawk is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * Tomahawk is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with Tomahawk. If not, see . + */ + #ifndef ELIDEDLABEL_H #define ELIDEDLABEL_H diff --git a/src/libtomahawk/utils/imagebutton.cpp b/src/libtomahawk/utils/imagebutton.cpp index de2a8a580..90eb1c521 100644 --- a/src/libtomahawk/utils/imagebutton.cpp +++ b/src/libtomahawk/utils/imagebutton.cpp @@ -1,3 +1,21 @@ +/* === This file is part of Tomahawk Player - === + * + * Copyright 2010-2011, Christian Muehlhaeuser + * + * Tomahawk is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * Tomahawk is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with Tomahawk. If not, see . + */ + #include "imagebutton.h" #include diff --git a/src/libtomahawk/utils/imagebutton.h b/src/libtomahawk/utils/imagebutton.h index 822d77cb3..d3b060ab4 100644 --- a/src/libtomahawk/utils/imagebutton.h +++ b/src/libtomahawk/utils/imagebutton.h @@ -1,3 +1,21 @@ +/* === This file is part of Tomahawk Player - === + * + * Copyright 2010-2011, Christian Muehlhaeuser + * + * Tomahawk is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * Tomahawk is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with Tomahawk. If not, see . + */ + #ifndef IMAGE_BUTTON_H #define IMAGE_BUTTON_H diff --git a/src/libtomahawk/utils/progresstreeview.cpp b/src/libtomahawk/utils/progresstreeview.cpp index 58ca9edef..2458de911 100644 --- a/src/libtomahawk/utils/progresstreeview.cpp +++ b/src/libtomahawk/utils/progresstreeview.cpp @@ -1,3 +1,21 @@ +/* === This file is part of Tomahawk Player - === + * + * Copyright 2010-2011, Christian Muehlhaeuser + * + * Tomahawk is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * Tomahawk is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with Tomahawk. If not, see . + */ + #include "progresstreeview.h" ProgressTreeView::ProgressTreeView( QWidget* parent ) diff --git a/src/libtomahawk/utils/progresstreeview.h b/src/libtomahawk/utils/progresstreeview.h index 154e1e7d0..e77971eaf 100644 --- a/src/libtomahawk/utils/progresstreeview.h +++ b/src/libtomahawk/utils/progresstreeview.h @@ -1,3 +1,21 @@ +/* === This file is part of Tomahawk Player - === + * + * Copyright 2010-2011, Christian Muehlhaeuser + * + * Tomahawk is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * Tomahawk is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with Tomahawk. If not, see . + */ + #ifndef PROGRESSTREEVIEW_H #define PROGRESSTREEVIEW_H diff --git a/src/libtomahawk/utils/proxystyle.cpp b/src/libtomahawk/utils/proxystyle.cpp index 617cd348f..038208a85 100644 --- a/src/libtomahawk/utils/proxystyle.cpp +++ b/src/libtomahawk/utils/proxystyle.cpp @@ -1,3 +1,21 @@ +/* === This file is part of Tomahawk Player - === + * + * Copyright 2010-2011, Christian Muehlhaeuser + * + * Tomahawk is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * Tomahawk is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with Tomahawk. If not, see . + */ + #include "proxystyle.h" #include diff --git a/src/libtomahawk/utils/proxystyle.h b/src/libtomahawk/utils/proxystyle.h index c104ac0fe..488c87908 100644 --- a/src/libtomahawk/utils/proxystyle.h +++ b/src/libtomahawk/utils/proxystyle.h @@ -1,3 +1,21 @@ +/* === This file is part of Tomahawk Player - === + * + * Copyright 2010-2011, Christian Muehlhaeuser + * + * Tomahawk is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * Tomahawk is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with Tomahawk. If not, see . + */ + #ifndef PROXYSTYLE_H #define PROXYSTYLE_H diff --git a/src/libtomahawk/utils/querylabel.cpp b/src/libtomahawk/utils/querylabel.cpp index 5a211f350..8a5d77ce0 100644 --- a/src/libtomahawk/utils/querylabel.cpp +++ b/src/libtomahawk/utils/querylabel.cpp @@ -1,3 +1,21 @@ +/* === This file is part of Tomahawk Player - === + * + * Copyright 2010-2011, Christian Muehlhaeuser + * + * Tomahawk is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * Tomahawk is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with Tomahawk. If not, see . + */ + #include "querylabel.h" #include diff --git a/src/libtomahawk/utils/querylabel.h b/src/libtomahawk/utils/querylabel.h index b584d255b..1fed3d9c2 100644 --- a/src/libtomahawk/utils/querylabel.h +++ b/src/libtomahawk/utils/querylabel.h @@ -1,3 +1,21 @@ +/* === This file is part of Tomahawk Player - === + * + * Copyright 2010-2011, Christian Muehlhaeuser + * + * Tomahawk is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * Tomahawk is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with Tomahawk. If not, see . + */ + #ifndef QUERYLABEL_H #define QUERYLABEL_H diff --git a/src/libtomahawk/utils/tomahawkutils.cpp b/src/libtomahawk/utils/tomahawkutils.cpp index ac766b178..a551e39c6 100644 --- a/src/libtomahawk/utils/tomahawkutils.cpp +++ b/src/libtomahawk/utils/tomahawkutils.cpp @@ -1,3 +1,21 @@ +/* === This file is part of Tomahawk Player - === + * + * Copyright 2010-2011, Christian Muehlhaeuser + * + * Tomahawk is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * Tomahawk is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with Tomahawk. If not, see . + */ + #include "tomahawkutils.h" #include diff --git a/src/libtomahawk/utils/tomahawkutils.h b/src/libtomahawk/utils/tomahawkutils.h index 5c1b68eaf..103e379ce 100644 --- a/src/libtomahawk/utils/tomahawkutils.h +++ b/src/libtomahawk/utils/tomahawkutils.h @@ -1,3 +1,21 @@ +/* === This file is part of Tomahawk Player - === + * + * Copyright 2010-2011, Christian Muehlhaeuser + * + * Tomahawk is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * Tomahawk is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with Tomahawk. If not, see . + */ + #ifndef TOMAHAWKUTILS_H #define TOMAHAWKUTILS_H diff --git a/src/libtomahawk/utils/widgetdragfilter.cpp b/src/libtomahawk/utils/widgetdragfilter.cpp index 40aec2aa8..fe5216669 100644 --- a/src/libtomahawk/utils/widgetdragfilter.cpp +++ b/src/libtomahawk/utils/widgetdragfilter.cpp @@ -1,3 +1,21 @@ +/* === This file is part of Tomahawk Player - === + * + * Copyright 2010-2011, Christian Muehlhaeuser + * + * Tomahawk is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * Tomahawk is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with Tomahawk. If not, see . + */ + #include "widgetdragfilter.h" #include #include diff --git a/src/libtomahawk/utils/widgetdragfilter.h b/src/libtomahawk/utils/widgetdragfilter.h index 18bfc36dd..ab6c5c5ea 100644 --- a/src/libtomahawk/utils/widgetdragfilter.h +++ b/src/libtomahawk/utils/widgetdragfilter.h @@ -1,3 +1,21 @@ +/* === This file is part of Tomahawk Player - === + * + * Copyright 2010-2011, Christian Muehlhaeuser + * + * Tomahawk is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * Tomahawk is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with Tomahawk. If not, see . + */ + #ifndef WIDGET_DRAG_FILTER_H #define WIDGET_DRAG_FILTER_H diff --git a/src/libtomahawk/utils/xspfloader.cpp b/src/libtomahawk/utils/xspfloader.cpp index 88ad39b6f..5b1899300 100644 --- a/src/libtomahawk/utils/xspfloader.cpp +++ b/src/libtomahawk/utils/xspfloader.cpp @@ -1,3 +1,21 @@ +/* === This file is part of Tomahawk Player - === + * + * Copyright 2010-2011, Christian Muehlhaeuser + * + * Tomahawk is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * Tomahawk is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with Tomahawk. If not, see . + */ + #include "xspfloader.h" #include diff --git a/src/libtomahawk/utils/xspfloader.h b/src/libtomahawk/utils/xspfloader.h index 7e0e1ece3..d541293cf 100644 --- a/src/libtomahawk/utils/xspfloader.h +++ b/src/libtomahawk/utils/xspfloader.h @@ -1,3 +1,21 @@ +/* === This file is part of Tomahawk Player - === + * + * Copyright 2010-2011, Christian Muehlhaeuser + * + * 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 . + */ + /* Fetches and parses an XSPF document from a QFile or QUrl. */ diff --git a/src/libtomahawk/viewpage.cpp b/src/libtomahawk/viewpage.cpp index 321bbbdf8..4cd52c941 100644 --- a/src/libtomahawk/viewpage.cpp +++ b/src/libtomahawk/viewpage.cpp @@ -1,3 +1,21 @@ +/* === This file is part of Tomahawk Player - === + * + * Copyright 2010-2011, Christian Muehlhaeuser + * + * Tomahawk is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * Tomahawk is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with Tomahawk. If not, see . + */ + #include "viewpage.h" #include diff --git a/src/libtomahawk/viewpage.h b/src/libtomahawk/viewpage.h index af087c669..b59695e7a 100644 --- a/src/libtomahawk/viewpage.h +++ b/src/libtomahawk/viewpage.h @@ -1,3 +1,21 @@ +/* === This file is part of Tomahawk Player - === + * + * Copyright 2010-2011, Christian Muehlhaeuser + * + * Tomahawk is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * Tomahawk is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with Tomahawk. If not, see . + */ + #ifndef VIEWPAGE_H #define VIEWPAGE_H diff --git a/src/libtomahawk/widgets/infowidgets/sourceinfowidget.cpp b/src/libtomahawk/widgets/infowidgets/sourceinfowidget.cpp index 80469499e..c78783538 100644 --- a/src/libtomahawk/widgets/infowidgets/sourceinfowidget.cpp +++ b/src/libtomahawk/widgets/infowidgets/sourceinfowidget.cpp @@ -1,3 +1,21 @@ +/* === This file is part of Tomahawk Player - === + * + * Copyright 2010-2011, Christian Muehlhaeuser + * + * Tomahawk is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * Tomahawk is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with Tomahawk. If not, see . + */ + #include "sourceinfowidget.h" #include "ui_sourceinfowidget.h" diff --git a/src/libtomahawk/widgets/infowidgets/sourceinfowidget.h b/src/libtomahawk/widgets/infowidgets/sourceinfowidget.h index f12fd942d..12528129e 100644 --- a/src/libtomahawk/widgets/infowidgets/sourceinfowidget.h +++ b/src/libtomahawk/widgets/infowidgets/sourceinfowidget.h @@ -1,3 +1,21 @@ +/* === This file is part of Tomahawk Player - === + * + * Copyright 2010-2011, Christian Muehlhaeuser + * + * Tomahawk is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * Tomahawk is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with Tomahawk. If not, see . + */ + #ifndef SOURCEINFOWIDGET_H #define SOURCEINFOWIDGET_H diff --git a/src/libtomahawk/widgets/newplaylistwidget.cpp b/src/libtomahawk/widgets/newplaylistwidget.cpp index 7882532c0..869bfdb3e 100644 --- a/src/libtomahawk/widgets/newplaylistwidget.cpp +++ b/src/libtomahawk/widgets/newplaylistwidget.cpp @@ -1,3 +1,21 @@ +/* === This file is part of Tomahawk Player - === + * + * Copyright 2010-2011, Christian Muehlhaeuser + * + * Tomahawk is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * Tomahawk is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with Tomahawk. If not, see . + */ + #include "newplaylistwidget.h" #include "ui_newplaylistwidget.h" diff --git a/src/libtomahawk/widgets/newplaylistwidget.h b/src/libtomahawk/widgets/newplaylistwidget.h index dfe54a988..bb6c27629 100644 --- a/src/libtomahawk/widgets/newplaylistwidget.h +++ b/src/libtomahawk/widgets/newplaylistwidget.h @@ -1,3 +1,21 @@ +/* === This file is part of Tomahawk Player - === + * + * Copyright 2010-2011, Christian Muehlhaeuser + * + * Tomahawk is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * Tomahawk is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with Tomahawk. If not, see . + */ + #ifndef NEWPLAYLISTWIDGET_H #define NEWPLAYLISTWIDGET_H diff --git a/src/libtomahawk/widgets/overlaywidget.cpp b/src/libtomahawk/widgets/overlaywidget.cpp index 2abceb272..cbfc44946 100644 --- a/src/libtomahawk/widgets/overlaywidget.cpp +++ b/src/libtomahawk/widgets/overlaywidget.cpp @@ -1,3 +1,21 @@ +/* === This file is part of Tomahawk Player - === + * + * Copyright 2010-2011, Christian Muehlhaeuser + * + * Tomahawk is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * Tomahawk is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with Tomahawk. If not, see . + */ + #include "overlaywidget.h" #include diff --git a/src/libtomahawk/widgets/overlaywidget.h b/src/libtomahawk/widgets/overlaywidget.h index 7efe77af8..5775b991d 100644 --- a/src/libtomahawk/widgets/overlaywidget.h +++ b/src/libtomahawk/widgets/overlaywidget.h @@ -1,3 +1,21 @@ +/* === This file is part of Tomahawk Player - === + * + * Copyright 2010-2011, Christian Muehlhaeuser + * + * Tomahawk is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * Tomahawk is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with Tomahawk. If not, see . + */ + #ifndef OVERLAYWIDGET_H #define OVERLAYWIDGET_H diff --git a/src/libtomahawk/widgets/welcomewidget.cpp b/src/libtomahawk/widgets/welcomewidget.cpp index feb2d988a..a9ff52819 100644 --- a/src/libtomahawk/widgets/welcomewidget.cpp +++ b/src/libtomahawk/widgets/welcomewidget.cpp @@ -1,3 +1,21 @@ +/* === This file is part of Tomahawk Player - === + * + * Copyright 2010-2011, Christian Muehlhaeuser + * + * Tomahawk is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * Tomahawk is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with Tomahawk. If not, see . + */ + #include "welcomewidget.h" #include "ui_welcomewidget.h" diff --git a/src/libtomahawk/widgets/welcomewidget.h b/src/libtomahawk/widgets/welcomewidget.h index 9d14859e8..9ec4e249c 100644 --- a/src/libtomahawk/widgets/welcomewidget.h +++ b/src/libtomahawk/widgets/welcomewidget.h @@ -1,3 +1,21 @@ +/* === This file is part of Tomahawk Player - === + * + * Copyright 2010-2011, Christian Muehlhaeuser + * + * Tomahawk is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * Tomahawk is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with Tomahawk. If not, see . + */ + #ifndef WELCOMEWIDGET_H #define WELCOMEWIDGET_H diff --git a/src/mac/macshortcuthandler.cpp b/src/mac/macshortcuthandler.cpp index 80bc64815..34701f7f1 100644 --- a/src/mac/macshortcuthandler.cpp +++ b/src/mac/macshortcuthandler.cpp @@ -1,3 +1,21 @@ +/* === This file is part of Tomahawk Player - === + * + * Copyright 2010-2011, Christian Muehlhaeuser + * + * Tomahawk is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * Tomahawk is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with Tomahawk. If not, see . + */ + #include "macshortcuthandler.h" #include diff --git a/src/mac/macshortcuthandler.h b/src/mac/macshortcuthandler.h index 014d71421..b7739c20d 100644 --- a/src/mac/macshortcuthandler.h +++ b/src/mac/macshortcuthandler.h @@ -1,3 +1,21 @@ +/* === This file is part of Tomahawk Player - === + * + * Copyright 2010-2011, Christian Muehlhaeuser + * + * Tomahawk is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * Tomahawk is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with Tomahawk. If not, see . + */ + #ifndef MACSHORTCUTHANDLER_H #define MACSHORTCUTHANDLER_H diff --git a/src/mac/tomahawkapp_mac.h b/src/mac/tomahawkapp_mac.h index 0d3f59f61..86ae6702c 100644 --- a/src/mac/tomahawkapp_mac.h +++ b/src/mac/tomahawkapp_mac.h @@ -1,3 +1,21 @@ +/* === This file is part of Tomahawk Player - === + * + * Copyright 2010-2011, Christian Muehlhaeuser + * + * Tomahawk is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * Tomahawk is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with Tomahawk. If not, see . + */ + #ifndef TOMAHAWKAPP_MAC_H #define TOMAHAWKAPP_MAC_H diff --git a/src/mac/tomahawkapp_mac.mm b/src/mac/tomahawkapp_mac.mm index 3ed942031..44660e742 100644 --- a/src/mac/tomahawkapp_mac.mm +++ b/src/mac/tomahawkapp_mac.mm @@ -1,3 +1,21 @@ +/* === This file is part of Tomahawk Player - === + * + * Copyright 2010-2011, Christian Muehlhaeuser + * + * Tomahawk is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * Tomahawk is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with Tomahawk. If not, see . + */ + #include "tomahawkapp_mac.h" #include "tomahawkapp_macdelegate.h" #include "macshortcuthandler.h" diff --git a/src/main.cpp b/src/main.cpp index 137172c69..916ffbb2e 100644 --- a/src/main.cpp +++ b/src/main.cpp @@ -1,3 +1,21 @@ +/* === This file is part of Tomahawk Player - === + * + * Copyright 2010-2011, Christian Muehlhaeuser + * + * Tomahawk is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * Tomahawk is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with Tomahawk. If not, see . + */ + #include "tomahawk/tomahawkapp.h" #ifdef Q_WS_MAC diff --git a/src/musicscanner.cpp b/src/musicscanner.cpp index c92231113..220baa1a7 100644 --- a/src/musicscanner.cpp +++ b/src/musicscanner.cpp @@ -1,3 +1,21 @@ +/* === This file is part of Tomahawk Player - === + * + * Copyright 2010-2011, Christian Muehlhaeuser + * + * Tomahawk is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * Tomahawk is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with Tomahawk. If not, see . + */ + #include "musicscanner.h" #include "tomahawk/tomahawkapp.h" diff --git a/src/musicscanner.h b/src/musicscanner.h index 0787f5486..bfde9e075 100644 --- a/src/musicscanner.h +++ b/src/musicscanner.h @@ -1,3 +1,21 @@ +/* === This file is part of Tomahawk Player - === + * + * Copyright 2010-2011, Christian Muehlhaeuser + * + * Tomahawk is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * Tomahawk is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with Tomahawk. If not, see . + */ + #ifndef MUSICSCANNER_H #define MUSICSCANNER_H diff --git a/src/plugins/fake/fakecollection.cpp b/src/plugins/fake/fakecollection.cpp index fc01399f8..01ebe8a05 100644 --- a/src/plugins/fake/fakecollection.cpp +++ b/src/plugins/fake/fakecollection.cpp @@ -1,3 +1,21 @@ +/* === This file is part of Tomahawk Player - === + * + * Copyright 2010-2011, Christian Muehlhaeuser + * + * Tomahawk is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * Tomahawk is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with Tomahawk. If not, see . + */ + #include "fakecollection.h" #include "tomahawk/functimeout.h" diff --git a/src/plugins/fake/fakecollection.h b/src/plugins/fake/fakecollection.h index 72cb9bc70..c75d25b64 100644 --- a/src/plugins/fake/fakecollection.h +++ b/src/plugins/fake/fakecollection.h @@ -1,3 +1,21 @@ +/* === This file is part of Tomahawk Player - === + * + * Copyright 2010-2011, Christian Muehlhaeuser + * + * Tomahawk is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * Tomahawk is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with Tomahawk. If not, see . + */ + #ifndef FAKECOLLECTION_H #define FAKECOLLECTION_H #include "tomahawk/collection.h" diff --git a/src/plugins/fake/fakeplugin.cpp b/src/plugins/fake/fakeplugin.cpp index 90f50c74e..d320043cd 100644 --- a/src/plugins/fake/fakeplugin.cpp +++ b/src/plugins/fake/fakeplugin.cpp @@ -1,3 +1,21 @@ +/* === This file is part of Tomahawk Player - === + * + * Copyright 2010-2011, Christian Muehlhaeuser + * + * Tomahawk is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * Tomahawk is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with Tomahawk. If not, see . + */ + #include "fakeplugin.h" #include "fakecollection.h" diff --git a/src/plugins/fake/fakeplugin.h b/src/plugins/fake/fakeplugin.h index 4244aa64c..aed77ed80 100644 --- a/src/plugins/fake/fakeplugin.h +++ b/src/plugins/fake/fakeplugin.h @@ -1,3 +1,21 @@ +/* === This file is part of Tomahawk Player - === + * + * Copyright 2010-2011, Christian Muehlhaeuser + * + * Tomahawk is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * Tomahawk is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with Tomahawk. If not, see . + */ + #ifndef TOMAHAWK_LIB_PLUGIN_H #define TOMAHAWK_LIB_PLUGIN_H #include diff --git a/src/resolvers/qtscriptresolver.cpp b/src/resolvers/qtscriptresolver.cpp index 1455c77c7..3b494fd61 100644 --- a/src/resolvers/qtscriptresolver.cpp +++ b/src/resolvers/qtscriptresolver.cpp @@ -1,3 +1,21 @@ +/* === This file is part of Tomahawk Player - === + * + * Copyright 2010-2011, Christian Muehlhaeuser + * + * Tomahawk is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * Tomahawk is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with Tomahawk. If not, see . + */ + #include "qtscriptresolver.h" #include "artist.h" diff --git a/src/resolvers/qtscriptresolver.h b/src/resolvers/qtscriptresolver.h index 873122e71..05b55cc71 100644 --- a/src/resolvers/qtscriptresolver.h +++ b/src/resolvers/qtscriptresolver.h @@ -1,3 +1,21 @@ +/* === This file is part of Tomahawk Player - === + * + * Copyright 2010-2011, Christian Muehlhaeuser + * + * Tomahawk is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * Tomahawk is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with Tomahawk. If not, see . + */ + #ifndef QTSCRIPTRESOLVER_H #define QTSCRIPTRESOLVER_H diff --git a/src/resolvers/scriptresolver.cpp b/src/resolvers/scriptresolver.cpp index 375f301e0..913588663 100644 --- a/src/resolvers/scriptresolver.cpp +++ b/src/resolvers/scriptresolver.cpp @@ -1,3 +1,21 @@ +/* === This file is part of Tomahawk Player - === + * + * Copyright 2010-2011, Christian Muehlhaeuser + * + * Tomahawk is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * Tomahawk is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with Tomahawk. If not, see . + */ + #include "scriptresolver.h" #include diff --git a/src/resolvers/scriptresolver.h b/src/resolvers/scriptresolver.h index e21d52e64..dcd5eeed6 100644 --- a/src/resolvers/scriptresolver.h +++ b/src/resolvers/scriptresolver.h @@ -1,3 +1,21 @@ +/* === This file is part of Tomahawk Player - === + * + * Copyright 2010-2011, Christian Muehlhaeuser + * + * Tomahawk is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * Tomahawk is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with Tomahawk. If not, see . + */ + #ifndef SCRIPTRESOLVER_H #define SCRIPTRESOLVER_H diff --git a/src/scanmanager.cpp b/src/scanmanager.cpp index 911381fc2..613d91d4c 100644 --- a/src/scanmanager.cpp +++ b/src/scanmanager.cpp @@ -1,3 +1,21 @@ +/* === This file is part of Tomahawk Player - === + * + * Copyright 2010-2011, Christian Muehlhaeuser + * + * Tomahawk is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * Tomahawk is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with Tomahawk. If not, see . + */ + #include "scanmanager.h" #include diff --git a/src/scanmanager.h b/src/scanmanager.h index 1c69a15e1..dc139cded 100644 --- a/src/scanmanager.h +++ b/src/scanmanager.h @@ -1,3 +1,21 @@ +/* === This file is part of Tomahawk Player - === + * + * Copyright 2010-2011, Christian Muehlhaeuser + * + * Tomahawk is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * Tomahawk is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with Tomahawk. If not, see . + */ + #ifndef SCANMANAGER_H #define SCANMANAGER_H diff --git a/src/scrobbler.cpp b/src/scrobbler.cpp index e1cdcbb93..f3fdaaf58 100644 --- a/src/scrobbler.cpp +++ b/src/scrobbler.cpp @@ -1,3 +1,21 @@ +/* === This file is part of Tomahawk Player - === + * + * Copyright 2010-2011, Christian Muehlhaeuser + * + * Tomahawk is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * Tomahawk is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with Tomahawk. If not, see . + */ + #include "scrobbler.h" #include diff --git a/src/scrobbler.h b/src/scrobbler.h index 3daca7537..69750427e 100644 --- a/src/scrobbler.h +++ b/src/scrobbler.h @@ -1,3 +1,20 @@ +/* === This file is part of Tomahawk Player - === + * + * Copyright 2010-2011, Christian Muehlhaeuser + * + * Tomahawk is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * Tomahawk is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with Tomahawk. If not, see . + */ #ifndef TOMAHAWK_SCROBBLER_H #define TOMAHAWK_SCROBBLER_H diff --git a/src/settingsdialog.cpp b/src/settingsdialog.cpp index 4b290e24a..8baadd6e9 100644 --- a/src/settingsdialog.cpp +++ b/src/settingsdialog.cpp @@ -1,3 +1,21 @@ +/* === This file is part of Tomahawk Player - === + * + * Copyright 2010-2011, Christian Muehlhaeuser + * + * Tomahawk is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * Tomahawk is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with Tomahawk. If not, see . + */ + #include "config.h" #include diff --git a/src/settingsdialog.h b/src/settingsdialog.h index 8f04c0ee4..02a3c608f 100644 --- a/src/settingsdialog.h +++ b/src/settingsdialog.h @@ -1,3 +1,21 @@ +/* === This file is part of Tomahawk Player - === + * + * Copyright 2010-2011, Christian Muehlhaeuser + * + * Tomahawk is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * Tomahawk is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with Tomahawk. If not, see . + */ + #ifndef SETTINGSDIALOG_H #define SETTINGSDIALOG_H diff --git a/src/shortcuthandler.cpp b/src/shortcuthandler.cpp index 7709789ca..766b971be 100644 --- a/src/shortcuthandler.cpp +++ b/src/shortcuthandler.cpp @@ -1,3 +1,21 @@ +/* === This file is part of Tomahawk Player - === + * + * Copyright 2010-2011, Christian Muehlhaeuser + * + * Tomahawk is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * Tomahawk is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with Tomahawk. If not, see . + */ + #include "shortcuthandler.h" using namespace Tomahawk; diff --git a/src/shortcuthandler.h b/src/shortcuthandler.h index 6462c0ebf..a74e7660d 100644 --- a/src/shortcuthandler.h +++ b/src/shortcuthandler.h @@ -1,3 +1,21 @@ +/* === This file is part of Tomahawk Player - === + * + * Copyright 2010-2011, Christian Muehlhaeuser + * + * Tomahawk is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * Tomahawk is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with Tomahawk. If not, see . + */ + #ifndef SHORTCUTHANDLER_H #define SHORTCUTHANDLER_H diff --git a/src/sip/SipHandler.cpp b/src/sip/SipHandler.cpp index c72076746..237d9ca78 100644 --- a/src/sip/SipHandler.cpp +++ b/src/sip/SipHandler.cpp @@ -1,3 +1,21 @@ +/* === This file is part of Tomahawk Player - === + * + * Copyright 2010-2011, Christian Muehlhaeuser + * + * Tomahawk is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * Tomahawk is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with Tomahawk. If not, see . + */ + #include "SipHandler.h" #include "sip/SipPlugin.h" diff --git a/src/sip/SipHandler.h b/src/sip/SipHandler.h index 47464e748..4f1d395c6 100644 --- a/src/sip/SipHandler.h +++ b/src/sip/SipHandler.h @@ -1,3 +1,21 @@ +/* === This file is part of Tomahawk Player - === + * + * Copyright 2010-2011, Christian Muehlhaeuser + * + * Tomahawk is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * Tomahawk is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with Tomahawk. If not, see . + */ + #ifndef SIPHANDLER_H #define SIPHANDLER_H diff --git a/src/sip/jabber/jabber.cpp b/src/sip/jabber/jabber.cpp index 89c5cfcbe..fa5800e39 100644 --- a/src/sip/jabber/jabber.cpp +++ b/src/sip/jabber/jabber.cpp @@ -1,3 +1,21 @@ +/* === This file is part of Tomahawk Player - === + * + * Copyright 2010-2011, Christian Muehlhaeuser + * + * Tomahawk is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * Tomahawk is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with Tomahawk. If not, see . + */ + #include "jabber.h" #include "tomahawksettings.h" diff --git a/src/sip/jabber/jabber.h b/src/sip/jabber/jabber.h index 025cb03c1..c702fed71 100644 --- a/src/sip/jabber/jabber.h +++ b/src/sip/jabber/jabber.h @@ -1,3 +1,21 @@ +/* === This file is part of Tomahawk Player - === + * + * Copyright 2010-2011, Christian Muehlhaeuser + * + * Tomahawk is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * Tomahawk is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with Tomahawk. If not, see . + */ + #ifndef JABBER_H #define JABBER_H diff --git a/src/sip/jabber/jabber_p.cpp b/src/sip/jabber/jabber_p.cpp index 69801b946..d4cbe801c 100644 --- a/src/sip/jabber/jabber_p.cpp +++ b/src/sip/jabber/jabber_p.cpp @@ -1,3 +1,21 @@ +/* === This file is part of Tomahawk Player - === + * + * Copyright 2010-2011, Christian Muehlhaeuser + * + * Tomahawk is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * Tomahawk is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with Tomahawk. If not, see . + */ + #include "jabber_p.h" #include diff --git a/src/sip/jabber/jabber_p.h b/src/sip/jabber/jabber_p.h index 3b0b74e5b..3e14c7c69 100644 --- a/src/sip/jabber/jabber_p.h +++ b/src/sip/jabber/jabber_p.h @@ -1,3 +1,21 @@ +/* === This file is part of Tomahawk Player - === + * + * Copyright 2010-2011, Christian Muehlhaeuser + * + * 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 . + */ + /* This is the Jabber client that the rest of the app sees Gloox stuff should NOT leak outside this class. diff --git a/src/sip/jreen/jabber.cpp b/src/sip/jreen/jabber.cpp index 147f59d0d..0d0c9acc6 100644 --- a/src/sip/jreen/jabber.cpp +++ b/src/sip/jreen/jabber.cpp @@ -1,3 +1,21 @@ +/* === This file is part of Tomahawk Player - === + * + * Copyright 2010-2011, Christian Muehlhaeuser + * + * Tomahawk is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * Tomahawk is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with Tomahawk. If not, see . + */ + #include "jabber.h" #include "tomahawksettings.h" diff --git a/src/sip/jreen/jabber.h b/src/sip/jreen/jabber.h index 430c69058..543a5a218 100644 --- a/src/sip/jreen/jabber.h +++ b/src/sip/jreen/jabber.h @@ -1,3 +1,21 @@ +/* === This file is part of Tomahawk Player - === + * + * Copyright 2010-2011, Christian Muehlhaeuser + * + * Tomahawk is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * Tomahawk is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with Tomahawk. If not, see . + */ + #ifndef JABBER_H #define JABBER_H diff --git a/src/sip/jreen/jabber_p.cpp b/src/sip/jreen/jabber_p.cpp index ef7a9a080..a902ef4a3 100644 --- a/src/sip/jreen/jabber_p.cpp +++ b/src/sip/jreen/jabber_p.cpp @@ -1,3 +1,21 @@ +/* === This file is part of Tomahawk Player - === + * + * Copyright 2010-2011, Christian Muehlhaeuser + * + * Tomahawk is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * Tomahawk is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with Tomahawk. If not, see . + */ + #include "jabber_p.h" #include diff --git a/src/sip/jreen/jabber_p.h b/src/sip/jreen/jabber_p.h index b69369b6e..f10541c2c 100644 --- a/src/sip/jreen/jabber_p.h +++ b/src/sip/jreen/jabber_p.h @@ -1,3 +1,21 @@ +/* === This file is part of Tomahawk Player - === + * + * Copyright 2010-2011, Christian Muehlhaeuser + * + * 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 . + */ + /* This is the Jabber client that the rest of the app sees Gloox stuff should NOT leak outside this class. diff --git a/src/sip/sipdllmacro.h b/src/sip/sipdllmacro.h index 8c93a8767..74a8eec22 100644 --- a/src/sip/sipdllmacro.h +++ b/src/sip/sipdllmacro.h @@ -1,3 +1,21 @@ +/* === This file is part of Tomahawk Player - === + * + * Copyright 2010-2011, Christian Muehlhaeuser + * + * Tomahawk is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * Tomahawk is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with Tomahawk. If not, see . + */ + #ifndef SIPDLLMACRO_H #define SIPDLLMACRO_H diff --git a/src/sip/twitter/twitter.cpp b/src/sip/twitter/twitter.cpp index 4dd0bcd0c..3058f1714 100644 --- a/src/sip/twitter/twitter.cpp +++ b/src/sip/twitter/twitter.cpp @@ -1,3 +1,21 @@ +/* === This file is part of Tomahawk Player - === + * + * Copyright 2010-2011, Christian Muehlhaeuser + * + * Tomahawk is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * Tomahawk is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with Tomahawk. If not, see . + */ + #include "twitter.h" #include "twitterconfigwidget.h" diff --git a/src/sip/twitter/twitter.h b/src/sip/twitter/twitter.h index 05a64d35f..9c0c0fd8f 100644 --- a/src/sip/twitter/twitter.h +++ b/src/sip/twitter/twitter.h @@ -1,3 +1,21 @@ +/* === This file is part of Tomahawk Player - === + * + * Copyright 2010-2011, Christian Muehlhaeuser + * + * Tomahawk is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * Tomahawk is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with Tomahawk. If not, see . + */ + #ifndef TWITTER_H #define TWITTER_H diff --git a/src/sip/twitter/twitterconfigwidget.cpp b/src/sip/twitter/twitterconfigwidget.cpp index 1e468278b..3981ce1b8 100644 --- a/src/sip/twitter/twitterconfigwidget.cpp +++ b/src/sip/twitter/twitterconfigwidget.cpp @@ -1,3 +1,21 @@ +/* === This file is part of Tomahawk Player - === + * + * Copyright 2010-2011, Christian Muehlhaeuser + * + * Tomahawk is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * Tomahawk is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with Tomahawk. If not, see . + */ + #include "twitterconfigwidget.h" #include "ui_twitterconfigwidget.h" diff --git a/src/sip/twitter/twitterconfigwidget.h b/src/sip/twitter/twitterconfigwidget.h index 355c45777..8e1d3ed19 100644 --- a/src/sip/twitter/twitterconfigwidget.h +++ b/src/sip/twitter/twitterconfigwidget.h @@ -1,3 +1,21 @@ +/* === This file is part of Tomahawk Player - === + * + * Copyright 2010-2011, Christian Muehlhaeuser + * + * Tomahawk is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * Tomahawk is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with Tomahawk. If not, see . + */ + #ifndef TWITTERCONFIGWIDGET_H #define TWITTERCONFIGWIDGET_H diff --git a/src/sip/zeroconf/tomahawkzeroconf.h b/src/sip/zeroconf/tomahawkzeroconf.h index d80f1103a..d9df53ada 100644 --- a/src/sip/zeroconf/tomahawkzeroconf.h +++ b/src/sip/zeroconf/tomahawkzeroconf.h @@ -1,3 +1,21 @@ +/* === This file is part of Tomahawk Player - === + * + * Copyright 2010-2011, Christian Muehlhaeuser + * + * Tomahawk is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * Tomahawk is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with Tomahawk. If not, see . + */ + #ifndef TOMAHAWKZCONF #define TOMAHAWKZCONF diff --git a/src/sip/zeroconf/zeroconf.cpp b/src/sip/zeroconf/zeroconf.cpp index 3a567ea48..9625f9abc 100644 --- a/src/sip/zeroconf/zeroconf.cpp +++ b/src/sip/zeroconf/zeroconf.cpp @@ -1,3 +1,21 @@ +/* === This file is part of Tomahawk Player - === + * + * Copyright 2010-2011, Christian Muehlhaeuser + * + * Tomahawk is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * Tomahawk is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with Tomahawk. If not, see . + */ + #include "zeroconf.h" #include diff --git a/src/sip/zeroconf/zeroconf.h b/src/sip/zeroconf/zeroconf.h index aa4926908..8c04fa507 100644 --- a/src/sip/zeroconf/zeroconf.h +++ b/src/sip/zeroconf/zeroconf.h @@ -1,3 +1,21 @@ +/* === This file is part of Tomahawk Player - === + * + * Copyright 2010-2011, Christian Muehlhaeuser + * + * Tomahawk is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * Tomahawk is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with Tomahawk. If not, see . + */ + #ifndef ZEROCONF_H #define ZEROCONF_H diff --git a/src/sourcetree/sourcesmodel.cpp b/src/sourcetree/sourcesmodel.cpp index e2a95de0c..bee554830 100644 --- a/src/sourcetree/sourcesmodel.cpp +++ b/src/sourcetree/sourcesmodel.cpp @@ -1,3 +1,21 @@ +/* === This file is part of Tomahawk Player - === + * + * Copyright 2010-2011, Christian Muehlhaeuser + * + * Tomahawk is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * Tomahawk is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with Tomahawk. If not, see . + */ + #include "sourcesmodel.h" #include diff --git a/src/sourcetree/sourcesmodel.h b/src/sourcetree/sourcesmodel.h index e258e73ff..a9ad5d74b 100644 --- a/src/sourcetree/sourcesmodel.h +++ b/src/sourcetree/sourcesmodel.h @@ -1,3 +1,21 @@ +/* === This file is part of Tomahawk Player - === + * + * Copyright 2010-2011, Christian Muehlhaeuser + * + * Tomahawk is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * Tomahawk is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with Tomahawk. If not, see . + */ + #ifndef SOURCESMODEL_H #define SOURCESMODEL_H diff --git a/src/sourcetree/sourcesproxymodel.cpp b/src/sourcetree/sourcesproxymodel.cpp index e4bcea7c3..d93cf098e 100644 --- a/src/sourcetree/sourcesproxymodel.cpp +++ b/src/sourcetree/sourcesproxymodel.cpp @@ -1,3 +1,21 @@ +/* === This file is part of Tomahawk Player - === + * + * Copyright 2010-2011, Christian Muehlhaeuser + * + * Tomahawk is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * Tomahawk is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with Tomahawk. If not, see . + */ + #include "sourcesproxymodel.h" #include diff --git a/src/sourcetree/sourcesproxymodel.h b/src/sourcetree/sourcesproxymodel.h index 5961b4a37..add57b257 100644 --- a/src/sourcetree/sourcesproxymodel.h +++ b/src/sourcetree/sourcesproxymodel.h @@ -1,3 +1,21 @@ +/* === This file is part of Tomahawk Player - === + * + * Copyright 2010-2011, Christian Muehlhaeuser + * + * Tomahawk is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * Tomahawk is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with Tomahawk. If not, see . + */ + #ifndef SOURCESPROXYMODEL_H #define SOURCESPROXYMODEL_H diff --git a/src/sourcetree/sourcetreeitem.cpp b/src/sourcetree/sourcetreeitem.cpp index 4fd22a4d3..1a8728931 100644 --- a/src/sourcetree/sourcetreeitem.cpp +++ b/src/sourcetree/sourcetreeitem.cpp @@ -1,3 +1,21 @@ +/* === This file is part of Tomahawk Player - === + * + * Copyright 2010-2011, Christian Muehlhaeuser + * + * Tomahawk is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * Tomahawk is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with Tomahawk. If not, see . + */ + #include "sourcetreeitem.h" #include diff --git a/src/sourcetree/sourcetreeitem.h b/src/sourcetree/sourcetreeitem.h index caedb01a3..36f07d61b 100644 --- a/src/sourcetree/sourcetreeitem.h +++ b/src/sourcetree/sourcetreeitem.h @@ -1,3 +1,21 @@ +/* === This file is part of Tomahawk Player - === + * + * Copyright 2010-2011, Christian Muehlhaeuser + * + * Tomahawk is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * Tomahawk is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with Tomahawk. If not, see . + */ + #ifndef SOURCETREEITEM_H #define SOURCETREEITEM_H diff --git a/src/sourcetree/sourcetreeitemwidget.cpp b/src/sourcetree/sourcetreeitemwidget.cpp index fe37abbf8..9f8bc64a2 100644 --- a/src/sourcetree/sourcetreeitemwidget.cpp +++ b/src/sourcetree/sourcetreeitemwidget.cpp @@ -1,3 +1,21 @@ +/* === This file is part of Tomahawk Player - === + * + * Copyright 2010-2011, Christian Muehlhaeuser + * + * Tomahawk is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * Tomahawk is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with Tomahawk. If not, see . + */ + #include "sourcetreeitemwidget.h" #include "ui_sourcetreeitemwidget.h" diff --git a/src/sourcetree/sourcetreeitemwidget.h b/src/sourcetree/sourcetreeitemwidget.h index 6414521b5..515b2de27 100644 --- a/src/sourcetree/sourcetreeitemwidget.h +++ b/src/sourcetree/sourcetreeitemwidget.h @@ -1,3 +1,21 @@ +/* === This file is part of Tomahawk Player - === + * + * Copyright 2010-2011, Christian Muehlhaeuser + * + * Tomahawk is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * Tomahawk is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with Tomahawk. If not, see . + */ + #ifndef SOURCETREEITEMWIDGET_H #define SOURCETREEITEMWIDGET_H diff --git a/src/sourcetree/sourcetreeview.cpp b/src/sourcetree/sourcetreeview.cpp index 173a6a957..6a360d97f 100644 --- a/src/sourcetree/sourcetreeview.cpp +++ b/src/sourcetree/sourcetreeview.cpp @@ -1,3 +1,21 @@ +/* === This file is part of Tomahawk Player - === + * + * Copyright 2010-2011, Christian Muehlhaeuser + * + * Tomahawk is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * Tomahawk is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with Tomahawk. If not, see . + */ + #include "sourcetreeview.h" #include "playlist.h" diff --git a/src/sourcetree/sourcetreeview.h b/src/sourcetree/sourcetreeview.h index 077fb2937..710c609cb 100644 --- a/src/sourcetree/sourcetreeview.h +++ b/src/sourcetree/sourcetreeview.h @@ -1,3 +1,21 @@ +/* === This file is part of Tomahawk Player - === + * + * Copyright 2010-2011, Christian Muehlhaeuser + * + * Tomahawk is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * Tomahawk is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with Tomahawk. If not, see . + */ + #ifndef SOURCETREEVIEW_H #define SOURCETREEVIEW_H diff --git a/src/tomahawkapp.cpp b/src/tomahawkapp.cpp index bedf2c0fc..9c7089e3f 100644 --- a/src/tomahawkapp.cpp +++ b/src/tomahawkapp.cpp @@ -1,3 +1,21 @@ +/* === This file is part of Tomahawk Player - === + * + * Copyright 2010-2011, Christian Muehlhaeuser + * + * Tomahawk is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * Tomahawk is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with Tomahawk. If not, see . + */ + #include "tomahawk/tomahawkapp.h" #include "config.h" diff --git a/src/tomahawktrayicon.cpp b/src/tomahawktrayicon.cpp index f364c4a53..e49a813ef 100644 --- a/src/tomahawktrayicon.cpp +++ b/src/tomahawktrayicon.cpp @@ -1,3 +1,21 @@ +/* === This file is part of Tomahawk Player - === + * + * Copyright 2010-2011, Christian Muehlhaeuser + * + * Tomahawk is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * Tomahawk is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with Tomahawk. If not, see . + */ + #include "tomahawktrayicon.h" #include diff --git a/src/tomahawktrayicon.h b/src/tomahawktrayicon.h index c73a85781..2cef11872 100644 --- a/src/tomahawktrayicon.h +++ b/src/tomahawktrayicon.h @@ -1,3 +1,21 @@ +/* === This file is part of Tomahawk Player - === + * + * Copyright 2010-2011, Christian Muehlhaeuser + * + * Tomahawk is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * Tomahawk is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with Tomahawk. If not, see . + */ + #ifndef TOMAHAWK_TRAYICON_H #define TOMAHAWK_TRAYICON_H diff --git a/src/tomahawkwindow.cpp b/src/tomahawkwindow.cpp index 4f04f84a6..363c4fb53 100644 --- a/src/tomahawkwindow.cpp +++ b/src/tomahawkwindow.cpp @@ -1,3 +1,21 @@ +/* === This file is part of Tomahawk Player - === + * + * Copyright 2010-2011, Christian Muehlhaeuser + * + * Tomahawk is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * Tomahawk is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with Tomahawk. If not, see . + */ + #include "tomahawkwindow.h" #include "ui_tomahawkwindow.h" diff --git a/src/tomahawkwindow.h b/src/tomahawkwindow.h index 4d729186c..2f24f9f65 100644 --- a/src/tomahawkwindow.h +++ b/src/tomahawkwindow.h @@ -1,3 +1,21 @@ +/* === This file is part of Tomahawk Player - === + * + * Copyright 2010-2011, Christian Muehlhaeuser + * + * Tomahawk is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * Tomahawk is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with Tomahawk. If not, see . + */ + #ifndef TOMAHAWKWINDOW_H #define TOMAHAWKWINDOW_H diff --git a/src/transferview.cpp b/src/transferview.cpp index 75231cc81..11df37755 100644 --- a/src/transferview.cpp +++ b/src/transferview.cpp @@ -1,3 +1,21 @@ +/* === This file is part of Tomahawk Player - === + * + * Copyright 2010-2011, Christian Muehlhaeuser + * + * Tomahawk is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * Tomahawk is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with Tomahawk. If not, see . + */ + #include "transferview.h" #include diff --git a/src/transferview.h b/src/transferview.h index 3520c03be..d006d047c 100644 --- a/src/transferview.h +++ b/src/transferview.h @@ -1,3 +1,21 @@ +/* === This file is part of Tomahawk Player - === + * + * Copyright 2010-2011, Christian Muehlhaeuser + * + * Tomahawk is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * Tomahawk is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with Tomahawk. If not, see . + */ + #ifndef TRANSFERVIEW_H #define TRANSFERVIEW_H diff --git a/src/web/api_v1.cpp b/src/web/api_v1.cpp index 2e32de572..67b2e4d8b 100644 --- a/src/web/api_v1.cpp +++ b/src/web/api_v1.cpp @@ -1,3 +1,21 @@ +/* === This file is part of Tomahawk Player - === + * + * Copyright 2010-2011, Christian Muehlhaeuser + * + * Tomahawk is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * Tomahawk is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with Tomahawk. If not, see . + */ + #include "api_v1.h" #include diff --git a/src/web/api_v1.h b/src/web/api_v1.h index 243f52449..f7476990e 100644 --- a/src/web/api_v1.h +++ b/src/web/api_v1.h @@ -1,3 +1,21 @@ +/* === This file is part of Tomahawk Player - === + * + * Copyright 2010-2011, Christian Muehlhaeuser + * + * Tomahawk is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * Tomahawk is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with Tomahawk. If not, see . + */ + #ifndef TOMAHAWK_WEBAPI_V1 #define TOMAHAWK_WEBAPI_V1 diff --git a/src/xmppbot/xmppbot.cpp b/src/xmppbot/xmppbot.cpp index 579aab92a..d4f026d35 100644 --- a/src/xmppbot/xmppbot.cpp +++ b/src/xmppbot/xmppbot.cpp @@ -1,3 +1,21 @@ +/* === This file is part of Tomahawk Player - === + * + * Copyright 2010-2011, Christian Muehlhaeuser + * + * Tomahawk is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * Tomahawk is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with Tomahawk. If not, see . + */ + #include "xmppbot.h" #include "tomahawk/tomahawkapp.h" diff --git a/src/xmppbot/xmppbot.h b/src/xmppbot/xmppbot.h index d1034d08e..e88704a44 100644 --- a/src/xmppbot/xmppbot.h +++ b/src/xmppbot/xmppbot.h @@ -1,3 +1,21 @@ +/* === This file is part of Tomahawk Player - === + * + * Copyright 2010-2011, Christian Muehlhaeuser + * + * Tomahawk is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * Tomahawk is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with Tomahawk. If not, see . + */ + #ifndef XMPPBOT_H #define XMPPBOT_H diff --git a/thirdparty/libportfwd/include/portfwd/portfwd.h b/thirdparty/libportfwd/include/portfwd/portfwd.h index faedd8f03..e54a00f0b 100644 --- a/thirdparty/libportfwd/include/portfwd/portfwd.h +++ b/thirdparty/libportfwd/include/portfwd/portfwd.h @@ -1,3 +1,20 @@ +/* + * Copyright 2010-2011, Christian Muehlhaeuser + * + * Tomahawk is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * Tomahawk is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with Tomahawk. If not, see . + */ + #ifndef LIBPORTFWD_PORTFWD_H #define LIBPORTFWD_PORTFWD_H true #include diff --git a/thirdparty/libportfwd/src/main.cpp b/thirdparty/libportfwd/src/main.cpp index 724a54bf6..5d4b5050c 100644 --- a/thirdparty/libportfwd/src/main.cpp +++ b/thirdparty/libportfwd/src/main.cpp @@ -1,4 +1,22 @@ +/* + * Copyright 2010-2011, Christian Muehlhaeuser + * + * Tomahawk is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * Tomahawk is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with Tomahawk. If not, see . + */ + #include "portfwd/portfwd.h" + int main(int argc, char** argv) { if(argc!=2) diff --git a/thirdparty/libportfwd/src/portfwd.cpp b/thirdparty/libportfwd/src/portfwd.cpp index e64024621..0dd315218 100644 --- a/thirdparty/libportfwd/src/portfwd.cpp +++ b/thirdparty/libportfwd/src/portfwd.cpp @@ -1,3 +1,20 @@ +/* + * Copyright 2010-2011, Christian Muehlhaeuser + * + * Tomahawk is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * Tomahawk is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with Tomahawk. If not, see . + */ + #include "portfwd/portfwd.h" #include "miniwget.h" From 2024a1d8d75383eda5f1ecca21e27714c67e25d8 Mon Sep 17 00:00:00 2001 From: Christian Muehlhaeuser Date: Fri, 18 Mar 2011 21:35:29 +0100 Subject: [PATCH 015/329] * Fixed GPL headers. --- include/tomahawk/infosystem.h | 18 ++++++++++++++++++ include/tomahawk/plugin_includes.h | 18 ++++++++++++++++++ include/tomahawk/tomahawkapp.h | 18 ++++++++++++++++++ include/tomahawk/tomahawkplugin.h | 18 ++++++++++++++++++ 4 files changed, 72 insertions(+) diff --git a/include/tomahawk/infosystem.h b/include/tomahawk/infosystem.h index 983305c47..4b6c02dc5 100644 --- a/include/tomahawk/infosystem.h +++ b/include/tomahawk/infosystem.h @@ -1,3 +1,21 @@ +/* === This file is part of Tomahawk Player - === + * + * Copyright 2010-2011, Christian Muehlhaeuser + * + * Tomahawk is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * Tomahawk is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with Tomahawk. If not, see . + */ + #ifndef TOMAHAWK_INFOSYSTEM_H #define TOMAHAWK_INFOSYSTEM_H diff --git a/include/tomahawk/plugin_includes.h b/include/tomahawk/plugin_includes.h index 77c472446..f8844e05f 100644 --- a/include/tomahawk/plugin_includes.h +++ b/include/tomahawk/plugin_includes.h @@ -1,3 +1,21 @@ +/* === This file is part of Tomahawk Player - === + * + * Copyright 2010-2011, Christian Muehlhaeuser + * + * Tomahawk is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * Tomahawk is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with Tomahawk. If not, see . + */ + #ifndef PLUGIN_INCLUDES_H #define PLUGIN_INCLUDES_H diff --git a/include/tomahawk/tomahawkapp.h b/include/tomahawk/tomahawkapp.h index 36235edd7..c58d95b63 100644 --- a/include/tomahawk/tomahawkapp.h +++ b/include/tomahawk/tomahawkapp.h @@ -1,3 +1,21 @@ +/* === This file is part of Tomahawk Player - === + * + * Copyright 2010-2011, Christian Muehlhaeuser + * + * Tomahawk is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * Tomahawk is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with Tomahawk. If not, see . + */ + #ifndef TOMAHAWKAPP_H #define TOMAHAWKAPP_H diff --git a/include/tomahawk/tomahawkplugin.h b/include/tomahawk/tomahawkplugin.h index 9f5f81d0e..cd4519e8a 100644 --- a/include/tomahawk/tomahawkplugin.h +++ b/include/tomahawk/tomahawkplugin.h @@ -1,3 +1,21 @@ +/* === This file is part of Tomahawk Player - === + * + * Copyright 2010-2011, Christian Muehlhaeuser + * + * Tomahawk is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * Tomahawk is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with Tomahawk. If not, see . + */ + #ifndef TOMAHAWK_PLUGIN_H #define TOMAHAWK_PLUGIN_H From c7c28b0dd702edaeb069562b4f9f087406d6af3b Mon Sep 17 00:00:00 2001 From: Christian Muehlhaeuser Date: Fri, 18 Mar 2011 21:36:33 +0100 Subject: [PATCH 016/329] * Fixed GPL headers. --- src/tomahawkapp_macdelegate.h | 18 ++++++++++++++++++ 1 file changed, 18 insertions(+) diff --git a/src/tomahawkapp_macdelegate.h b/src/tomahawkapp_macdelegate.h index a5884e225..01ef7a401 100644 --- a/src/tomahawkapp_macdelegate.h +++ b/src/tomahawkapp_macdelegate.h @@ -1,3 +1,21 @@ +/* === This file is part of Tomahawk Player - === + * + * Copyright 2010-2011, Christian Muehlhaeuser + * + * 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 . + */ + #import #include "config.h" From a7ef75633a37f88516292b516808c15585521ad7 Mon Sep 17 00:00:00 2001 From: Christian Muehlhaeuser Date: Fri, 18 Mar 2011 21:51:12 +0100 Subject: [PATCH 017/329] * Removed old junk and cleaned up source: Never include tomahawkapp.h in libtomahawk. --- include/tomahawk/tomahawkapp.h | 3 +- include/tomahawk/tomahawkplugin.h | 49 -- src/junk/remoteioconnection.cpp | 104 ---- src/junk/remoteioconnection.h | 56 -- src/junk/remoteiodevice.cpp | 119 ---- src/junk/remoteiodevice.h | 61 -- src/libtomahawk/CMakeLists.txt | 2 - .../databasecommand_createdynamicplaylist.cpp | 1 - .../databasecommand_createplaylist.cpp | 1 - .../dynamic/widgets/CollapsibleControls.cpp | 2 +- .../dynamic/widgets/DynamicControlList.cpp | 4 +- .../dynamic/widgets/DynamicControlWrapper.cpp | 3 +- .../dynamic/widgets/LoadingSpinner.cpp | 4 +- src/libtomahawk/pluginapi.cpp | 64 --- src/libtomahawk/pluginapi.h | 67 --- src/libtomahawk/resolver.h | 8 +- src/libtomahawk/utils/modeltest.cpp | 539 ------------------ src/libtomahawk/utils/modeltest.h | 76 --- src/plugins/fake/CMakeLists.txt | 45 -- src/plugins/fake/fakecollection.cpp | 45 -- src/plugins/fake/fakecollection.h | 41 -- src/plugins/fake/fakeplugin.cpp | 44 -- src/plugins/fake/fakeplugin.h | 49 -- src/transferview.cpp | 1 + 24 files changed, 10 insertions(+), 1378 deletions(-) delete mode 100644 include/tomahawk/tomahawkplugin.h delete mode 100644 src/junk/remoteioconnection.cpp delete mode 100644 src/junk/remoteioconnection.h delete mode 100644 src/junk/remoteiodevice.cpp delete mode 100644 src/junk/remoteiodevice.h delete mode 100644 src/libtomahawk/pluginapi.cpp delete mode 100644 src/libtomahawk/pluginapi.h delete mode 100644 src/libtomahawk/utils/modeltest.cpp delete mode 100644 src/libtomahawk/utils/modeltest.h delete mode 100644 src/plugins/fake/CMakeLists.txt delete mode 100644 src/plugins/fake/fakecollection.cpp delete mode 100644 src/plugins/fake/fakecollection.h delete mode 100644 src/plugins/fake/fakeplugin.cpp delete mode 100644 src/plugins/fake/fakeplugin.h diff --git a/include/tomahawk/tomahawkapp.h b/include/tomahawk/tomahawkapp.h index c58d95b63..7f467ca90 100644 --- a/include/tomahawk/tomahawkapp.h +++ b/include/tomahawk/tomahawkapp.h @@ -34,10 +34,10 @@ #include "QxtHttpServerConnector" #include "QxtHttpSessionManager" -#include "tomahawk/tomahawkplugin.h" #include "typedefs.h" #include "playlist.h" #include "resolver.h" +#include "network/servent.h" #include "utils/tomahawkutils.h" @@ -109,7 +109,6 @@ private: void startHTTP(); QList m_collections; - QList m_plugins; QList m_scriptResolvers; Database* m_database; diff --git a/include/tomahawk/tomahawkplugin.h b/include/tomahawk/tomahawkplugin.h deleted file mode 100644 index cd4519e8a..000000000 --- a/include/tomahawk/tomahawkplugin.h +++ /dev/null @@ -1,49 +0,0 @@ -/* === This file is part of Tomahawk Player - === - * - * Copyright 2010-2011, Christian Muehlhaeuser - * - * Tomahawk is free software: you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation, either version 3 of the License, or - * (at your option) any later version. - * - * Tomahawk is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License - * along with Tomahawk. If not, see . - */ - -#ifndef TOMAHAWK_PLUGIN_H -#define TOMAHAWK_PLUGIN_H - -#include -#include - -#include "pluginapi.h" - -class TomahawkPlugin -{ -public: - TomahawkPlugin(){}; - TomahawkPlugin(Tomahawk::PluginAPI * api) - : m_api(api) {}; - - virtual TomahawkPlugin * factory(Tomahawk::PluginAPI * api) = 0; - - virtual QString name() const = 0; - virtual QString description() const = 0; - -protected: - Tomahawk::PluginAPI * api() const { return m_api; }; - -private: - Tomahawk::PluginAPI * m_api; - -}; - -Q_DECLARE_INTERFACE(TomahawkPlugin, "org.tomahawk.TomahawkPlugin/1.0") - -#endif diff --git a/src/junk/remoteioconnection.cpp b/src/junk/remoteioconnection.cpp deleted file mode 100644 index 2c4fa30f5..000000000 --- a/src/junk/remoteioconnection.cpp +++ /dev/null @@ -1,104 +0,0 @@ -/* === This file is part of Tomahawk Player - === - * - * Copyright 2010-2011, Christian Muehlhaeuser - * - * Tomahawk is free software: you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation, either version 3 of the License, or - * (at your option) any later version. - * - * Tomahawk is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License - * along with Tomahawk. If not, see . - */ - -#include "remoteioconnection.h" -#include - -RemoteIOConnection::RemoteIOConnection(Servent * s, FileTransferSession * fts) - : Connection(s), m_fts(fts) -{ - qDebug() << "CTOR " << id() ; -} - -RemoteIOConnection::~RemoteIOConnection() -{ - qDebug() << "DTOR " << id() ; -} - -QString RemoteIOConnection::id() const -{ - return QString("RemoteIOConnection[%1]").arg(m_fts->fid()); -} - -void RemoteIOConnection::shutdown(bool wait) -{ - Connection::shutdown(wait); - /*if(!wait) - { - Connection::shutdown(wait); - return; - } - qDebug() << id() << " shutdown requested - waiting until we've received all data TODO"; - */ -} - -void RemoteIOConnection::setup() -{ - if(m_fts->type() == FileTransferSession::RECEIVING) - { - qDebug() << "RemoteIOConnection in RX mode"; - return; - } - - qDebug() << "RemoteIOConnection in TX mode, fid:" << m_fts->fid(); - - QString url("/tmp/test.mp3"); - qDebug() << "TODO map fid to file://, hardcoded for now"; - - qDebug() << "Opening for transmission:" << url; - m_readdev = QSharedPointer(new QFile(url)); - m_readdev->open(QIODevice::ReadOnly); - if(!m_readdev->isOpen()) - { - qDebug() << "WARNING file is not readable: " << url; - shutdown(); - } - // send chunks within our event loop, since we're not in our own thread - sendSome(); -} - -void RemoteIOConnection::handleMsg(QByteArray msg) -{ - Q_ASSERT(m_fts->type() == FileTransferSession::RECEIVING); - m_fts->iodevice()->addData(msg); - if(msg.length()==0) qDebug() << "Got 0len msg. end?"; -} - - -Connection * RemoteIOConnection::clone() -{ - Q_ASSERT(false); return 0; -}; - - -void RemoteIOConnection::sendSome() -{ - Q_ASSERT(m_fts->type() == FileTransferSession::SENDING); - if(m_readdev->atEnd()) - { - qDebug() << "Sent all. DONE"; - shutdown(true); - return; - } - QByteArray ba = m_readdev->read(4096); - //qDebug() << "Sending " << ba.length() << " bytes of audiofile"; - sendMsg(ba); - QTimer::singleShot(0, this, SLOT(sendSome())); -} - - diff --git a/src/junk/remoteioconnection.h b/src/junk/remoteioconnection.h deleted file mode 100644 index 62ac4bd98..000000000 --- a/src/junk/remoteioconnection.h +++ /dev/null @@ -1,56 +0,0 @@ -/* === This file is part of Tomahawk Player - === - * - * Copyright 2010-2011, Christian Muehlhaeuser - * - * Tomahawk is free software: you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation, either version 3 of the License, or - * (at your option) any later version. - * - * Tomahawk is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License - * along with Tomahawk. If not, see . - */ - -#ifndef REMOTEIOCONNECTION_H -#define REMOTEIOCONNECTION_H - -#include -#include -#include -#include -#include - -#include "controlconnection.h" -#include "filetransfersession.h" - -class RemoteIOConnection : public Connection -{ - Q_OBJECT -public: - RemoteIOConnection(Servent * s, FileTransferSession * fts); - ~RemoteIOConnection(); - QString id() const; - - - void shutdown(bool wait = false); - void setup(); - void handleMsg(QByteArray msg); - Connection * clone(); - -signals: - -private slots: - void sendSome(); - -private: - - FileTransferSession * m_fts; - QSharedPointer m_readdev; -}; - -#endif // REMOTEIOCONNECTION_H diff --git a/src/junk/remoteiodevice.cpp b/src/junk/remoteiodevice.cpp deleted file mode 100644 index 6b8a63191..000000000 --- a/src/junk/remoteiodevice.cpp +++ /dev/null @@ -1,119 +0,0 @@ -/* === This file is part of Tomahawk Player - === - * - * Copyright 2010-2011, Christian Muehlhaeuser - * - * Tomahawk is free software: you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation, either version 3 of the License, or - * (at your option) any later version. - * - * Tomahawk is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License - * along with Tomahawk. If not, see . - */ - -#include "remoteiodevice.h" - -RemoteIODevice::RemoteIODevice(RemoteIOConnection * c) - : m_eof(false), m_totalAdded(0), m_rioconn(c) -{ - qDebug() << "CTOR RemoteIODevice"; -} - -RemoteIODevice::~RemoteIODevice() -{ - qDebug() << "DTOR RemoteIODevice"; - m_rioconn->shutdown(); -} - -void RemoteIODevice::close() -{ - qDebug() << "RemoteIODevice::close"; - QIODevice::close(); - deleteLater(); -} - -bool RemoteIODevice::open ( OpenMode mode ) -{ - return QIODevice::open(mode & QIODevice::ReadOnly); -} - -qint64 RemoteIODevice::bytesAvailable () const -{ - return m_buffer.length(); -} - -bool RemoteIODevice::isSequential () const -{ - return true; -}; - -bool RemoteIODevice::atEnd() const -{ - return m_eof && m_buffer.length() == 0; -}; - -void RemoteIODevice::addData(QByteArray msg) -{ - m_mut_recv.lock(); - if(msg.length()==0) - { - m_eof=true; - //qDebug() << "addData finished, entire file received. EOF."; - m_mut_recv.unlock(); - m_wait.wakeAll(); - return; - } - else - { - m_buffer.append(msg); - m_totalAdded += msg.length(); - //qDebug() << "RemoteIODevice has seen in total: " << m_totalAdded ; - m_mut_recv.unlock(); - m_wait.wakeAll(); - emit readyRead(); - return; - } -} - -qint64 RemoteIODevice::writeData ( const char * data, qint64 maxSize ) -{ - Q_ASSERT(false); - return 0; -} - -qint64 RemoteIODevice::readData ( char * data, qint64 maxSize ) -{ - //qDebug() << "RemIO::readData, bytes in buffer: " << m_buffer.length(); - m_mut_recv.lock(); - if(m_eof && m_buffer.length() == 0) - { - // eof - qDebug() << "readData called when EOF"; - m_mut_recv.unlock(); - return 0; - } - if(!m_buffer.length())// return 0; - { - //qDebug() << "WARNING readData when buffer is empty"; - m_mut_recv.unlock(); - return 0; - } - int len; - if(maxSize>=m_buffer.length()) // whole buffer - { - len = m_buffer.length(); - memcpy(data, m_buffer.constData(), len); - m_buffer.clear(); - } else { // partial - len = maxSize; - memcpy(data, m_buffer.constData(), len); - m_buffer.remove(0,len); - } - m_mut_recv.unlock(); - return len; -} diff --git a/src/junk/remoteiodevice.h b/src/junk/remoteiodevice.h deleted file mode 100644 index 277bb547f..000000000 --- a/src/junk/remoteiodevice.h +++ /dev/null @@ -1,61 +0,0 @@ -/* === This file is part of Tomahawk Player - === - * - * Copyright 2010-2011, Christian Muehlhaeuser - * - * Tomahawk is free software: you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation, either version 3 of the License, or - * (at your option) any later version. - * - * Tomahawk is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License - * along with Tomahawk. If not, see . - */ - -#ifndef REMOTEIODEVICE_H -#define REMOTEIODEVICE_H -#include -#include -#include -#include -#include -#include "remoteioconnection.h" - -class RemoteIOConnection; - -class RemoteIODevice : public QIODevice -{ - Q_OBJECT -public: - - RemoteIODevice(RemoteIOConnection * c); - ~RemoteIODevice(); - virtual void close(); - virtual bool open ( OpenMode mode ); - qint64 bytesAvailable () const; - virtual bool isSequential () const; - virtual bool atEnd() const; - -public slots: - - void addData(QByteArray msg); - -protected: - - virtual qint64 writeData ( const char * data, qint64 maxSize ); - virtual qint64 readData ( char * data, qint64 maxSize ); - -private: - QByteArray m_buffer; - QMutex m_mut_wait, m_mut_recv; - QWaitCondition m_wait; - bool m_eof; - int m_totalAdded; - - RemoteIOConnection * m_rioconn; -}; -#endif // REMOTEIODEVICE_H diff --git a/src/libtomahawk/CMakeLists.txt b/src/libtomahawk/CMakeLists.txt index bdf495524..e71d6387b 100644 --- a/src/libtomahawk/CMakeLists.txt +++ b/src/libtomahawk/CMakeLists.txt @@ -20,7 +20,6 @@ set( libSources album.cpp collection.cpp playlist.cpp - pluginapi.cpp query.cpp result.cpp source.cpp @@ -157,7 +156,6 @@ set( libHeaders functimeout.h collection.h - pluginapi.h query.h resolver.h result.h diff --git a/src/libtomahawk/database/databasecommand_createdynamicplaylist.cpp b/src/libtomahawk/database/databasecommand_createdynamicplaylist.cpp index 26a2d44fc..f432f3a25 100644 --- a/src/libtomahawk/database/databasecommand_createdynamicplaylist.cpp +++ b/src/libtomahawk/database/databasecommand_createdynamicplaylist.cpp @@ -26,7 +26,6 @@ #include "dynamic/GeneratorInterface.h" #include "network/servent.h" -#include "tomahawk/tomahawkapp.h" #include "playlist/playlistmanager.h" using namespace Tomahawk; diff --git a/src/libtomahawk/database/databasecommand_createplaylist.cpp b/src/libtomahawk/database/databasecommand_createplaylist.cpp index 0d75ae32f..b5bd2ccc2 100644 --- a/src/libtomahawk/database/databasecommand_createplaylist.cpp +++ b/src/libtomahawk/database/databasecommand_createplaylist.cpp @@ -21,7 +21,6 @@ #include #include "network/servent.h" -#include "tomahawk/tomahawkapp.h" #include "playlist/playlistmanager.h" using namespace Tomahawk; diff --git a/src/libtomahawk/playlist/dynamic/widgets/CollapsibleControls.cpp b/src/libtomahawk/playlist/dynamic/widgets/CollapsibleControls.cpp index 1eb22dbfb..36c04a2dd 100644 --- a/src/libtomahawk/playlist/dynamic/widgets/CollapsibleControls.cpp +++ b/src/libtomahawk/playlist/dynamic/widgets/CollapsibleControls.cpp @@ -18,11 +18,11 @@ #include "CollapsibleControls.h" -#include "tomahawk/tomahawkapp.h" #include "DynamicControlList.h" #include "DynamicControlWrapper.h" #include "dynamic/GeneratorInterface.h" #include "dynamic/DynamicControl.h" +#include "utils/tomahawkutils.h" #include #include diff --git a/src/libtomahawk/playlist/dynamic/widgets/DynamicControlList.cpp b/src/libtomahawk/playlist/dynamic/widgets/DynamicControlList.cpp index 6dcc65ab6..5128562af 100644 --- a/src/libtomahawk/playlist/dynamic/widgets/DynamicControlList.cpp +++ b/src/libtomahawk/playlist/dynamic/widgets/DynamicControlList.cpp @@ -25,11 +25,11 @@ #include #include #include +#include #include "DynamicControlWrapper.h" #include "dynamic/GeneratorInterface.h" -#include "tomahawk/tomahawkapp.h" -#include +#include "utils/tomahawkutils.h" using namespace Tomahawk; diff --git a/src/libtomahawk/playlist/dynamic/widgets/DynamicControlWrapper.cpp b/src/libtomahawk/playlist/dynamic/widgets/DynamicControlWrapper.cpp index ba3b482b7..c3ed4fed6 100644 --- a/src/libtomahawk/playlist/dynamic/widgets/DynamicControlWrapper.cpp +++ b/src/libtomahawk/playlist/dynamic/widgets/DynamicControlWrapper.cpp @@ -18,9 +18,10 @@ #include "DynamicControlWrapper.h" -#include "tomahawk/tomahawkapp.h" #include "dynamic/DynamicControl.h" +#include "utils/tomahawkutils.h" +#include #include #include #include diff --git a/src/libtomahawk/playlist/dynamic/widgets/LoadingSpinner.cpp b/src/libtomahawk/playlist/dynamic/widgets/LoadingSpinner.cpp index 1e4b75b05..f3814c238 100644 --- a/src/libtomahawk/playlist/dynamic/widgets/LoadingSpinner.cpp +++ b/src/libtomahawk/playlist/dynamic/widgets/LoadingSpinner.cpp @@ -18,14 +18,14 @@ #include "LoadingSpinner.h" -#include "tomahawk/tomahawkapp.h" - #include #include #include #include #include +#include "utils/tomahawkutils.h" + #define ANIM_LENGTH 300 LoadingSpinner::LoadingSpinner( QWidget* parent ) diff --git a/src/libtomahawk/pluginapi.cpp b/src/libtomahawk/pluginapi.cpp deleted file mode 100644 index d2c2de84b..000000000 --- a/src/libtomahawk/pluginapi.cpp +++ /dev/null @@ -1,64 +0,0 @@ -/* === This file is part of Tomahawk Player - === - * - * Copyright 2010-2011, Christian Muehlhaeuser - * - * Tomahawk is free software: you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation, either version 3 of the License, or - * (at your option) any later version. - * - * Tomahawk is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License - * along with Tomahawk. If not, see . - */ - -#include "pluginapi.h" - -#include "pipeline.h" -#include "sourcelist.h" - -using namespace Tomahawk; - - -PluginAPI::PluginAPI( Pipeline* p ) - : m_pipeline( p ) -{ -} - - -/*void -PluginAPI::reportResults( const QString& qid, const QList& vresults ) -{ - QList< result_ptr > results; - foreach( QVariantMap m, vresults ) - { - result_ptr rp( new Result( m ) ); - results.append( rp ); - } - m_pipeline->reportResults( QID( qid ), results ); -}*/ - - -void -PluginAPI::addSource( source_ptr s ) -{ -// SourceList::instance()->add( s ); -} - - -void -PluginAPI::removeSource( source_ptr s ) -{ -// SourceList::instance()->remove( s ); -} - - -void -PluginAPI::addResolver( Resolver* r ) -{ - Pipeline::instance()->addResolver( r ); -} diff --git a/src/libtomahawk/pluginapi.h b/src/libtomahawk/pluginapi.h deleted file mode 100644 index a895d388c..000000000 --- a/src/libtomahawk/pluginapi.h +++ /dev/null @@ -1,67 +0,0 @@ -/* === This file is part of Tomahawk Player - === - * - * Copyright 2010-2011, Christian Muehlhaeuser - * - * Tomahawk is free software: you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation, either version 3 of the License, or - * (at your option) any later version. - * - * Tomahawk is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License - * along with Tomahawk. If not, see . - */ - -#ifndef PLUGINAPI_H -#define PLUGINAPI_H - -#include -#include - -#include "collection.h" -#include "source.h" - -#include "dllmacro.h" - -/* - This is the only API plugins have access to. - This class must proxy calls to internal functions, because plugins can't - get a pointer to any old object and start calling methods on it. -*/ - -namespace Tomahawk -{ -class Resolver; -class Pipeline; - -class DLLEXPORT PluginAPI : public QObject -{ -Q_OBJECT - -public: - explicit PluginAPI( Pipeline * p ); - - /// call every time new results are available for a running query -// void reportResults( const QString& qid, const QList& results ); - - /// add/remove sources (which have collections) - void addSource( source_ptr s ); - void removeSource( source_ptr s ); - - /// register object capable of searching - void addResolver( Resolver * r ); - - Pipeline * pipeline() const { return m_pipeline; } - -private: - Pipeline * m_pipeline; -}; - - -}; //ns - -#endif // PLUGINAPI_H diff --git a/src/libtomahawk/resolver.h b/src/libtomahawk/resolver.h index c6ff57639..d1b526ffe 100644 --- a/src/libtomahawk/resolver.h +++ b/src/libtomahawk/resolver.h @@ -21,7 +21,7 @@ #include -#include "pluginapi.h" +#include "query.h" #include "dllmacro.h" @@ -35,7 +35,6 @@ */ namespace Tomahawk { -class PluginAPI; class DLLEXPORT Resolver : public QObject { @@ -52,13 +51,8 @@ public: //virtual QWidget * configUI() { return 0; }; //etc - PluginAPI * api() const { return m_api; } - public slots: virtual void resolve( const Tomahawk::query_ptr& query ) = 0; - -private: - PluginAPI * m_api; }; class DLLEXPORT ExternalResolver : public Resolver diff --git a/src/libtomahawk/utils/modeltest.cpp b/src/libtomahawk/utils/modeltest.cpp deleted file mode 100644 index 40d7d282a..000000000 --- a/src/libtomahawk/utils/modeltest.cpp +++ /dev/null @@ -1,539 +0,0 @@ -/**************************************************************************** -** -** Copyright (C) 2007 Trolltech ASA. All rights reserved. -** -** This file is part of the Qt Concurrent project on Trolltech Labs. -** -** This file may be used under the terms of the GNU General Public -** License version 2.0 as published by the Free Software Foundation -** and appearing in the file LICENSE.GPL included in the packaging of -** this file. Please review the following information to ensure GNU -** General Public Licensing requirements will be met: -** http://www.trolltech.com/products/qt/opensource.html -** -** If you are unsure which license is appropriate for your use, please -** review the following information: -** http://www.trolltech.com/products/qt/licensing.html or contact the -** sales department at sales@trolltech.com. -** -** This file is provided AS IS with NO WARRANTY OF ANY KIND, INCLUDING THE -** WARRANTY OF DESIGN, MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE. -** -****************************************************************************/ - -#include - -#include "modeltest.h" - -Q_DECLARE_METATYPE(QModelIndex) - -/*! - Connect to all of the models signals. Whenever anything happens recheck everything. -*/ -ModelTest::ModelTest(QAbstractItemModel *_model, QObject *parent) : QObject(parent), model(_model), fetchingMore(false) -{ - Q_ASSERT(model); - - connect(model, SIGNAL(columnsAboutToBeInserted(const QModelIndex &, int, int)), - this, SLOT(runAllTests())); - connect(model, SIGNAL(columnsAboutToBeRemoved(const QModelIndex &, int, int)), - this, SLOT(runAllTests())); - connect(model, SIGNAL(columnsInserted(const QModelIndex &, int, int)), - this, SLOT(runAllTests())); - connect(model, SIGNAL(columnsRemoved(const QModelIndex &, int, int)), - this, SLOT(runAllTests())); - connect(model, SIGNAL(dataChanged(const QModelIndex &, const QModelIndex &)), - this, SLOT(runAllTests())); - connect(model, SIGNAL(headerDataChanged(Qt::Orientation, int, int)), - this, SLOT(runAllTests())); - connect(model, SIGNAL(layoutAboutToBeChanged ()), this, SLOT(runAllTests())); - connect(model, SIGNAL(layoutChanged ()), this, SLOT(runAllTests())); - connect(model, SIGNAL(modelReset ()), this, SLOT(runAllTests())); - connect(model, SIGNAL(rowsAboutToBeInserted(const QModelIndex &, int, int)), - this, SLOT(runAllTests())); - connect(model, SIGNAL(rowsAboutToBeRemoved(const QModelIndex &, int, int)), - this, SLOT(runAllTests())); - connect(model, SIGNAL(rowsInserted(const QModelIndex &, int, int)), - this, SLOT(runAllTests())); - connect(model, SIGNAL(rowsRemoved(const QModelIndex &, int, int)), - this, SLOT(runAllTests())); - - // Special checks for inserting/removing - connect(model, SIGNAL(layoutAboutToBeChanged()), - this, SLOT(layoutAboutToBeChanged())); - connect(model, SIGNAL(layoutChanged()), - this, SLOT(layoutChanged())); - - connect(model, SIGNAL(rowsAboutToBeInserted(const QModelIndex &, int, int)), - this, SLOT(rowsAboutToBeInserted(const QModelIndex &, int, int))); - connect(model, SIGNAL(rowsAboutToBeRemoved(const QModelIndex &, int, int)), - this, SLOT(rowsAboutToBeRemoved(const QModelIndex &, int, int))); - connect(model, SIGNAL(rowsInserted(const QModelIndex &, int, int)), - this, SLOT(rowsInserted(const QModelIndex &, int, int))); - connect(model, SIGNAL(rowsRemoved(const QModelIndex &, int, int)), - this, SLOT(rowsRemoved(const QModelIndex &, int, int))); - - runAllTests(); -} - -void ModelTest::runAllTests() -{ - if (fetchingMore) - return; - nonDestructiveBasicTest(); - rowCount(); - columnCount(); - hasIndex(); - index(); - parent(); - data(); -} - -/*! - nonDestructiveBasicTest tries to call a number of the basic functions (not all) - to make sure the model doesn't outright segfault, testing the functions that makes sense. -*/ -void ModelTest::nonDestructiveBasicTest() -{ - Q_ASSERT(model->buddy(QModelIndex()) == QModelIndex()); - model->canFetchMore(QModelIndex()); - Q_ASSERT(model->columnCount(QModelIndex()) >= 0); - Q_ASSERT(model->data(QModelIndex()) == QVariant()); - fetchingMore = true; - model->fetchMore(QModelIndex()); - fetchingMore = false; - Qt::ItemFlags flags = model->flags(QModelIndex()); - Q_ASSERT(flags == Qt::ItemIsDropEnabled || flags == 0); - model->hasChildren(QModelIndex()); - model->hasIndex(0, 0); - model->headerData(0, Qt::Horizontal); - model->index(0, 0); - Q_ASSERT(model->index(-1, -1) == QModelIndex()); - model->itemData(QModelIndex()); - QVariant cache; - model->match(QModelIndex(), -1, cache); - model->mimeTypes(); - Q_ASSERT(model->parent(QModelIndex()) == QModelIndex()); - Q_ASSERT(model->rowCount() >= 0); - QVariant variant; - model->setData(QModelIndex(), variant, -1); - model->setHeaderData(-1, Qt::Horizontal, QVariant()); - model->setHeaderData(0, Qt::Horizontal, QVariant()); - model->setHeaderData(999999, Qt::Horizontal, QVariant()); - QMap roles; - model->sibling(0, 0, QModelIndex()); - model->span(QModelIndex()); - model->supportedDropActions(); -} - -/*! - Tests model's implementation of QAbstractItemModel::rowCount() and hasChildren() - - Models that are dynamically populated are not as fully tested here. - */ -void ModelTest::rowCount() -{ - // check top row - QModelIndex topIndex = model->index(0, 0, QModelIndex()); - int rows = model->rowCount(topIndex); - Q_ASSERT(rows >= 0); - if (rows > 0) - Q_ASSERT(model->hasChildren(topIndex) == true); - - QModelIndex secondLevelIndex = model->index(0, 0, topIndex); - if (secondLevelIndex.isValid()) { // not the top level - // check a row count where parent is valid - rows = model->rowCount(secondLevelIndex); - Q_ASSERT(rows >= 0); - if (rows > 0) - Q_ASSERT(model->hasChildren(secondLevelIndex) == true); - } - - // The models rowCount() is tested more extensively in checkChildren(), - // but this catches the big mistakes -} - -/*! - Tests model's implementation of QAbstractItemModel::columnCount() and hasChildren() - */ -void ModelTest::columnCount() -{ - // check top row - QModelIndex topIndex = model->index(0, 0, QModelIndex()); - Q_ASSERT(model->columnCount(topIndex) >= 0); - - // check a column count where parent is valid - QModelIndex childIndex = model->index(0, 0, topIndex); - if (childIndex.isValid()) - Q_ASSERT(model->columnCount(childIndex) >= 0); - - // columnCount() is tested more extensively in checkChildren(), - // but this catches the big mistakes -} - -/*! - Tests model's implementation of QAbstractItemModel::hasIndex() - */ -void ModelTest::hasIndex() -{ - // Make sure that invalid values returns an invalid index - Q_ASSERT(model->hasIndex(-2, -2) == false); - Q_ASSERT(model->hasIndex(-2, 0) == false); - Q_ASSERT(model->hasIndex(0, -2) == false); - - int rows = model->rowCount(); - int columns = model->columnCount(); - - // check out of bounds - Q_ASSERT(model->hasIndex(rows, columns) == false); - Q_ASSERT(model->hasIndex(rows + 1, columns + 1) == false); - - if (rows > 0) - Q_ASSERT(model->hasIndex(0, 0) == true); - - // hasIndex() is tested more extensively in checkChildren(), - // but this catches the big mistakes -} - -/*! - Tests model's implementation of QAbstractItemModel::index() - */ -void ModelTest::index() -{ - // Make sure that invalid values returns an invalid index - Q_ASSERT(model->index(-2, -2) == QModelIndex()); - Q_ASSERT(model->index(-2, 0) == QModelIndex()); - Q_ASSERT(model->index(0, -2) == QModelIndex()); - - int rows = model->rowCount(); - int columns = model->columnCount(); - - if (rows == 0) - return; - - // Catch off by one errors - Q_ASSERT(model->index(rows, columns) == QModelIndex()); - Q_ASSERT(model->index(0, 0).isValid() == true); - - // Make sure that the same index is *always* returned - QModelIndex a = model->index(0, 0); - QModelIndex b = model->index(0, 0); - Q_ASSERT(a == b); - - // index() is tested more extensively in checkChildren(), - // but this catches the big mistakes -} - -/*! - Tests model's implementation of QAbstractItemModel::parent() - */ -void ModelTest::parent() -{ - // Make sure the model wont crash and will return an invalid QModelIndex - // when asked for the parent of an invalid index. - Q_ASSERT(model->parent(QModelIndex()) == QModelIndex()); - - if (model->rowCount() == 0) - return; - - // Column 0 | Column 1 | - // QModelIndex() | | - // \- topIndex | topIndex1 | - // \- childIndex | childIndex1 | - - // Common error test #1, make sure that a top level index has a parent - // that is a invalid QModelIndex. - QModelIndex topIndex = model->index(0, 0, QModelIndex()); - Q_ASSERT(model->parent(topIndex) == QModelIndex()); - - // Common error test #2, make sure that a second level index has a parent - // that is the first level index. - if (model->rowCount(topIndex) > 0) { - QModelIndex childIndex = model->index(0, 0, topIndex); - Q_ASSERT(model->parent(childIndex) == topIndex); - } - - // Common error test #3, the second column should NOT have the same children - // as the first column in a row. - // Usually the second column shouldn't have children. - QModelIndex topIndex1 = model->index(0, 1, QModelIndex()); - if (model->rowCount(topIndex1) > 0) { - QModelIndex childIndex = model->index(0, 0, topIndex); - QModelIndex childIndex1 = model->index(0, 0, topIndex1); - Q_ASSERT(childIndex != childIndex1); - } - - // Full test, walk n levels deep through the model making sure that all - // parent's children correctly specify their parent. - checkChildren(QModelIndex()); -} - -/*! - Called from the parent() test. - - A model that returns an index of parent X should also return X when asking - for the parent of the index. - - This recursive function does pretty extensive testing on the whole model in an - effort to catch edge cases. - - This function assumes that rowCount(), columnCount() and index() already work. - If they have a bug it will point it out, but the above tests should have already - found the basic bugs because it is easier to figure out the problem in - those tests then this one. - */ -void ModelTest::checkChildren(const QModelIndex &parent, int currentDepth) -{ - // First just try walking back up the tree. - QModelIndex p = parent; - while (p.isValid()) - p = p.parent(); - - // For models that are dynamically populated - if (model->canFetchMore(parent)) { - fetchingMore = true; - model->fetchMore(parent); - fetchingMore = false; - } - - int rows = model->rowCount(parent); - int columns = model->columnCount(parent); - - if (rows > 0) - Q_ASSERT(model->hasChildren(parent)); - - // Some further testing against rows(), columns(), and hasChildren() - Q_ASSERT(rows >= 0); - Q_ASSERT(columns >= 0); - if (rows > 0) - Q_ASSERT(model->hasChildren(parent) == true); - - //qDebug() << "parent:" << model->data(parent).toString() << "rows:" << rows - // << "columns:" << columns << "parent column:" << parent.column(); - - Q_ASSERT(model->hasIndex(rows + 1, 0, parent) == false); - for (int r = 0; r < rows; ++r) { - if (model->canFetchMore(parent)) { - fetchingMore = true; - model->fetchMore(parent); - fetchingMore = false; - } - Q_ASSERT(model->hasIndex(r, columns + 1, parent) == false); - for (int c = 0; c < columns; ++c) { - Q_ASSERT(model->hasIndex(r, c, parent) == true); - QModelIndex index = model->index(r, c, parent); - // rowCount() and columnCount() said that it existed... - Q_ASSERT(index.isValid() == true); - - // index() should always return the same index when called twice in a row - QModelIndex modifiedIndex = model->index(r, c, parent); - Q_ASSERT(index == modifiedIndex); - - // Make sure we get the same index if we request it twice in a row - QModelIndex a = model->index(r, c, parent); - QModelIndex b = model->index(r, c, parent); - Q_ASSERT(a == b); - - // Some basic checking on the index that is returned - Q_ASSERT(index.model() == model); - Q_ASSERT(index.row() == r); - Q_ASSERT(index.column() == c); - // While you can technically return a QVariant usually this is a sign - // of an bug in data() Disable if this really is ok in your model. - //Q_ASSERT(model->data(index, Qt::DisplayRole).isValid() == true); - - // If the next test fails here is some somewhat useful debug you play with. - /* - if (model->parent(index) != parent) { - qDebug() << r << c << currentDepth << model->data(index).toString() - << model->data(parent).toString(); - qDebug() << index << parent << model->parent(index); - // And a view that you can even use to show the model. - //QTreeView view; - //view.setModel(model); - //view.show(); - }*/ - - // Check that we can get back our real parent. - QModelIndex p = model->parent(index); - //qDebug() << "child:" << index; - //qDebug() << p; - //qDebug() << parent; - Q_ASSERT(model->parent(index) == parent); - - // recursively go down the children - if (model->hasChildren(index) && currentDepth < 10 ) { - //qDebug() << r << c << "has children" << model->rowCount(index); - checkChildren(index, ++currentDepth); - }/* else { if (currentDepth >= 10) qDebug() << "checked 10 deep"; };*/ - - // make sure that after testing the children that the index doesn't change. - QModelIndex newerIndex = model->index(r, c, parent); - Q_ASSERT(index == newerIndex); - } - } -} - -/*! - Tests model's implementation of QAbstractItemModel::data() - */ -void ModelTest::data() -{ - // Invalid index should return an invalid qvariant - Q_ASSERT(!model->data(QModelIndex()).isValid()); - - if (model->rowCount() == 0) - return; - - // A valid index should have a valid QVariant data - Q_ASSERT(model->index(0, 0).isValid()); - - // shouldn't be able to set data on an invalid index - Q_ASSERT(model->setData(QModelIndex(), QLatin1String("foo"), Qt::DisplayRole) == false); - - // General Purpose roles that should return a QString - QVariant variant = model->data(model->index(0, 0), Qt::ToolTipRole); - if (variant.isValid()) { - Q_ASSERT(qVariantCanConvert(variant)); - } - variant = model->data(model->index(0, 0), Qt::StatusTipRole); - if (variant.isValid()) { - Q_ASSERT(qVariantCanConvert(variant)); - } - variant = model->data(model->index(0, 0), Qt::WhatsThisRole); - if (variant.isValid()) { - Q_ASSERT(qVariantCanConvert(variant)); - } - - // General Purpose roles that should return a QSize - variant = model->data(model->index(0, 0), Qt::SizeHintRole); - if (variant.isValid()) { - Q_ASSERT(qVariantCanConvert(variant)); - } - - // General Purpose roles that should return a QFont - QVariant fontVariant = model->data(model->index(0, 0), Qt::FontRole); - if (fontVariant.isValid()) { - Q_ASSERT(qVariantCanConvert(fontVariant)); - } - - // Check that the alignment is one we know about - QVariant textAlignmentVariant = model->data(model->index(0, 0), Qt::TextAlignmentRole); - if (textAlignmentVariant.isValid()) { - int alignment = textAlignmentVariant.toInt(); - Q_ASSERT(alignment == Qt::AlignLeft || - alignment == Qt::AlignRight || - alignment == Qt::AlignHCenter || - alignment == Qt::AlignJustify || - alignment == Qt::AlignTop || - alignment == Qt::AlignBottom || - alignment == Qt::AlignVCenter || - alignment == Qt::AlignCenter || - alignment == Qt::AlignAbsolute || - alignment == Qt::AlignLeading || - alignment == Qt::AlignTrailing); - } - - // General Purpose roles that should return a QColor - QVariant colorVariant = model->data(model->index(0, 0), Qt::BackgroundColorRole); - if (colorVariant.isValid()) { - Q_ASSERT(qVariantCanConvert(colorVariant)); - } - - colorVariant = model->data(model->index(0, 0), Qt::TextColorRole); - if (colorVariant.isValid()) { - Q_ASSERT(qVariantCanConvert(colorVariant)); - } - - // Check that the "check state" is one we know about. - QVariant checkStateVariant = model->data(model->index(0, 0), Qt::CheckStateRole); - if (checkStateVariant.isValid()) { - int state = checkStateVariant.toInt(); - Q_ASSERT(state == Qt::Unchecked || - state == Qt::PartiallyChecked || - state == Qt::Checked); - } -} - -/*! - Store what is about to be inserted to make sure it actually happens - - \sa rowsInserted() - */ -void ModelTest::rowsAboutToBeInserted(const QModelIndex &parent, int start, int end) -{ - Q_UNUSED(end); - Changing c; - c.parent = parent; - c.oldSize = model->rowCount(parent); - c.last = model->data(model->index(start - 1, 0, parent)); - c.next = model->data(model->index(start, 0, parent)); - insert.push(c); -} - -/*! - Confirm that what was said was going to happen actually did - - \sa rowsAboutToBeInserted() - */ -void ModelTest::rowsInserted(const QModelIndex & parent, int start, int end) -{ - Changing c = insert.pop(); - Q_ASSERT(c.parent == parent); - Q_ASSERT(c.oldSize + (end - start + 1) == model->rowCount(parent)); - Q_ASSERT(c.last == model->data(model->index(start - 1, 0, c.parent))); - /* - if (c.next != model->data(model->index(end + 1, 0, c.parent))) { - qDebug() << start << end; - for (int i=0; i < model->rowCount(); ++i) - qDebug() << model->index(i, 0).data().toString(); - qDebug() << c.next << model->data(model->index(end + 1, 0, c.parent)); - } - */ - Q_ASSERT(c.next == model->data(model->index(end + 1, 0, c.parent))); -} - -void ModelTest::layoutAboutToBeChanged() -{ - for (int i = 0; i < qBound(0, model->rowCount(), 100); ++i) - changing.append(QPersistentModelIndex(model->index(i, 0))); -} - -void ModelTest::layoutChanged() -{ - for (int i = 0; i < changing.count(); ++i) { - QPersistentModelIndex p = changing[i]; - Q_ASSERT(p == model->index(p.row(), p.column(), p.parent())); - } - changing.clear(); -} - -/*! - Store what is about to be inserted to make sure it actually happens - - \sa rowsRemoved() - */ -void ModelTest::rowsAboutToBeRemoved(const QModelIndex &parent, int start, int end) -{ - Changing c; - c.parent = parent; - c.oldSize = model->rowCount(parent); - c.last = model->data(model->index(start - 1, 0, parent)); - c.next = model->data(model->index(end + 1, 0, parent)); - remove.push(c); -} - -/*! - Confirm that what was said was going to happen actually did - - \sa rowsAboutToBeRemoved() - */ -void ModelTest::rowsRemoved(const QModelIndex & parent, int start, int end) -{ - Changing c = remove.pop(); - Q_ASSERT(c.parent == parent); - Q_ASSERT(c.oldSize - (end - start + 1) == model->rowCount(parent)); - Q_ASSERT(c.last == model->data(model->index(start - 1, 0, c.parent))); - Q_ASSERT(c.next == model->data(model->index(start, 0, c.parent))); -} - diff --git a/src/libtomahawk/utils/modeltest.h b/src/libtomahawk/utils/modeltest.h deleted file mode 100644 index 38b6b2bed..000000000 --- a/src/libtomahawk/utils/modeltest.h +++ /dev/null @@ -1,76 +0,0 @@ -/**************************************************************************** -** -** Copyright (C) 2007 Trolltech ASA. All rights reserved. -** -** This file is part of the Qt Concurrent project on Trolltech Labs. -** -** This file may be used under the terms of the GNU General Public -** License version 2.0 as published by the Free Software Foundation -** and appearing in the file LICENSE.GPL included in the packaging of -** this file. Please review the following information to ensure GNU -** General Public Licensing requirements will be met: -** http://www.trolltech.com/products/qt/opensource.html -** -** If you are unsure which license is appropriate for your use, please -** review the following information: -** http://www.trolltech.com/products/qt/licensing.html or contact the -** sales department at sales@trolltech.com. -** -** This file is provided AS IS with NO WARRANTY OF ANY KIND, INCLUDING THE -** WARRANTY OF DESIGN, MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE. -** -****************************************************************************/ - -#ifndef MODELTEST_H -#define MODELTEST_H - -#include -#include -#include - -class ModelTest : public QObject -{ - Q_OBJECT - -public: - ModelTest(QAbstractItemModel *model, QObject *parent = 0); - -private Q_SLOTS: - void nonDestructiveBasicTest(); - void rowCount(); - void columnCount(); - void hasIndex(); - void index(); - void parent(); - void data(); - -protected Q_SLOTS: - void runAllTests(); - void layoutAboutToBeChanged(); - void layoutChanged(); - void rowsAboutToBeInserted(const QModelIndex &parent, int start, int end); - void rowsInserted(const QModelIndex & parent, int start, int end); - void rowsAboutToBeRemoved(const QModelIndex &parent, int start, int end); - void rowsRemoved(const QModelIndex & parent, int start, int end); - -private: - void checkChildren(const QModelIndex &parent, int currentDepth = 0); - - QAbstractItemModel *model; - - struct Changing - { - QModelIndex parent; - int oldSize; - QVariant last; - QVariant next; - }; - QStack insert; - QStack remove; - - bool fetchingMore; - - QList changing; -}; - -#endif diff --git a/src/plugins/fake/CMakeLists.txt b/src/plugins/fake/CMakeLists.txt deleted file mode 100644 index 8fe1f24bf..000000000 --- a/src/plugins/fake/CMakeLists.txt +++ /dev/null @@ -1,45 +0,0 @@ -project( tomahawk ) -cmake_minimum_required(VERSION 2.6) -find_package( Qt4 REQUIRED ) - -include( ${QT_USE_FILE} ) - - -SET(TOMAHAWK_INC_DIR "${CMAKE_CURRENT_SOURCE_DIR}/../../../include/") - -SET(CMAKE_LIBRARY_OUTPUT_DIRECTORY "${TOMAHAWK_INC_DIR}/..") - -set(cpps - fakeplugin.cpp - fakecollection.cpp -) - -set(hs -fakeplugin.h -fakecollection.h -) - -include_directories( - . - .. - ${TOMAHAWK_INC_DIR} - ${CMAKE_CURRENT_BINARY_DIR} - ${QT_INCLUDE_DIR} -) - -qt4_wrap_cpp( mocs ${hs} ) - -ADD_DEFINITIONS(${QT_DEFINITIONS}) -ADD_DEFINITIONS(-DQT_PLUGIN) -#ADD_DEFINITIONS(-DQT_NO_DEBUG) -ADD_DEFINITIONS(-DQT_SHARED) - -add_library(fake SHARED - ${cpps} - ${mocs} -) - -target_link_libraries(fake - ${QT_LIBRARIES} - ${QT_QTSQL_LIBRARIES} -) diff --git a/src/plugins/fake/fakecollection.cpp b/src/plugins/fake/fakecollection.cpp deleted file mode 100644 index 01ebe8a05..000000000 --- a/src/plugins/fake/fakecollection.cpp +++ /dev/null @@ -1,45 +0,0 @@ -/* === This file is part of Tomahawk Player - === - * - * Copyright 2010-2011, Christian Muehlhaeuser - * - * Tomahawk is free software: you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation, either version 3 of the License, or - * (at your option) any later version. - * - * Tomahawk is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License - * along with Tomahawk. If not, see . - */ - -#include "fakecollection.h" -#include "tomahawk/functimeout.h" - -FakeCollection::FakeCollection(QObject *parent) : - Collection("FakeCollection", parent) -{ -} - -void FakeCollection::load() -{ - QList tracks; - QVariantMap t1, t2, t3; - t1["artist"] = "0AAAAAArtist 1"; - t1["track"] = "0TTTTTTrack 1"; - t1["album"] = "0AAAAAAlbum 1"; - t1["url"] = "fake://1"; - t1["filesize"] = 5000000; - t1["duration"] = 300; - t1["bitrate"] = 192; - tracks << t1; - - new Tomahawk::FuncTimeout(5000, boost::bind(&FakeCollection::removeTracks, - this, tracks)); - - addTracks(tracks); - reportFinishedLoading(); - } diff --git a/src/plugins/fake/fakecollection.h b/src/plugins/fake/fakecollection.h deleted file mode 100644 index c75d25b64..000000000 --- a/src/plugins/fake/fakecollection.h +++ /dev/null @@ -1,41 +0,0 @@ -/* === This file is part of Tomahawk Player - === - * - * Copyright 2010-2011, Christian Muehlhaeuser - * - * Tomahawk is free software: you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation, either version 3 of the License, or - * (at your option) any later version. - * - * Tomahawk is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License - * along with Tomahawk. If not, see . - */ - -#ifndef FAKECOLLECTION_H -#define FAKECOLLECTION_H -#include "tomahawk/collection.h" - -class FakeCollection : public Collection -{ -Q_OBJECT -public: - explicit FakeCollection(QObject *parent = 0); - ~FakeCollection() - { - qDebug() << Q_FUNC_INFO; - } - - virtual void load(); - -signals: - -public slots: - -}; - -#endif // FAKECOLLECTION_H diff --git a/src/plugins/fake/fakeplugin.cpp b/src/plugins/fake/fakeplugin.cpp deleted file mode 100644 index d320043cd..000000000 --- a/src/plugins/fake/fakeplugin.cpp +++ /dev/null @@ -1,44 +0,0 @@ -/* === This file is part of Tomahawk Player - === - * - * Copyright 2010-2011, Christian Muehlhaeuser - * - * Tomahawk is free software: you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation, either version 3 of the License, or - * (at your option) any later version. - * - * Tomahawk is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License - * along with Tomahawk. If not, see . - */ - -#include "fakeplugin.h" -#include "fakecollection.h" - -Q_EXPORT_PLUGIN2(fake, FakePlugin) - -FakePlugin::FakePlugin(Tomahawk::PluginAPI* api) - : TomahawkPlugin(api), m_api(api) -{ - init(); -} - -TomahawkPlugin * -FakePlugin::factory(Tomahawk::PluginAPI* api) -{ - return new FakePlugin(api); -} - -void FakePlugin::init() -{ - source_ptr src(new Source("Mr. Fake")); - collection_ptr coll(new FakeCollection); - src->addCollection(coll); - m_api->addSource(src); - coll->load(); -}; - diff --git a/src/plugins/fake/fakeplugin.h b/src/plugins/fake/fakeplugin.h deleted file mode 100644 index aed77ed80..000000000 --- a/src/plugins/fake/fakeplugin.h +++ /dev/null @@ -1,49 +0,0 @@ -/* === This file is part of Tomahawk Player - === - * - * Copyright 2010-2011, Christian Muehlhaeuser - * - * Tomahawk is free software: you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation, either version 3 of the License, or - * (at your option) any later version. - * - * Tomahawk is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License - * along with Tomahawk. If not, see . - */ - -#ifndef TOMAHAWK_LIB_PLUGIN_H -#define TOMAHAWK_LIB_PLUGIN_H -#include - -#include "tomahawk/plugin_includes.h" - -class FakePlugin : public QObject, public TomahawkPlugin -{ - Q_OBJECT - Q_INTERFACES(TomahawkPlugin) - -public: - - FakePlugin(){}; - - FakePlugin(Tomahawk::PluginAPI* api); - TomahawkPlugin * factory(Tomahawk::PluginAPI* api); - QString name() const { return "FakePlugin"; }; - QString description() const { return "Fake stuff, hardcoded"; }; - -private: - - void init(); - - Tomahawk::PluginAPI* m_api; -}; - - - -#endif - diff --git a/src/transferview.cpp b/src/transferview.cpp index 11df37755..8baa3d1f3 100644 --- a/src/transferview.cpp +++ b/src/transferview.cpp @@ -23,6 +23,7 @@ #include "tomahawk/tomahawkapp.h" #include "artist.h" +#include "source.h" #include "network/filetransferconnection.h" #include "network/servent.h" From 2e3492e68d66766cbe4a25cdda26576994823f36 Mon Sep 17 00:00:00 2001 From: Leo Franchi Date: Fri, 18 Mar 2011 17:12:38 -0400 Subject: [PATCH 018/329] only selectively shrink font size on osx --- src/audiocontrols.cpp | 8 +++++++- src/libtomahawk/playlist/trackview.cpp | 8 +++++++- src/sourcetree/sourcetreeview.cpp | 8 +++++++- src/tomahawkapp.cpp | 4 ---- 4 files changed, 21 insertions(+), 7 deletions(-) diff --git a/src/audiocontrols.cpp b/src/audiocontrols.cpp index f7b820e7d..7615645c2 100644 --- a/src/audiocontrols.cpp +++ b/src/audiocontrols.cpp @@ -44,7 +44,13 @@ AudioControls::AudioControls( QWidget* parent ) QFont font( ui->artistTrackLabel->font() ); font.setPixelSize( 12 ); - + +#ifdef Q_WS_MAC + QFont f( font() ); + f.setPointSize( f.pointSize() - 2 ); + setFont( f ); +#endif + ui->artistTrackLabel->setFont( font ); ui->artistTrackLabel->setElideMode( Qt::ElideMiddle ); ui->artistTrackLabel->setType( QueryLabel::ArtistAndTrack ); diff --git a/src/libtomahawk/playlist/trackview.cpp b/src/libtomahawk/playlist/trackview.cpp index 76c685833..0c8b817cb 100644 --- a/src/libtomahawk/playlist/trackview.cpp +++ b/src/libtomahawk/playlist/trackview.cpp @@ -69,7 +69,13 @@ TrackView::TrackView( QWidget* parent ) f.setPointSize( f.pointSize() - 1 ); setFont( f ); #endif - + +#ifdef Q_WS_MAC + QFont f( font() ); + f.setPointSize( f.pointSize() - 2 ); + setFont( f ); +#endif + connect( this, SIGNAL( doubleClicked( QModelIndex ) ), SLOT( onItemActivated( QModelIndex ) ) ); } diff --git a/src/sourcetree/sourcetreeview.cpp b/src/sourcetree/sourcetreeview.cpp index 6a360d97f..b66387afd 100644 --- a/src/sourcetree/sourcetreeview.cpp +++ b/src/sourcetree/sourcetreeview.cpp @@ -81,7 +81,13 @@ SourceTreeView::SourceTreeView( QWidget* parent ) setUniformRowHeights( false ); setIndentation( 16 ); setAnimated( true ); - + +#ifdef Q_WS_MAC + QFont f( font() ); + f.setPointSize( f.pointSize() - 2 ); + setFont( f ); +#endif + setItemDelegate( new SourceDelegate( this ) ); setContextMenuPolicy( Qt::CustomContextMenu ); diff --git a/src/tomahawkapp.cpp b/src/tomahawkapp.cpp index 9c7089e3f..cfc6459e3 100644 --- a/src/tomahawkapp.cpp +++ b/src/tomahawkapp.cpp @@ -196,10 +196,6 @@ TomahawkApp::TomahawkApp( int& argc, char *argv[] ) Tomahawk::setShortcutHandler( static_cast( m_shortcutHandler) ); Tomahawk::setApplicationHandler( this ); - - QFont f( QApplication::font() ); - f.setPointSize( f.pointSize() - 2 ); - QApplication::setFont( f ); #endif // Connect up shortcuts From 5a08e56db484eb43c3cd7181433324f6f2dac958 Mon Sep 17 00:00:00 2001 From: Leo Franchi Date: Fri, 18 Mar 2011 17:40:46 -0400 Subject: [PATCH 019/329] fix compile --- src/libtomahawk/playlist/trackview.cpp | 1 - 1 file changed, 1 deletion(-) diff --git a/src/libtomahawk/playlist/trackview.cpp b/src/libtomahawk/playlist/trackview.cpp index 0c8b817cb..d3d064ec2 100644 --- a/src/libtomahawk/playlist/trackview.cpp +++ b/src/libtomahawk/playlist/trackview.cpp @@ -71,7 +71,6 @@ TrackView::TrackView( QWidget* parent ) #endif #ifdef Q_WS_MAC - QFont f( font() ); f.setPointSize( f.pointSize() - 2 ); setFont( f ); #endif From feed05463fcac5710abf1f32fd527cae8f9eb9aa Mon Sep 17 00:00:00 2001 From: Leo Franchi Date: Fri, 18 Mar 2011 17:44:05 -0400 Subject: [PATCH 020/329] oops and again --- src/audiocontrols.cpp | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/src/audiocontrols.cpp b/src/audiocontrols.cpp index 7615645c2..d160df1d6 100644 --- a/src/audiocontrols.cpp +++ b/src/audiocontrols.cpp @@ -46,9 +46,7 @@ AudioControls::AudioControls( QWidget* parent ) font.setPixelSize( 12 ); #ifdef Q_WS_MAC - QFont f( font() ); - f.setPointSize( f.pointSize() - 2 ); - setFont( f ); + font.setPointSize( font.pointSize() - 2 ); #endif ui->artistTrackLabel->setFont( font ); From 64846c1abdddbdd0c6d13994053b66bd85121798 Mon Sep 17 00:00:00 2001 From: Christian Muehlhaeuser Date: Sat, 19 Mar 2011 03:05:43 +0100 Subject: [PATCH 021/329] * Prevented loading a dupe SIP plugin. --- src/libtomahawk/network/controlconnection.cpp | 2 +- src/sip/SipHandler.cpp | 48 ++++++++++++------- src/sip/SipHandler.h | 4 +- src/sip/twitter/twitter.cpp | 1 - 4 files changed, 36 insertions(+), 19 deletions(-) diff --git a/src/libtomahawk/network/controlconnection.cpp b/src/libtomahawk/network/controlconnection.cpp index d75395fbd..373129e75 100644 --- a/src/libtomahawk/network/controlconnection.cpp +++ b/src/libtomahawk/network/controlconnection.cpp @@ -123,7 +123,7 @@ ControlConnection::registerSource() void ControlConnection::setupDbSyncConnection( bool ondemand ) { - qDebug() << Q_FUNC_INFO << ondemand << m_source->id() << ondemand << m_dbconnkey << m_dbsyncconn << m_registered; + qDebug() << Q_FUNC_INFO << ondemand << m_source->id() << m_dbconnkey << m_dbsyncconn << m_registered; if ( m_dbsyncconn || !m_registered ) return; diff --git a/src/sip/SipHandler.cpp b/src/sip/SipHandler.cpp index 237d9ca78..3b3327082 100644 --- a/src/sip/SipHandler.cpp +++ b/src/sip/SipHandler.cpp @@ -31,6 +31,7 @@ #include "config.h" + SipHandler::SipHandler( QObject* parent ) : QObject( parent ) , m_connected( false ) @@ -80,11 +81,11 @@ SipHandler::findPlugins() #endif QDir libDir( CMAKE_INSTALL_PREFIX "/lib" ); - + QDir lib64Dir( appDir ); lib64Dir.cdUp(); lib64Dir.cd( "lib64" ); - + pluginDirs << appDir << libDir << lib64Dir << QDir( qApp->applicationDirPath() ); foreach ( const QDir& pluginDir, pluginDirs ) { @@ -113,29 +114,31 @@ SipHandler::loadPlugins( const QStringList& paths ) continue; qDebug() << "Trying to load plugin:" << fileName; - - QPluginLoader loader( fileName ); - QObject* plugin = loader.instance(); - if ( plugin ) - { - // Connect via that plugin - qDebug() << "Loaded plugin:" << loader.fileName(); - loadPlugin( plugin ); - } - else - { - qDebug() << "Error loading library:" << loader.errorString(); - } + loadPlugin( fileName ); } } void -SipHandler::loadPlugin( QObject* plugin ) +SipHandler::loadPlugin( const QString& path ) { + QPluginLoader loader( path ); + QObject* plugin = loader.instance(); + if ( !plugin ) + { + qDebug() << "Error loading plugin:" << loader.errorString(); + } + SipPlugin* sip = qobject_cast(plugin); if ( sip ) { + if ( pluginLoaded( sip->name() ) ) + { + qDebug() << "Plugin" << sip->name() << "already loaded! Not loading:" << loader.fileName(); + return; + } + qDebug() << "Loaded plugin:" << loader.fileName(); + QObject::connect( sip, SIGNAL( peerOnline( QString ) ), SLOT( onPeerOnline( QString ) ) ); QObject::connect( sip, SIGNAL( peerOffline( QString ) ), SLOT( onPeerOffline( QString ) ) ); QObject::connect( sip, SIGNAL( msgReceived( QString, QString ) ), SLOT( onMessage( QString, QString ) ) ); @@ -149,6 +152,19 @@ SipHandler::loadPlugin( QObject* plugin ) } +bool +SipHandler::pluginLoaded( const QString& name ) const +{ + foreach( SipPlugin* plugin, m_plugins ) + { + if ( plugin->name() == name ) + return true; + } + + return false; +} + + void SipHandler::connectPlugins( bool startup, const QString &pluginName ) { diff --git a/src/sip/SipHandler.h b/src/sip/SipHandler.h index 4f1d395c6..f127dcc16 100644 --- a/src/sip/SipHandler.h +++ b/src/sip/SipHandler.h @@ -58,8 +58,10 @@ private slots: private: QStringList findPlugins(); + bool pluginLoaded( const QString& name ) const; + void loadPlugins( const QStringList& paths ); - void loadPlugin( QObject* plugin ); + void loadPlugin( const QString& path ); QList< SipPlugin* > m_plugins; bool m_connected; diff --git a/src/sip/twitter/twitter.cpp b/src/sip/twitter/twitter.cpp index 3058f1714..3588802b7 100644 --- a/src/sip/twitter/twitter.cpp +++ b/src/sip/twitter/twitter.cpp @@ -70,7 +70,6 @@ TwitterPlugin::isValid() const QString TwitterPlugin::name() { - qDebug() << "TwitterPlugin returning plugin name " << QString( MYNAME ); return QString( MYNAME ); } From 520806f0bb6c11405bd4a5a220510bbd51e8ac85 Mon Sep 17 00:00:00 2001 From: Christian Muehlhaeuser Date: Sat, 19 Mar 2011 05:09:57 +0100 Subject: [PATCH 022/329] * Don't skip over unsolved but playable tracks. * Don't assert on invalid connections. --- src/libtomahawk/network/controlconnection.cpp | 2 +- src/libtomahawk/playlist/trackproxymodel.cpp | 2 +- src/libtomahawk/query.cpp | 2 ++ src/libtomahawk/query.h | 7 +++++-- 4 files changed, 9 insertions(+), 4 deletions(-) diff --git a/src/libtomahawk/network/controlconnection.cpp b/src/libtomahawk/network/controlconnection.cpp index 373129e75..2e122ca96 100644 --- a/src/libtomahawk/network/controlconnection.cpp +++ b/src/libtomahawk/network/controlconnection.cpp @@ -183,7 +183,7 @@ ControlConnection::dbSyncConnection() if ( !m_dbsyncconn ) { setupDbSyncConnection( true ); - Q_ASSERT( m_dbsyncconn ); +// Q_ASSERT( m_dbsyncconn ); } return m_dbsyncconn; diff --git a/src/libtomahawk/playlist/trackproxymodel.cpp b/src/libtomahawk/playlist/trackproxymodel.cpp index edda2bf36..e271bf40b 100644 --- a/src/libtomahawk/playlist/trackproxymodel.cpp +++ b/src/libtomahawk/playlist/trackproxymodel.cpp @@ -137,7 +137,7 @@ TrackProxyModel::siblingItem( int itemsAway ) { PlItem* item = itemFromIndex( mapToSource( idx ) ); qDebug() << item->query()->toString(); - if ( item && item->query()->solved() ) + if ( item && item->query()->playable() ) { qDebug() << "Next PlaylistItem found:" << item->query()->toString() << item->query()->results().at( 0 )->url(); setCurrentItem( idx ); diff --git a/src/libtomahawk/query.cpp b/src/libtomahawk/query.cpp index 159662be1..7a5b54772 100644 --- a/src/libtomahawk/query.cpp +++ b/src/libtomahawk/query.cpp @@ -44,6 +44,7 @@ Query::get( const QString& artist, const QString& track, const QString& album, c Query::Query( const QString& artist, const QString& track, const QString& album, const QID& qid ) : m_solved( false ) + , m_playable( false ) , m_qid( qid ) , m_artist( artist ) , m_album( album ) @@ -167,6 +168,7 @@ Query::checkResults() { bool becameSolved = false; bool becameUnsolved = true; + m_playable = m_results.count() > 0; // hook up signals, and check solved status foreach( const result_ptr& rp, m_results ) diff --git a/src/libtomahawk/query.h b/src/libtomahawk/query.h index 9f7d53fc4..bfa2b3d1d 100644 --- a/src/libtomahawk/query.h +++ b/src/libtomahawk/query.h @@ -59,8 +59,10 @@ public: /// sorter for list of results static bool resultSorter( const result_ptr &left, const result_ptr& right ); - /// solved=true when a perfect result has been found (score of 1.0) + /// true when a perfect result has been found (score of 1.0) bool solved() const { return m_solved; } + /// true when any result has been found (score may be less than 1.0) + bool playable() const { return m_playable; } unsigned int lastPipelineWeight() const { return m_lastpipelineweight; } void setLastPipelineWeight( unsigned int w ) { m_lastpipelineweight = w; } @@ -79,7 +81,7 @@ public: QString album() const { return m_album; } QString track() const { return m_track; } int duration() const { return m_duration; } - + signals: void resultsAdded( const QList& ); void resultsRemoved( const Tomahawk::result_ptr& ); @@ -105,6 +107,7 @@ private: QList< Tomahawk::result_ptr > m_results; bool m_solved; + bool m_playable; mutable QID m_qid; unsigned int m_lastpipelineweight; From 85826249c901f08215510e1cab6d4678570d5254 Mon Sep 17 00:00:00 2001 From: Christian Muehlhaeuser Date: Sat, 19 Mar 2011 05:15:34 +0100 Subject: [PATCH 023/329] * Prevent Tomahawk from ever trying to connect to itself. --- src/libtomahawk/network/servent.cpp | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/src/libtomahawk/network/servent.cpp b/src/libtomahawk/network/servent.cpp index 6079394f9..495e903de 100644 --- a/src/libtomahawk/network/servent.cpp +++ b/src/libtomahawk/network/servent.cpp @@ -481,6 +481,13 @@ Servent::connectToPeer( const QString& ha, int port, const QString &key, Connect qDebug() << "Servent::connectToPeer:" << ha << ":" << port << thread() << QThread::currentThread(); + if ( ( ha == m_externalAddress.toString() || ha == m_externalHostname ) && + ( port == m_externalPort ) ) + { + qDebug() << "ERROR: Tomahawk won't try to connect to" << ha << ":" << port << ": identified as ourselves."; + return; + } + if( key.length() && conn->firstMessage().isNull() ) { QVariantMap m; From 9de221e9d0302bcba81b0ea3128b9c0ce35a8928 Mon Sep 17 00:00:00 2001 From: Christian Muehlhaeuser Date: Sat, 19 Mar 2011 06:14:22 +0100 Subject: [PATCH 024/329] * Fixed broken signal / slot connection. Re-scanning now live-updates the CollectionFlatModel, again. --- README | 1 - src/libtomahawk/collection.cpp | 1 - src/libtomahawk/collection.h | 4 +++- src/libtomahawk/database/databasecollection.cpp | 5 ++--- src/libtomahawk/database/databasecollection.h | 3 --- .../database/databasecommand_addfiles.cpp | 6 +++--- src/libtomahawk/database/databasecommand_addfiles.h | 2 +- .../database/databasecommand_deletefiles.cpp | 12 ++++++++---- .../database/databasecommand_deletefiles.h | 2 +- 9 files changed, 18 insertions(+), 18 deletions(-) diff --git a/README b/README index 8cfbf2f87..b5814e47a 100644 --- a/README +++ b/README @@ -105,7 +105,6 @@ Dependencies To build the app: ----------------- - $ mkdir build && cd build Pick one of the following two choices. If uncertain pick the second one, you probably want a GUI. diff --git a/src/libtomahawk/collection.cpp b/src/libtomahawk/collection.cpp index a89385fdf..47b42c014 100644 --- a/src/libtomahawk/collection.cpp +++ b/src/libtomahawk/collection.cpp @@ -176,7 +176,6 @@ Collection::setTracks( const QList& tracks ) { qDebug() << Q_FUNC_INFO << tracks.count() << name(); - m_isLoaded = true; m_tracks << tracks; emit tracksAdded( tracks ); } diff --git a/src/libtomahawk/collection.h b/src/libtomahawk/collection.h index 3ed499f84..ca2aa66fd 100644 --- a/src/libtomahawk/collection.h +++ b/src/libtomahawk/collection.h @@ -51,6 +51,7 @@ public: Collection( const source_ptr& source, const QString& name, QObject* parent = 0 ); virtual ~Collection(); + virtual void setLoaded() { m_isLoaded = true; } virtual bool isLoaded() const { return m_isLoaded; } virtual QString name() const; @@ -90,9 +91,10 @@ public slots: void setPlaylists( const QList& plists ); void setDynamicPlaylists( const QList< Tomahawk::dynplaylist_ptr >& dynplists ); - void setTracks( const QList& tracks ); + void setTracks( const QList& tracks ); void delTracks( const QStringList& files ); + void resetTrackCache() { m_tracks.clear(); m_isLoaded = false; } protected: QString m_name; diff --git a/src/libtomahawk/database/databasecollection.cpp b/src/libtomahawk/database/databasecollection.cpp index c90da627c..564180398 100644 --- a/src/libtomahawk/database/databasecollection.cpp +++ b/src/libtomahawk/database/databasecollection.cpp @@ -30,7 +30,6 @@ using namespace Tomahawk; DatabaseCollection::DatabaseCollection( const source_ptr& src, QObject* parent ) : Collection( src, QString( "dbcollection:%1" ).arg( src->userName() ), parent ) - , m_loadedTracks( false ) { } @@ -66,7 +65,7 @@ DatabaseCollection::loadTracks() { qDebug() << Q_FUNC_INFO << source()->userName(); - m_loadedTracks = true; + setLoaded(); DatabaseCommand_AllTracks* cmd = new DatabaseCommand_AllTracks( source()->collection() ); connect( cmd, SIGNAL( tracks( QList ) ), @@ -128,7 +127,7 @@ DatabaseCollection::tracks() { qDebug() << Q_FUNC_INFO; - if ( !m_loadedTracks ) + if ( !isLoaded() ) { loadTracks(); } diff --git a/src/libtomahawk/database/databasecollection.h b/src/libtomahawk/database/databasecollection.h index 74edcad77..5b15913c0 100644 --- a/src/libtomahawk/database/databasecollection.h +++ b/src/libtomahawk/database/databasecollection.h @@ -52,9 +52,6 @@ public slots: private slots: void dynamicPlaylistCreated( const Tomahawk::source_ptr& source, const QVariantList& data ); - -private: - bool m_loadedTracks; }; #endif // DATABASECOLLECTION_H diff --git a/src/libtomahawk/database/databasecommand_addfiles.cpp b/src/libtomahawk/database/databasecommand_addfiles.cpp index 64d96f079..e28164290 100644 --- a/src/libtomahawk/database/databasecommand_addfiles.cpp +++ b/src/libtomahawk/database/databasecommand_addfiles.cpp @@ -63,11 +63,11 @@ DatabaseCommand_AddFiles::postCommitHook() // collection browser will update/fade in etc. Collection* coll = source()->collection().data(); - connect( this, SIGNAL( notify( QList, Tomahawk::collection_ptr ) ), - coll, SLOT( setTracks( QList, Tomahawk::collection_ptr ) ), + connect( this, SIGNAL( notify( QList ) ), + coll, SLOT( setTracks( QList ) ), Qt::QueuedConnection ); - emit notify( m_queries, source()->collection() ); + emit notify( m_queries ); // also re-calc the collection stats, to updates the "X tracks" in the sidebar etc: DatabaseCommand_CollectionStats* cmd = new DatabaseCommand_CollectionStats( source() ); diff --git a/src/libtomahawk/database/databasecommand_addfiles.h b/src/libtomahawk/database/databasecommand_addfiles.h index bb09d72b5..5d82ec9ac 100644 --- a/src/libtomahawk/database/databasecommand_addfiles.h +++ b/src/libtomahawk/database/databasecommand_addfiles.h @@ -55,7 +55,7 @@ public: signals: void done( const QList&, const Tomahawk::collection_ptr& ); - void notify( const QList&, const Tomahawk::collection_ptr& ); + void notify( const QList& ); private: QVariantList m_files; diff --git a/src/libtomahawk/database/databasecommand_deletefiles.cpp b/src/libtomahawk/database/databasecommand_deletefiles.cpp index 03b4b1399..503d3887b 100644 --- a/src/libtomahawk/database/databasecommand_deletefiles.cpp +++ b/src/libtomahawk/database/databasecommand_deletefiles.cpp @@ -41,10 +41,10 @@ DatabaseCommand_DeleteFiles::postCommitHook() // collection browser will update/fade in etc. Collection* coll = source()->collection().data(); - connect( this, SIGNAL( notify( QStringList, Tomahawk::collection_ptr ) ), - coll, SLOT( delTracks( QStringList, Tomahawk::collection_ptr ) ), Qt::QueuedConnection ); + connect( this, SIGNAL( notify( QStringList ) ), + coll, SLOT( delTracks( QStringList ) ), Qt::QueuedConnection ); - emit notify( m_files, source()->collection() ); + emit notify( m_files ); // also re-calc the collection stats, to updates the "X tracks" in the sidebar etc: DatabaseCommand_CollectionStats* cmd = new DatabaseCommand_CollectionStats( source() ); @@ -66,6 +66,7 @@ DatabaseCommand_DeleteFiles::exec( DatabaseImpl* dbi ) int deleted = 0; QVariant srcid = source()->isLocal() ? QVariant( QVariant::Int ) : source()->id(); TomahawkSqlQuery delquery = dbi->newquery(); + QString lastPath; if ( !m_dir.path().isEmpty() && source()->isLocal() ) { @@ -85,7 +86,10 @@ DatabaseCommand_DeleteFiles::exec( DatabaseImpl* dbi ) QFileInfo fi( dirquery.value( 1 ).toString().mid( 7 ) ); // remove file:// if ( fi.absolutePath() != m_dir.absolutePath() ) { - qDebug() << "Skipping subdir:" << fi.absolutePath(); + if ( lastPath != fi.absolutePath() ) + qDebug() << "Skipping subdir:" << fi.absolutePath(); + + lastPath = fi.absolutePath(); continue; } diff --git a/src/libtomahawk/database/databasecommand_deletefiles.h b/src/libtomahawk/database/databasecommand_deletefiles.h index a51e0a34a..4668d0249 100644 --- a/src/libtomahawk/database/databasecommand_deletefiles.h +++ b/src/libtomahawk/database/databasecommand_deletefiles.h @@ -59,7 +59,7 @@ public: signals: void done( const QStringList&, const Tomahawk::collection_ptr& ); - void notify( const QStringList&, const Tomahawk::collection_ptr& ); + void notify( const QStringList& ); private: QDir m_dir; From de2d8aa2fdc07ed052379753d3aa8886d26b6be7 Mon Sep 17 00:00:00 2001 From: Christian Muehlhaeuser Date: Sat, 19 Mar 2011 07:18:28 +0100 Subject: [PATCH 025/329] * Prevent QLabel's default-text from appearing while loading PlaylistManager. --- src/libtomahawk/playlist/infobar/infobar.cpp | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/src/libtomahawk/playlist/infobar/infobar.cpp b/src/libtomahawk/playlist/infobar/infobar.cpp index 488fbf4b4..c1c3a6e12 100644 --- a/src/libtomahawk/playlist/infobar/infobar.cpp +++ b/src/libtomahawk/playlist/infobar/infobar.cpp @@ -50,7 +50,11 @@ InfoBar::InfoBar( QWidget* parent ) ui->captionLabel->setPalette( whitePal ); ui->descriptionLabel->setPalette( whitePal ); - + + ui->captionLabel->setText( QString() ); + ui->descriptionLabel->setText( QString() ); + ui->imageLabel->setText( QString() ); + setAutoFillBackground( true ); } From 5b7775d7a14f78b065c75558cd763f80bcd17b69 Mon Sep 17 00:00:00 2001 From: Christian Muehlhaeuser Date: Sat, 19 Mar 2011 07:28:09 +0100 Subject: [PATCH 026/329] * Fixed m_playable in Query. --- src/libtomahawk/query.cpp | 21 +++++++++++++-------- 1 file changed, 13 insertions(+), 8 deletions(-) diff --git a/src/libtomahawk/query.cpp b/src/libtomahawk/query.cpp index 7a5b54772..750ccec3f 100644 --- a/src/libtomahawk/query.cpp +++ b/src/libtomahawk/query.cpp @@ -168,19 +168,24 @@ Query::checkResults() { bool becameSolved = false; bool becameUnsolved = true; - m_playable = m_results.count() > 0; + m_playable = false; // hook up signals, and check solved status foreach( const result_ptr& rp, m_results ) { - if ( !m_solved && rp->score() > 0.99 && rp->collection()->source()->isOnline() ) + if ( rp->collection()->source()->isOnline() ) { - m_solved = true; - becameSolved = true; - } - if ( rp->score() > 0.99 && rp->collection()->source()->isOnline() ) - { - becameUnsolved = false; + m_playable = true; + + if ( !m_solved && rp->score() > 0.99 ) + { + m_solved = true; + becameSolved = true; + } + if ( rp->score() > 0.99 ) + { + becameUnsolved = false; + } } } From d3643636d2ec946fb9e23495fd629b94114ff8e8 Mon Sep 17 00:00:00 2001 From: Christian Muehlhaeuser Date: Sat, 19 Mar 2011 07:34:34 +0100 Subject: [PATCH 027/329] * Fixed Query crash. --- src/libtomahawk/query.cpp | 13 +++++++------ 1 file changed, 7 insertions(+), 6 deletions(-) diff --git a/src/libtomahawk/query.cpp b/src/libtomahawk/query.cpp index 750ccec3f..5c44a452a 100644 --- a/src/libtomahawk/query.cpp +++ b/src/libtomahawk/query.cpp @@ -173,18 +173,19 @@ Query::checkResults() // hook up signals, and check solved status foreach( const result_ptr& rp, m_results ) { - if ( rp->collection()->source()->isOnline() ) + if ( !rp->collection().isNull() && rp->collection()->source()->isOnline() ) { m_playable = true; - if ( !m_solved && rp->score() > 0.99 ) - { - m_solved = true; - becameSolved = true; - } if ( rp->score() > 0.99 ) { becameUnsolved = false; + + if ( !m_solved ) + { + m_solved = true; + becameSolved = true; + } } } } From d66f78bab0ebe5ee9496b6d5dc4b13feacf81017 Mon Sep 17 00:00:00 2001 From: Christian Muehlhaeuser Date: Sat, 19 Mar 2011 07:41:04 +0100 Subject: [PATCH 028/329] * Advance to HTTP results, again. --- src/libtomahawk/query.cpp | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/src/libtomahawk/query.cpp b/src/libtomahawk/query.cpp index 5c44a452a..b8c48e83c 100644 --- a/src/libtomahawk/query.cpp +++ b/src/libtomahawk/query.cpp @@ -173,6 +173,10 @@ Query::checkResults() // hook up signals, and check solved status foreach( const result_ptr& rp, m_results ) { + if ( rp->score() > 0.0 && rp->collection().isNull() ) + { + m_playable = true; + } if ( !rp->collection().isNull() && rp->collection()->source()->isOnline() ) { m_playable = true; From 3f46501e0fecfba54026d8c34320a15e0fd5706c Mon Sep 17 00:00:00 2001 From: Christian Muehlhaeuser Date: Sat, 19 Mar 2011 08:35:54 +0100 Subject: [PATCH 029/329] * Moved gen_resources.sh to admin/ dir. --- gen_resources.sh => admin/gen_resources.sh | 0 1 file changed, 0 insertions(+), 0 deletions(-) rename gen_resources.sh => admin/gen_resources.sh (100%) diff --git a/gen_resources.sh b/admin/gen_resources.sh similarity index 100% rename from gen_resources.sh rename to admin/gen_resources.sh From 77164b0d89dd0b8f1c8ef8056c2077f2c5f71b05 Mon Sep 17 00:00:00 2001 From: Christian Muehlhaeuser Date: Sat, 19 Mar 2011 09:25:35 +0100 Subject: [PATCH 030/329] * Fixed disconnected signal not being hooked up in gloox jabber. --- src/sip/jabber/jabber.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/sip/jabber/jabber.cpp b/src/sip/jabber/jabber.cpp index fa5800e39..f20024d1e 100644 --- a/src/sip/jabber/jabber.cpp +++ b/src/sip/jabber/jabber.cpp @@ -99,7 +99,7 @@ JabberPlugin::connectPlugin( bool startup ) QObject::connect( p, SIGNAL( msgReceived( QString, QString ) ), SIGNAL( msgReceived( QString, QString ) ) ); QObject::connect( p, SIGNAL( connected() ), SLOT( onConnected() ) ); - QObject::connect( p, SIGNAL( disconnected() ), SIGNAL( onDisconnected() ) ); + QObject::connect( p, SIGNAL( disconnected() ), SLOT( onDisconnected() ) ); QObject::connect( p, SIGNAL( authError( int, QString ) ), SLOT( onAuthError( int, QString ) ) ); p->resolveHostSRV(); From 36997366e14a558fae946b7acbfd930d36c08943 Mon Sep 17 00:00:00 2001 From: Christian Muehlhaeuser Date: Sat, 19 Mar 2011 09:45:02 +0100 Subject: [PATCH 031/329] * SIP Jabber cleanup (gloox). --- src/sip/jabber/jabber.cpp | 47 ++++++++++++++++++++++++--------------- 1 file changed, 29 insertions(+), 18 deletions(-) diff --git a/src/sip/jabber/jabber.cpp b/src/sip/jabber/jabber.cpp index f20024d1e..761335d5f 100644 --- a/src/sip/jabber/jabber.cpp +++ b/src/sip/jabber/jabber.cpp @@ -25,6 +25,7 @@ #include #include + JabberPlugin::JabberPlugin() : p( 0 ), m_menu ( 0 ), @@ -32,6 +33,7 @@ JabberPlugin::JabberPlugin() { } + void JabberPlugin::setProxy( QNetworkProxy* proxy ) { @@ -45,24 +47,28 @@ JabberPlugin::name() return QString( MYNAME ); } + const QString JabberPlugin::friendlyName() { return QString( "Jabber" ); } + const QString JabberPlugin::accountName() { return TomahawkSettings::instance()->jabberUsername(); } + QMenu* JabberPlugin::menu() { return m_menu; } + bool JabberPlugin::connectPlugin( bool startup ) { @@ -103,59 +109,64 @@ JabberPlugin::connectPlugin( bool startup ) QObject::connect( p, SIGNAL( authError( int, QString ) ), SLOT( onAuthError( int, QString ) ) ); p->resolveHostSRV(); - + return true; } -void + +void JabberPlugin::onConnected() { - if( !m_menu ) { - m_menu = new QMenu( QString( "Jabber (").append( accountName() ).append( ")" ) ); - - m_addFriendAction = m_menu->addAction( "Add Friend..." ); - - connect( m_addFriendAction, SIGNAL( triggered() ), - this, SLOT( showAddFriendDialog() ) ) ; - + if ( !m_menu ) + { + m_menu = new QMenu( QString( "Jabber (" ).append( accountName() ).append( ")" ) ); + m_addFriendAction = m_menu->addAction( tr( "Add Friend..." ) ); + + connect( m_addFriendAction, SIGNAL( triggered() ), SLOT( showAddFriendDialog() ) ) ; + emit addMenu( m_menu ); } - + emit connected(); } -void + +void JabberPlugin::onDisconnected() { - if( m_menu && m_addFriendAction ) { + if ( m_menu && m_addFriendAction ) + { emit removeMenu( m_menu ); - + delete m_menu; m_menu = 0; m_addFriendAction = 0; } - + emit disconnected(); } - void JabberPlugin::onAuthError( int code, const QString& message ) { if ( code == gloox::ConnAuthenticationFailed ) + { emit error( SipPlugin::AuthError, message ); + } else + { emit error( SipPlugin::ConnectionError, message ); + } } + void JabberPlugin::showAddFriendDialog() { bool ok; QString id = QInputDialog::getText( 0, tr( "Add Friend" ), - tr( "Enter Jabber ID:" ), QLineEdit::Normal, - "", &ok ); + tr( "Enter Jabber ID:" ), QLineEdit::Normal, "", &ok ); if ( !ok ) return; From 75aef1497a0d85f75368584d562e3034b457c3c3 Mon Sep 17 00:00:00 2001 From: Dominik Schmidt Date: Sat, 19 Mar 2011 19:17:31 +0100 Subject: [PATCH 032/329] Don't check for TagLib twice. We're already checking with optional find macro. --- CMakeLists.txt | 1 - 1 file changed, 1 deletion(-) diff --git a/CMakeLists.txt b/CMakeLists.txt index a9077b33c..78009c025 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -47,7 +47,6 @@ macro_log_feature(TAGLIB_FOUND "TagLib" "Audio Meta-Data Library" "http://develo # we need pthreads too find_package(Threads) -FIND_PACKAGE( Taglib 1.6.0 REQUIRED ) include( CheckTagLibFileName ) check_taglib_filename( COMPLEX_TAGLIB_FILENAME ) From c2c41eaee524ade097e7428335b96fbfa1eff8d2 Mon Sep 17 00:00:00 2001 From: Dominik Schmidt Date: Sat, 19 Mar 2011 19:19:58 +0100 Subject: [PATCH 033/329] Fix inclusion of CLuceneConfig.cmake. Shouldn't we be using CLucene_DIR? --- CMakeModules/FindCLucene.cmake | 9 ++++++++- 1 file changed, 8 insertions(+), 1 deletion(-) diff --git a/CMakeModules/FindCLucene.cmake b/CMakeModules/FindCLucene.cmake index 32bc16b84..5a00f45ab 100644 --- a/CMakeModules/FindCLucene.cmake +++ b/CMakeModules/FindCLucene.cmake @@ -75,7 +75,14 @@ FIND_PATH(CLUCENE_LIBRARY_DIR PATHS ${TRIAL_LIBRARY_PATHS} ${TRIAL_INCLUDE_PATHS} NO_DEFAULT_PATH) IF (CLUCENE_LIBRARY_DIR) MESSAGE(STATUS "Found CLucene library dir: ${CLUCENE_LIBRARY_DIR}") - INCLUDE(${CLUCENE_LIBRARY_DIR}/CLuceneConfig.cmake/CLuceneConfig.cmake) + # include CLuceneConfig/CLuceneConfig.cmake + IF(EXISTS ${CLUCENE_LIBRARY_DIR}/CLuceneConfig.cmake/CLuceneConfig.cmake) + INCLUDE(${CLUCENE_LIBRARY_DIR}/CLuceneConfig.cmake/CLuceneConfig.cmake) + ENDIF(EXISTS ${CLUCENE_LIBRARY_DIR}/CLuceneConfig.cmake/CLuceneConfig.cmake) + # include CLucene/CLuceneConfig.cmake + IF(EXISTS ${CLUCENE_LIBRARY_DIR}/CLucene/CLuceneConfig.cmake) + INCLUDE(${CLUCENE_LIBRARY_DIR}/CLucene/CLuceneConfig.cmake) + ENDIF(EXISTS ${CLUCENE_LIBRARY_DIR}/CLucene/CLuceneConfig.cmake) IF (CLUCENE_VERSION STRLESS "${CLUCENE_MIN_VERSION}") MESSAGE(ERROR " CLucene version ${CLUCENE_VERSION} is less than the required minimum ${CLUCENE_MIN_VERSION}") SET(CLUCENE_GOOD_VERSION FALSE) From 4b24be300b7bd1866672656548acb20c05124b55 Mon Sep 17 00:00:00 2001 From: Dominik Schmidt Date: Sat, 19 Mar 2011 20:10:27 +0100 Subject: [PATCH 034/329] Remove LIBRARY from install DESTINATIONs because it breaks cmake configure for windows --- src/sip/jabber/CMakeLists.txt | 2 +- src/sip/jreen/CMakeLists.txt | 2 +- src/sip/twitter/CMakeLists.txt | 2 +- src/sip/zeroconf/CMakeLists.txt | 2 +- 4 files changed, 4 insertions(+), 4 deletions(-) diff --git a/src/sip/jabber/CMakeLists.txt b/src/sip/jabber/CMakeLists.txt index 1b487cb08..e0cc60d25 100644 --- a/src/sip/jabber/CMakeLists.txt +++ b/src/sip/jabber/CMakeLists.txt @@ -43,4 +43,4 @@ IF( APPLE ) # SET( CMAKE_SHARED_LINKER_FLAGS ${CMAKE_SHARED_LINKER_FLAGS} "-undefined dynamic_lookup" ) ENDIF( APPLE ) -install( TARGETS tomahawk_sipjabber LIBRARY DESTINATION lib ) +install( TARGETS tomahawk_sipjabber DESTINATION lib ) diff --git a/src/sip/jreen/CMakeLists.txt b/src/sip/jreen/CMakeLists.txt index 3432152e4..0c136b6f7 100644 --- a/src/sip/jreen/CMakeLists.txt +++ b/src/sip/jreen/CMakeLists.txt @@ -43,4 +43,4 @@ IF( APPLE ) # SET( CMAKE_SHARED_LINKER_FLAGS ${CMAKE_SHARED_LINKER_FLAGS} "-undefined dynamic_lookup" ) ENDIF( APPLE ) -install( TARGETS tomahawk_sipjreen LIBRARY DESTINATION lib ) +install( TARGETS tomahawk_sipjreen DESTINATION lib ) diff --git a/src/sip/twitter/CMakeLists.txt b/src/sip/twitter/CMakeLists.txt index 82109f0db..45a98b91d 100644 --- a/src/sip/twitter/CMakeLists.txt +++ b/src/sip/twitter/CMakeLists.txt @@ -50,4 +50,4 @@ IF( APPLE ) SET( CMAKE_SHARED_LINKER_FLAGS ${CMAKE_SHARED_LINKER_FLAGS} "-undefined dynamic_lookup" ) ENDIF( APPLE ) -install( TARGETS tomahawk_siptwitter LIBRARY DESTINATION lib ) +install( TARGETS tomahawk_siptwitter DESTINATION lib ) diff --git a/src/sip/zeroconf/CMakeLists.txt b/src/sip/zeroconf/CMakeLists.txt index e8b7814ad..167ae8208 100644 --- a/src/sip/zeroconf/CMakeLists.txt +++ b/src/sip/zeroconf/CMakeLists.txt @@ -41,4 +41,4 @@ IF( APPLE ) # SET( CMAKE_SHARED_LINKER_FLAGS ${CMAKE_SHARED_LINKER_FLAGS} "-undefined dynamic_lookup" ) ENDIF( APPLE ) -install( TARGETS tomahawk_sipzeroconf LIBRARY DESTINATION lib ) +install( TARGETS tomahawk_sipzeroconf DESTINATION lib ) From c43da4b5893c62453997ac9dd99ece9a728ec76e Mon Sep 17 00:00:00 2001 From: Leo Franchi Date: Sat, 19 Mar 2011 19:41:55 -0400 Subject: [PATCH 035/329] Shrink the text as much as we need to fit it. Also, make it smaller on osx here too. --- src/libtomahawk/widgets/overlaywidget.cpp | 25 +++++++++++++++++++++-- 1 file changed, 23 insertions(+), 2 deletions(-) diff --git a/src/libtomahawk/widgets/overlaywidget.cpp b/src/libtomahawk/widgets/overlaywidget.cpp index cbfc44946..5f71de9c7 100644 --- a/src/libtomahawk/widgets/overlaywidget.cpp +++ b/src/libtomahawk/widgets/overlaywidget.cpp @@ -40,6 +40,12 @@ OverlayWidget::OverlayWidget( QWidget* parent ) m_timer.setSingleShot( true ); connect( &m_timer, SIGNAL( timeout() ), this, SLOT( hide() ) ); + +#ifdef Q_WS_MAC + QFont f( font() ); + f.setPointSize( f.pointSize() - 2 ); + setFont( f ); +#endif } @@ -134,11 +140,26 @@ OverlayWidget::paintEvent( QPaintEvent* event ) QTextOption to( Qt::AlignCenter ); to.setWrapMode( QTextOption::WrapAtWordBoundaryOrAnywhere ); + // shrink to fit if needed QFont f( font() ); - f.setPixelSize( FONT_SIZE ); + f.setPointSize( FONT_SIZE ); f.setBold( true ); + QRectF textRect = r.adjusted( 8, 8, -8, -8 ); + qreal availHeight = textRect.height(); + + QFontMetricsF fm( f ); + qreal textHeight = fm.boundingRect( textRect, Qt::AlignCenter | Qt::TextWordWrap, text() ).height(); + while( textHeight > availHeight ) + { + if( f.pointSize() <= 4 ) // don't try harder + break; + + f.setPointSize( f.pointSize() - 1 ); + fm = QFontMetricsF( f ); + textHeight = fm.boundingRect( textRect, Qt::AlignCenter | Qt::TextWordWrap, text() ).height(); + } p.setFont( f ); p.setPen( palette().highlightedText().color() ); - p.drawText( r.adjusted( 16, 16, -16, -16 ), text(), to ); + p.drawText( r.adjusted( 8, 8, -8, -8 ), text(), to ); } From 3925e1bd2f90625a1e7ad14bc2cc52f2fbc44a59 Mon Sep 17 00:00:00 2001 From: Leo Franchi Date: Sat, 19 Mar 2011 19:42:19 -0400 Subject: [PATCH 036/329] Show error if we failed to get a preview of a station --- src/libtomahawk/playlist/dynamic/DynamicModel.cpp | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/src/libtomahawk/playlist/dynamic/DynamicModel.cpp b/src/libtomahawk/playlist/dynamic/DynamicModel.cpp index 3f8047c06..3ce8fa2b9 100644 --- a/src/libtomahawk/playlist/dynamic/DynamicModel.cpp +++ b/src/libtomahawk/playlist/dynamic/DynamicModel.cpp @@ -218,7 +218,9 @@ DynamicModel::filteringTrackResolved( bool successful ) if( m_playlist->mode() == OnDemand ) { m_lastResolvedRow = rowCount( QModelIndex() ); } - } + } + if( m_toResolveList.isEmpty() && rowCount( QModelIndex() ) == 0 ) // we failed + emit trackGenerationFailure( tr( "Could not find a playable track.\n\nPlease change the filters or try again." ) ); } From 5c963912a833f5c8315083dcd3c4517db403ca25 Mon Sep 17 00:00:00 2001 From: Leo Franchi Date: Sat, 19 Mar 2011 20:00:03 -0400 Subject: [PATCH 037/329] Decrease point size for overlay a tad --- src/libtomahawk/widgets/overlaywidget.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/libtomahawk/widgets/overlaywidget.cpp b/src/libtomahawk/widgets/overlaywidget.cpp index 5f71de9c7..3f46f5e11 100644 --- a/src/libtomahawk/widgets/overlaywidget.cpp +++ b/src/libtomahawk/widgets/overlaywidget.cpp @@ -24,7 +24,7 @@ #define CORNER_ROUNDNESS 16.0 #define FADING_DURATION 500 -#define FONT_SIZE 18 +#define FONT_SIZE 16 #define OPACITY 0.86 From e72010b263efcf8ce16d0d7b15afeade6a8bec1e Mon Sep 17 00:00:00 2001 From: Leo Franchi Date: Sat, 19 Mar 2011 20:00:13 -0400 Subject: [PATCH 038/329] Don't show neverending loading slider for empty collections --- src/libtomahawk/playlist/collectionflatmodel.cpp | 2 ++ 1 file changed, 2 insertions(+) diff --git a/src/libtomahawk/playlist/collectionflatmodel.cpp b/src/libtomahawk/playlist/collectionflatmodel.cpp index 769ef337c..83d4a6386 100644 --- a/src/libtomahawk/playlist/collectionflatmodel.cpp +++ b/src/libtomahawk/playlist/collectionflatmodel.cpp @@ -197,6 +197,8 @@ CollectionFlatModel::onTracksAdded( const QList& tracks ) if ( m_tracksToAdd.count() && kickOff ) processTracksToAdd(); + else if ( m_tracksToAdd.isEmpty() && m_loadingCollections.isEmpty() ) + emit loadingFinished(); } From 1f66b31416a649165b4f44a82e3050da91b2897c Mon Sep 17 00:00:00 2001 From: Leo Franchi Date: Sat, 19 Mar 2011 20:34:04 -0400 Subject: [PATCH 039/329] shrink the twitter config dialog a bit. The text didn't fit by default in on OS X. Removed the groupbox as it made huge margins and cut off text. Removed some text, and reworked some of the contents of a qlabel into another qlabel because multiline qlabels fail at layouting --- src/sip/twitter/twitterconfigwidget.cpp | 16 ++- src/sip/twitter/twitterconfigwidget.ui | 183 ++++++++++++------------ 2 files changed, 101 insertions(+), 98 deletions(-) diff --git a/src/sip/twitter/twitterconfigwidget.cpp b/src/sip/twitter/twitterconfigwidget.cpp index 3981ce1b8..70c16a512 100644 --- a/src/sip/twitter/twitterconfigwidget.cpp +++ b/src/sip/twitter/twitterconfigwidget.cpp @@ -47,13 +47,15 @@ TwitterConfigWidget::TwitterConfigWidget(SipPlugin* plugin, QWidget *parent) : { ui->twitterStatusLabel->setText("Status: No saved credentials"); ui->twitterAuthenticateButton->setText( "Authenticate" ); - ui->twitterInstructionsBox->setVisible( false ); + ui->twitterInstructionsInfoLabel->setVisible( false ); + ui->twitterTweetGotTomahawkButton->setVisible( false ); } else { ui->twitterStatusLabel->setText("Status: Credentials saved"); ui->twitterAuthenticateButton->setText( "Re-authenticate" ); - ui->twitterInstructionsBox->setVisible( true ); + ui->twitterInstructionsInfoLabel->setVisible( true ); + ui->twitterTweetGotTomahawkButton->setVisible( true ); } } @@ -77,7 +79,10 @@ TwitterConfigWidget::authenticateTwitter() s->setTwitterOAuthTokenSecret( twitAuth->oauthTokenSecret() ); ui->twitterStatusLabel->setText("Status: Credentials saved"); ui->twitterAuthenticateButton->setText( "Re-authenticate" ); - ui->twitterInstructionsBox->setVisible( true ); + + ui->twitterInstructionsInfoLabel->setVisible( true ); + ui->twitterTweetGotTomahawkButton->setVisible( true ); + TomahawkSettings::instance()->setTwitterCachedFriendsSinceId( 0 ); TomahawkSettings::instance()->setTwitterCachedMentionsSinceId( 0 ); m_plugin->connectPlugin( false ); @@ -90,7 +95,10 @@ TwitterConfigWidget::authenticateTwitter() s->setTwitterOAuthTokenSecret( QString() ); ui->twitterStatusLabel->setText("Status: No saved credentials"); ui->twitterAuthenticateButton->setText( "Authenticate" ); - ui->twitterInstructionsBox->setVisible( false ); + + ui->twitterInstructionsInfoLabel->setVisible( false ); + ui->twitterTweetGotTomahawkButton->setVisible( false ); + QMessageBox::critical( 0, QString("Tweetin' Error"), QString("There was an error validating your authentication") ); } } diff --git a/src/sip/twitter/twitterconfigwidget.ui b/src/sip/twitter/twitterconfigwidget.ui index ad35c9d55..f7109915a 100644 --- a/src/sip/twitter/twitterconfigwidget.ui +++ b/src/sip/twitter/twitterconfigwidget.ui @@ -1,106 +1,101 @@ + - - - TwitterConfigWidget - - Twitter - - + + + 0 + 0 + 795 + 509 + + + + + + + + + + 0 + 0 + + + + Authenticating with Twitter allows you to discover and play music from your Twitter friends running Tomahawk. + + + true + + + + + + + This feature works best when you have set a static host name in the "Network" settings tab under Advanced Settings, but may work even if you do not. Tomahawk uses Direct Messages and this will only work when both Twitter users have followed each other. + + + true + + + + + - - - - - - 0 - 0 - - - - Authenticating with Twitter allows you to discover and play music from your Twitter friends running Tomahawk. - -This feature works best when you have set a static host name in the "Network" settings tab under Advanced Settings, but may work even if you do not. Please note: this discovery uses Direct Messages and will only work when both Twitter users have followed each other. Tomahawk will attempt to "clean up" after itself to keep your Direct Message inbox tidy, but may miss some. - -When you press the button your web browser will launch and take you to Twitter.com to authenticate. You must copy and paste the PIN number into the dialog box that appears. - - - true - - - - - - - - - Status: No saved credentials - - - Qt::AutoText - - - - - - - Authenticate with Twitter - - - - - - - - - - - Instructions + + + Status: No saved credentials + + + Qt::AutoText - - - - - - - How it works is simple: just press the button below to tweet "Got Tomahawk?" and some necessary information. Then be (very) patient. Twitter is an asynchronous protocol so it can take a bit! - -If connections to peers seem to have been lost, just press the button again to re-post a tweet for resynchronization. - - - - true - - - - - - - Press here to have Tomahawk post a tweet - - - - - - - - - Qt::Vertical + + + Authenticate with Twitter - - - 20 - 40 - - - + - - + + + + + + + Here's how it works: just press the button below to tweet "Got Tomahawk?" and some necessary information. Then be (very) patient. Twitter is an asynchronous protocol so it can take a bit! + +If connections to peers seem to have been lost, just press the button again to re-post a tweet for resynchronization. + + + true + + + + + + + Press here to have Tomahawk post a tweet + + + + + + + Qt::Vertical + + + + 20 + 40 + + + + + + + - \ No newline at end of file + From 64331d7fa523a2ca359d041ef5e0eb7fb118ee0d Mon Sep 17 00:00:00 2001 From: Leo Franchi Date: Sat, 19 Mar 2011 20:57:26 -0400 Subject: [PATCH 040/329] set smaller font in the delegate not the view so animations use it too --- src/sourcetree/sourcetreeview.cpp | 17 +++++++++++------ 1 file changed, 11 insertions(+), 6 deletions(-) diff --git a/src/sourcetree/sourcetreeview.cpp b/src/sourcetree/sourcetreeview.cpp index b66387afd..c0012a3dc 100644 --- a/src/sourcetree/sourcetreeview.cpp +++ b/src/sourcetree/sourcetreeview.cpp @@ -82,12 +82,6 @@ SourceTreeView::SourceTreeView( QWidget* parent ) setIndentation( 16 ); setAnimated( true ); -#ifdef Q_WS_MAC - QFont f( font() ); - f.setPointSize( f.pointSize() - 2 ); - setFont( f ); -#endif - setItemDelegate( new SourceDelegate( this ) ); setContextMenuPolicy( Qt::CustomContextMenu ); @@ -507,6 +501,13 @@ SourceDelegate::paint( QPainter* painter, const QStyleOptionViewItem& option, co o2.rect.setX( 0 ); o2.state = option.state; +#ifdef Q_WS_MAC + QFont savedFont = painter->font(); + QFont smaller = savedFont; + smaller.setPointSize( smaller.pointSize() - 1 ); + painter->setFont( smaller ); +#endif + if ( ( option.state & QStyle::State_Enabled ) == QStyle::State_Enabled ) { o.state = QStyle::State_Enabled; @@ -609,4 +610,8 @@ SourceDelegate::paint( QPainter* painter, const QStyleOptionViewItem& option, co { QStyledItemDelegate::paint( painter, o, index ); } + +#ifdef Q_WS_MAC + painter->setFont( savedFont ); +#endif } From ca016dae43480fb5f60fd601b78e84d626b9b0b5 Mon Sep 17 00:00:00 2001 From: Leo Franchi Date: Sat, 19 Mar 2011 21:09:57 -0400 Subject: [PATCH 041/329] paint the playlist rows smaller too --- src/sourcetree/sourcetreeview.cpp | 6 ++---- 1 file changed, 2 insertions(+), 4 deletions(-) diff --git a/src/sourcetree/sourcetreeview.cpp b/src/sourcetree/sourcetreeview.cpp index c0012a3dc..07267d530 100644 --- a/src/sourcetree/sourcetreeview.cpp +++ b/src/sourcetree/sourcetreeview.cpp @@ -497,15 +497,13 @@ void SourceDelegate::paint( QPainter* painter, const QStyleOptionViewItem& option, const QModelIndex& index ) const { QStyleOptionViewItem o = option; - QStyleOptionViewItem o2 = option; - o2.rect.setX( 0 ); - o2.state = option.state; #ifdef Q_WS_MAC QFont savedFont = painter->font(); QFont smaller = savedFont; - smaller.setPointSize( smaller.pointSize() - 1 ); + smaller.setPointSize( smaller.pointSize() - 2 ); painter->setFont( smaller ); + o.font = smaller; #endif if ( ( option.state & QStyle::State_Enabled ) == QStyle::State_Enabled ) From c0f7cf79eb241e98be7deba8678e87abc79d4926 Mon Sep 17 00:00:00 2001 From: Christian Muehlhaeuser Date: Sun, 20 Mar 2011 03:04:44 +0100 Subject: [PATCH 042/329] * Don't include CPack.txt for now. --- src/CMakeLists.txt | 2 +- src/libtomahawk/functimeout.h | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/src/CMakeLists.txt b/src/CMakeLists.txt index b3acb3f05..f28149c70 100644 --- a/src/CMakeLists.txt +++ b/src/CMakeLists.txt @@ -236,4 +236,4 @@ ELSEIF( APPLE ) install( TARGETS tomahawk RUNTIME DESTINATION bin ) ENDIF( APPLE ) -INCLUDE( "CPack.txt" ) +#INCLUDE( "CPack.txt" ) diff --git a/src/libtomahawk/functimeout.h b/src/libtomahawk/functimeout.h index 9a6b97d24..c66134311 100644 --- a/src/libtomahawk/functimeout.h +++ b/src/libtomahawk/functimeout.h @@ -47,7 +47,7 @@ public: : m_func( func ) { //qDebug() << Q_FUNC_INFO; - QTimer::singleShot( ms, this, SLOT(exec() ) ); + QTimer::singleShot( ms, this, SLOT( exec() ) ); }; ~FuncTimeout() From cf38e1cefba23468b58b69651e49ca5297b1ec07 Mon Sep 17 00:00:00 2001 From: Dominik Schmidt Date: Sun, 20 Mar 2011 03:14:08 +0100 Subject: [PATCH 043/329] Update Toolchain-mingw32-openSUSE.cmake to use w64 --- admin/win/Toolchain-mingw32-openSUSE.cmake | 16 +++++++++------- 1 file changed, 9 insertions(+), 7 deletions(-) diff --git a/admin/win/Toolchain-mingw32-openSUSE.cmake b/admin/win/Toolchain-mingw32-openSUSE.cmake index 5e62dc2e5..ceb2ce9be 100644 --- a/admin/win/Toolchain-mingw32-openSUSE.cmake +++ b/admin/win/Toolchain-mingw32-openSUSE.cmake @@ -2,16 +2,18 @@ SET(CMAKE_SYSTEM_NAME Windows) # specify the cross compiler -SET(CMAKE_C_COMPILER i686-pc-mingw32-gcc) -SET(CMAKE_CXX_COMPILER i686-pc-mingw32-g++) +SET(CMAKE_C_COMPILER i686-w64-mingw32-gcc) +SET(CMAKE_CXX_COMPILER i686-w64-mingw32-g++) # where is the target environment containing libraries -SET(CMAKE_FIND_ROOT_PATH /usr/i686-pc-mingw32/sys-root/mingw) +SET(CMAKE_FIND_ROOT_PATH /usr/i686-w64-mingw32/sys-root/mingw) +SET(CMAKE_FIND_ROOT_PATH_MODE_LIBRARY ONLY) +SET(CMAKE_FIND_ROOT_PATH_MODE_INCLUDE ONLY) +SET(CMAKE_FIND_ROOT_PATH_MODE_PROGRAM NEVER) # windres executable for application icon support -set(WINDRES_EXECUTABLE /usr/bin/i686-pc-mingw32-windres) - +SET(WINDRES_EXECUTABLE /usr/bin/i686-w64-mingw32-windres) # libs with broken find modules -set(TAGLIB_FOUND true) -set(TAGLIB_LIBRARIES ${CMAKE_FIND_ROOT_PATH}/lib/libtag.dll.a) +SET(TAGLIB_FOUND true) +SET(TAGLIB_LIBRARIES ${CMAKE_FIND_ROOT_PATH}/lib/libtag.dll.a) From 650f3782b446125021517191efeaa21f3015a23e Mon Sep 17 00:00:00 2001 From: Christian Muehlhaeuser Date: Sun, 20 Mar 2011 03:25:42 +0000 Subject: [PATCH 044/329] * Fixed tomahawk.nsi. --- admin/win/nsi/revision.txt | 2 +- admin/win/nsi/tomahawk.nsi | 8 ++++---- 2 files changed, 5 insertions(+), 5 deletions(-) diff --git a/admin/win/nsi/revision.txt b/admin/win/nsi/revision.txt index efee1f88b..c147342cb 100644 --- a/admin/win/nsi/revision.txt +++ b/admin/win/nsi/revision.txt @@ -1 +1 @@ -78 \ No newline at end of file +81 \ No newline at end of file diff --git a/admin/win/nsi/tomahawk.nsi b/admin/win/nsi/tomahawk.nsi index a9b6e8081..0f59afaea 100755 --- a/admin/win/nsi/tomahawk.nsi +++ b/admin/win/nsi/tomahawk.nsi @@ -15,7 +15,7 @@ ;----------------------------------------------------------------------------- ; Some paths. ;----------------------------------------------------------------------------- -!define MING_PATH "/usr/i686-pc-mingw32/sys-root/mingw" +!define MING_PATH "/usr/i686-w64-mingw32/sys-root/mingw" !define MING_BIN "${MING_PATH}/bin" !define MING_DLL_PATH "${MING_BIN}" !define MING_LIB "${MING_PATH}/lib" @@ -254,9 +254,9 @@ Section "Tomahawk Player" SEC_TOMAHAWK_PLAYER ;Plugins File "${BUILD_PATH}\src\libtomahawk\libtomahawklib.dll" - File "${BUILD_PATH}\libsip_jabber.dll" - File "${BUILD_PATH}\libsip_twitter.dll" - File "${BUILD_PATH}\libsip_zeroconf.dll" + File "${BUILD_PATH}\libtomahawk_sipjabber.dll" + File "${BUILD_PATH}\libtomahawk_siptwitter.dll" + File "${BUILD_PATH}\libtomahawk_sipzeroconf.dll" ;License & release notes. File "${ROOT_PATH}\LICENSE.txt" From fe518a5cbbf168a3142bb30ef77f56ef2c4bc1da Mon Sep 17 00:00:00 2001 From: Christian Muehlhaeuser Date: Sun, 20 Mar 2011 03:47:57 +0100 Subject: [PATCH 045/329] * Fixed cmake install targets. --- src/CMakeLists.txt | 2 +- src/libtomahawk/CMakeLists.txt | 2 +- thirdparty/qxt/qxtweb-standalone/CMakeLists.txt | 2 ++ thirdparty/rtaudio/CMakeLists.txt | 4 ++-- 4 files changed, 6 insertions(+), 4 deletions(-) diff --git a/src/CMakeLists.txt b/src/CMakeLists.txt index f28149c70..eb5206223 100644 --- a/src/CMakeLists.txt +++ b/src/CMakeLists.txt @@ -233,7 +233,7 @@ IF( APPLE ) ENDIF(HAVE_SPARKLE) ELSEIF( APPLE ) - install( TARGETS tomahawk RUNTIME DESTINATION bin ) + INSTALL( TARGETS tomahawk RUNTIME DESTINATION bin ) ENDIF( APPLE ) #INCLUDE( "CPack.txt" ) diff --git a/src/libtomahawk/CMakeLists.txt b/src/libtomahawk/CMakeLists.txt index e71d6387b..bdb4bb4e6 100644 --- a/src/libtomahawk/CMakeLists.txt +++ b/src/libtomahawk/CMakeLists.txt @@ -400,4 +400,4 @@ target_link_libraries( tomahawklib ${CMAKE_THREAD_LIBS_INIT} ) -install( TARGETS tomahawklib DESTINATION lib ) +INSTALL( TARGETS tomahawklib DESTINATION lib ) diff --git a/thirdparty/qxt/qxtweb-standalone/CMakeLists.txt b/thirdparty/qxt/qxtweb-standalone/CMakeLists.txt index ec81d04cb..30da48197 100644 --- a/thirdparty/qxt/qxtweb-standalone/CMakeLists.txt +++ b/thirdparty/qxt/qxtweb-standalone/CMakeLists.txt @@ -102,3 +102,5 @@ target_link_libraries( qxtweb-standalone # ${QT_LIBRARIES} # "${CMAKE_CURRENT_SOURCE_DIR}/libqxtweb-standalone.a" # ) + +INSTALL( TARGETS qxtweb-standalone DESTINATION lib ) diff --git a/thirdparty/rtaudio/CMakeLists.txt b/thirdparty/rtaudio/CMakeLists.txt index 4791afbc9..c97bbcb9c 100644 --- a/thirdparty/rtaudio/CMakeLists.txt +++ b/thirdparty/rtaudio/CMakeLists.txt @@ -38,6 +38,6 @@ target_link_libraries( rtaudio ${AUDIO_LIBS} ) -IF(WIN32) +IF(WIN32 OR APPLE) INSTALL(TARGETS rtaudio ARCHIVE DESTINATION lib) -ENDIF() \ No newline at end of file +ENDIF() From e109415addeb3c75f01a3ba9dc7a82ed9edf8f39 Mon Sep 17 00:00:00 2001 From: Christian Muehlhaeuser Date: Sun, 20 Mar 2011 03:50:19 +0100 Subject: [PATCH 046/329] * Fixed rtaudio install. --- thirdparty/rtaudio/CMakeLists.txt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/thirdparty/rtaudio/CMakeLists.txt b/thirdparty/rtaudio/CMakeLists.txt index c97bbcb9c..29815d3f4 100644 --- a/thirdparty/rtaudio/CMakeLists.txt +++ b/thirdparty/rtaudio/CMakeLists.txt @@ -39,5 +39,5 @@ target_link_libraries( rtaudio ) IF(WIN32 OR APPLE) -INSTALL(TARGETS rtaudio ARCHIVE DESTINATION lib) +INSTALL( TARGETS rtaudio DESTINATION lib ) ENDIF() From eea1a5e0b6366f3e05c5e4fa85b370483c88b8b6 Mon Sep 17 00:00:00 2001 From: Christian Muehlhaeuser Date: Sun, 20 Mar 2011 03:54:45 +0100 Subject: [PATCH 047/329] * Final CMakeLists.txt fixes? --- src/CMakeLists.txt | 4 ++-- thirdparty/rtaudio/CMakeLists.txt | 2 +- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/src/CMakeLists.txt b/src/CMakeLists.txt index eb5206223..94d603af4 100644 --- a/src/CMakeLists.txt +++ b/src/CMakeLists.txt @@ -232,8 +232,8 @@ IF( APPLE ) INSTALL(DIRECTORY "${SPARKLE}/Versions/Current/Resources" DESTINATION "${CMAKE_BINARY_DIR}/tomahawk.app/Contents/Frameworks/Sparkle.framework") ENDIF(HAVE_SPARKLE) -ELSEIF( APPLE ) - INSTALL( TARGETS tomahawk RUNTIME DESTINATION bin ) ENDIF( APPLE ) +INSTALL( TARGETS tomahawk RUNTIME DESTINATION bin ) + #INCLUDE( "CPack.txt" ) diff --git a/thirdparty/rtaudio/CMakeLists.txt b/thirdparty/rtaudio/CMakeLists.txt index 29815d3f4..a0a4eb704 100644 --- a/thirdparty/rtaudio/CMakeLists.txt +++ b/thirdparty/rtaudio/CMakeLists.txt @@ -38,6 +38,6 @@ target_link_libraries( rtaudio ${AUDIO_LIBS} ) -IF(WIN32 OR APPLE) +IF(WIN32) INSTALL( TARGETS rtaudio DESTINATION lib ) ENDIF() From eb72bb4b38006fe0c196ca495121d74f22d11316 Mon Sep 17 00:00:00 2001 From: Christian Muehlhaeuser Date: Sun, 20 Mar 2011 04:01:39 +0100 Subject: [PATCH 048/329] * cmake fix for OS X. --- src/CMakeLists.txt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/CMakeLists.txt b/src/CMakeLists.txt index 94d603af4..68b3280bd 100644 --- a/src/CMakeLists.txt +++ b/src/CMakeLists.txt @@ -234,6 +234,6 @@ IF( APPLE ) ENDIF(HAVE_SPARKLE) ENDIF( APPLE ) -INSTALL( TARGETS tomahawk RUNTIME DESTINATION bin ) +INSTALL( TARGETS tomahawk BUNDLE DESTINATION . RUNTIME DESTINATION bin ) #INCLUDE( "CPack.txt" ) From c046119a6c445df3a6a1618d06f5f0d387e7b3ed Mon Sep 17 00:00:00 2001 From: Christian Muehlhaeuser Date: Sun, 20 Mar 2011 04:27:05 +0100 Subject: [PATCH 049/329] * Fixed compiler warnings. --- src/libtomahawk/playlist/collectionview.cpp | 1 + src/libtomahawk/playlist/dynamic/widgets/DynamicWidget.cpp | 1 + src/libtomahawk/playlist/playlistview.cpp | 1 + 3 files changed, 3 insertions(+) diff --git a/src/libtomahawk/playlist/collectionview.cpp b/src/libtomahawk/playlist/collectionview.cpp index 6c16f4330..d67b02535 100644 --- a/src/libtomahawk/playlist/collectionview.cpp +++ b/src/libtomahawk/playlist/collectionview.cpp @@ -117,4 +117,5 @@ bool CollectionView::jumpToCurrentTrack() { scrollTo( proxyModel()->currentItem(), QAbstractItemView::PositionAtCenter ); + return true; } diff --git a/src/libtomahawk/playlist/dynamic/widgets/DynamicWidget.cpp b/src/libtomahawk/playlist/dynamic/widgets/DynamicWidget.cpp index f7e0dd855..4615b37b5 100644 --- a/src/libtomahawk/playlist/dynamic/widgets/DynamicWidget.cpp +++ b/src/libtomahawk/playlist/dynamic/widgets/DynamicWidget.cpp @@ -398,4 +398,5 @@ bool DynamicWidget::jumpToCurrentTrack() { m_view->scrollTo( m_view->proxyModel()->currentItem(), QAbstractItemView::PositionAtCenter ); + return true; } diff --git a/src/libtomahawk/playlist/playlistview.cpp b/src/libtomahawk/playlist/playlistview.cpp index c7fbd5599..9b66257ad 100644 --- a/src/libtomahawk/playlist/playlistview.cpp +++ b/src/libtomahawk/playlist/playlistview.cpp @@ -152,4 +152,5 @@ bool PlaylistView::jumpToCurrentTrack() { scrollTo( proxyModel()->currentItem(), QAbstractItemView::PositionAtCenter ); + return true; } From f5c73d765afeb1c829b2e354d794f682779d0cbc Mon Sep 17 00:00:00 2001 From: Christian Muehlhaeuser Date: Sun, 20 Mar 2011 05:11:48 +0100 Subject: [PATCH 050/329] * Removed obsolete CPack.txt include from CMakeLists.linux.txt. --- src/CMakeLists.linux.txt | 2 -- 1 file changed, 2 deletions(-) diff --git a/src/CMakeLists.linux.txt b/src/CMakeLists.linux.txt index eacb2d368..54a3ac268 100644 --- a/src/CMakeLists.linux.txt +++ b/src/CMakeLists.linux.txt @@ -11,5 +11,3 @@ ELSE() gnutls ) ENDIF() - -#include( "CPack.txt" ) From b87f638e87642262c9a3c85efabff1718cc12b69 Mon Sep 17 00:00:00 2001 From: Christian Muehlhaeuser Date: Sun, 20 Mar 2011 07:12:17 +0000 Subject: [PATCH 051/329] * Updated tomahawk.nsi. --- admin/win/nsi/revision.txt | 2 +- admin/win/nsi/tomahawk.nsi | 4 +++- 2 files changed, 4 insertions(+), 2 deletions(-) diff --git a/admin/win/nsi/revision.txt b/admin/win/nsi/revision.txt index c147342cb..615be700b 100644 --- a/admin/win/nsi/revision.txt +++ b/admin/win/nsi/revision.txt @@ -1 +1 @@ -81 \ No newline at end of file +85 \ No newline at end of file diff --git a/admin/win/nsi/tomahawk.nsi b/admin/win/nsi/tomahawk.nsi index 0f59afaea..13a055394 100755 --- a/admin/win/nsi/tomahawk.nsi +++ b/admin/win/nsi/tomahawk.nsi @@ -284,7 +284,7 @@ Section "Tomahawk Player" SEC_TOMAHAWK_PLAYER ;Audio stuff File "${BUILD_PATH}\thirdparty\rtaudio\librtaudio.dll" - ;File "${MING_DLL_PATH}\libmad.dll" + File "${MING_DLL_PATH}\libmad-0.dll" File "${MING_DLL_PATH}\libogg-0.dll" File "${MING_DLL_PATH}\libvorbisfile-3.dll" File "${MING_DLL_PATH}\libvorbis-0.dll" @@ -304,6 +304,8 @@ Section "Tomahawk Player" SEC_TOMAHAWK_PLAYER File "${MING_LIB}\libclucene-core.dll" File "${MING_LIB}\libclucene-shared.dll" + File "${MING_BIN}\libqtsparkle.dll" + File "${BUILD_PATH}\thirdparty\qxt\qxtweb-standalone\libqxtweb-standalone.dll" File "${BUILD_PATH}\thirdparty\jdns\libtomahawk_jdns.dll" File "${BUILD_PATH}\thirdparty\qtweetlib\libtomahawk_qtweetlib.dll" From 4e4d304b01b5d1346660f9798f8ab099de2d154a Mon Sep 17 00:00:00 2001 From: Dominik Schmidt Date: Sun, 20 Mar 2011 19:53:27 +0100 Subject: [PATCH 052/329] Respect 64 when installing libraries --- src/CPack.txt | 4 ++-- src/libtomahawk/CMakeLists.txt | 2 +- src/sip/jabber/CMakeLists.txt | 2 +- src/sip/jreen/CMakeLists.txt | 2 +- src/sip/twitter/CMakeLists.txt | 2 +- src/sip/zeroconf/CMakeLists.txt | 2 +- thirdparty/alsa-playback/CMakeLists.txt | 2 +- thirdparty/jdns/CMakeLists.txt | 2 +- thirdparty/libportfwd/CMakeLists.txt | 2 +- thirdparty/qtweetlib/CMakeLists.txt | 2 +- thirdparty/qxt/qxtweb-standalone/CMakeLists.txt | 2 +- thirdparty/rtaudio/CMakeLists.txt | 2 +- 12 files changed, 13 insertions(+), 13 deletions(-) diff --git a/src/CPack.txt b/src/CPack.txt index 46174a970..37c2e1cc6 100644 --- a/src/CPack.txt +++ b/src/CPack.txt @@ -58,8 +58,8 @@ SET(CPACK_DEBIAN_PACKAGE_DEPENDS "libqtgui4 (>=4:4.7.0-0ubuntu1), libtag1c2a (>= INSTALL( TARGETS tomahawk DESTINATION bin RUNTIME DESTINATION bin -# LIBRARY DESTINATION lib -# ARCHIVE DESTINATION lib +# LIBRARY DESTINATION lib${LIB_SUFFIX} +# ARCHIVE DESTINATION lib${LIB_SUFFIX} ) INSTALL( diff --git a/src/libtomahawk/CMakeLists.txt b/src/libtomahawk/CMakeLists.txt index bdb4bb4e6..d93171a9d 100644 --- a/src/libtomahawk/CMakeLists.txt +++ b/src/libtomahawk/CMakeLists.txt @@ -400,4 +400,4 @@ target_link_libraries( tomahawklib ${CMAKE_THREAD_LIBS_INIT} ) -INSTALL( TARGETS tomahawklib DESTINATION lib ) +INSTALL( TARGETS tomahawklib DESTINATION lib${LIB_SUFFIX} ) diff --git a/src/sip/jabber/CMakeLists.txt b/src/sip/jabber/CMakeLists.txt index e0cc60d25..56ff17970 100644 --- a/src/sip/jabber/CMakeLists.txt +++ b/src/sip/jabber/CMakeLists.txt @@ -43,4 +43,4 @@ IF( APPLE ) # SET( CMAKE_SHARED_LINKER_FLAGS ${CMAKE_SHARED_LINKER_FLAGS} "-undefined dynamic_lookup" ) ENDIF( APPLE ) -install( TARGETS tomahawk_sipjabber DESTINATION lib ) +install( TARGETS tomahawk_sipjabber DESTINATION lib${LIB_SUFFIX} ) diff --git a/src/sip/jreen/CMakeLists.txt b/src/sip/jreen/CMakeLists.txt index 0c136b6f7..df2a4f7b6 100644 --- a/src/sip/jreen/CMakeLists.txt +++ b/src/sip/jreen/CMakeLists.txt @@ -43,4 +43,4 @@ IF( APPLE ) # SET( CMAKE_SHARED_LINKER_FLAGS ${CMAKE_SHARED_LINKER_FLAGS} "-undefined dynamic_lookup" ) ENDIF( APPLE ) -install( TARGETS tomahawk_sipjreen DESTINATION lib ) +install( TARGETS tomahawk_sipjreen DESTINATION lib${LIB_SUFFIX} ) diff --git a/src/sip/twitter/CMakeLists.txt b/src/sip/twitter/CMakeLists.txt index 45a98b91d..bb5b26346 100644 --- a/src/sip/twitter/CMakeLists.txt +++ b/src/sip/twitter/CMakeLists.txt @@ -50,4 +50,4 @@ IF( APPLE ) SET( CMAKE_SHARED_LINKER_FLAGS ${CMAKE_SHARED_LINKER_FLAGS} "-undefined dynamic_lookup" ) ENDIF( APPLE ) -install( TARGETS tomahawk_siptwitter DESTINATION lib ) +install( TARGETS tomahawk_siptwitter DESTINATION lib${LIB_SUFFIX} ) diff --git a/src/sip/zeroconf/CMakeLists.txt b/src/sip/zeroconf/CMakeLists.txt index 167ae8208..199704dff 100644 --- a/src/sip/zeroconf/CMakeLists.txt +++ b/src/sip/zeroconf/CMakeLists.txt @@ -41,4 +41,4 @@ IF( APPLE ) # SET( CMAKE_SHARED_LINKER_FLAGS ${CMAKE_SHARED_LINKER_FLAGS} "-undefined dynamic_lookup" ) ENDIF( APPLE ) -install( TARGETS tomahawk_sipzeroconf DESTINATION lib ) +install( TARGETS tomahawk_sipzeroconf DESTINATION lib${LIB_SUFFIX} ) diff --git a/thirdparty/alsa-playback/CMakeLists.txt b/thirdparty/alsa-playback/CMakeLists.txt index 676ed83e1..4a4e98d99 100644 --- a/thirdparty/alsa-playback/CMakeLists.txt +++ b/thirdparty/alsa-playback/CMakeLists.txt @@ -41,4 +41,4 @@ target_link_libraries( alsaplayback ${AUDIO_LIBS} ) -#INSTALL(TARGETS alsaplayback ARCHIVE DESTINATION lib) +#INSTALL(TARGETS alsaplayback ARCHIVE DESTINATION lib${LIB_SUFFIX}) diff --git a/thirdparty/jdns/CMakeLists.txt b/thirdparty/jdns/CMakeLists.txt index 59f820b93..85667cb7e 100644 --- a/thirdparty/jdns/CMakeLists.txt +++ b/thirdparty/jdns/CMakeLists.txt @@ -49,4 +49,4 @@ target_link_libraries(tomahawk_jdns SET_TARGET_PROPERTIES( tomahawk_jdns PROPERTIES DEFINE_SYMBOL MAKE_JDNS_LIB ) -INSTALL(TARGETS tomahawk_jdns DESTINATION lib) +INSTALL(TARGETS tomahawk_jdns DESTINATION lib${LIB_SUFFIX}) diff --git a/thirdparty/libportfwd/CMakeLists.txt b/thirdparty/libportfwd/CMakeLists.txt index ac1d881ce..c301bf95b 100644 --- a/thirdparty/libportfwd/CMakeLists.txt +++ b/thirdparty/libportfwd/CMakeLists.txt @@ -46,6 +46,6 @@ ENDIF() # ) #TARGET_LINK_LIBRARIES(portfwd-demo portfwd) -# INSTALL(TARGETS portfwd ARCHIVE DESTINATION lib) +# INSTALL(TARGETS portfwd ARCHIVE DESTINATION lib${LIB_SUFFIX}) #INSTALL(TARGETS portfwd-demo RUNTIME DESTINATION bin) #INSTALL(DIRECTORY include/portfwd DESTINATION include PATTERN "*~" EXCLUDE) diff --git a/thirdparty/qtweetlib/CMakeLists.txt b/thirdparty/qtweetlib/CMakeLists.txt index 2f93871af..c68f22031 100644 --- a/thirdparty/qtweetlib/CMakeLists.txt +++ b/thirdparty/qtweetlib/CMakeLists.txt @@ -180,6 +180,6 @@ target_link_libraries(tomahawk_qtweetlib INCLUDE( ${CMAKE_CURRENT_SOURCE_DIR}/twitter-api-keys ) -INSTALL(TARGETS tomahawk_qtweetlib DESTINATION lib) +INSTALL(TARGETS tomahawk_qtweetlib DESTINATION lib${LIB_SUFFIX}) diff --git a/thirdparty/qxt/qxtweb-standalone/CMakeLists.txt b/thirdparty/qxt/qxtweb-standalone/CMakeLists.txt index 30da48197..b1f51d1ba 100644 --- a/thirdparty/qxt/qxtweb-standalone/CMakeLists.txt +++ b/thirdparty/qxt/qxtweb-standalone/CMakeLists.txt @@ -103,4 +103,4 @@ target_link_libraries( qxtweb-standalone # "${CMAKE_CURRENT_SOURCE_DIR}/libqxtweb-standalone.a" # ) -INSTALL( TARGETS qxtweb-standalone DESTINATION lib ) +INSTALL( TARGETS qxtweb-standalone DESTINATION lib${LIB_SUFFIX} ) diff --git a/thirdparty/rtaudio/CMakeLists.txt b/thirdparty/rtaudio/CMakeLists.txt index a0a4eb704..a65914d4d 100644 --- a/thirdparty/rtaudio/CMakeLists.txt +++ b/thirdparty/rtaudio/CMakeLists.txt @@ -39,5 +39,5 @@ target_link_libraries( rtaudio ) IF(WIN32) -INSTALL( TARGETS rtaudio DESTINATION lib ) +INSTALL( TARGETS rtaudio DESTINATION lib${LIB_SUFFIX} ) ENDIF() From d7255942032e566418026f2b0ccba5873fac549f Mon Sep 17 00:00:00 2001 From: Dominik Schmidt Date: Mon, 21 Mar 2011 03:01:54 +0100 Subject: [PATCH 053/329] Install ./admin/win to share/tomahawk/admin if building for windows --- CMakeLists.txt | 1 + admin/CMakeLists.txt | 3 +++ 2 files changed, 4 insertions(+) create mode 100644 admin/CMakeLists.txt diff --git a/CMakeLists.txt b/CMakeLists.txt index 78009c025..f20d19228 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -104,3 +104,4 @@ ENDIF( NOT APPLE ) ADD_SUBDIRECTORY( thirdparty ) ADD_SUBDIRECTORY( src/libtomahawk ) ADD_SUBDIRECTORY( src ) +ADD_SUBDIRECTORY( admin ) diff --git a/admin/CMakeLists.txt b/admin/CMakeLists.txt new file mode 100644 index 000000000..48db60b20 --- /dev/null +++ b/admin/CMakeLists.txt @@ -0,0 +1,3 @@ +IF(WIN32) + INSTALL(DIRECTORY win DESTINATION share/tomahawk/admin ) +ENDIF(WIN32) \ No newline at end of file From 04cbd72ef67b14014fc53c3ce49d015bba4fd6b8 Mon Sep 17 00:00:00 2001 From: Christian Muehlhaeuser Date: Mon, 21 Mar 2011 03:11:37 +0100 Subject: [PATCH 054/329] * Hopefully fixed delete playlist crash. --- src/libtomahawk/collection.cpp | 6 +++--- src/sourcetree/sourcetreeitem.cpp | 4 ++-- 2 files changed, 5 insertions(+), 5 deletions(-) diff --git a/src/libtomahawk/collection.cpp b/src/libtomahawk/collection.cpp index 47b42c014..e02acd260 100644 --- a/src/libtomahawk/collection.cpp +++ b/src/libtomahawk/collection.cpp @@ -110,10 +110,10 @@ Collection::deleteDynamicPlaylist( const Tomahawk::dynplaylist_ptr& p ) QList todelete; todelete << p; m_dynplaylists.removeAll( p ); - + qDebug() << Q_FUNC_INFO << "Collection name" << name() - << "from source id" << source()->id() - << "numplaylists:" << m_playlists.length(); + << "from source id" << source()->id() + << "numplaylists:" << m_playlists.length(); emit dynamicPlaylistsDeleted( todelete ); } diff --git a/src/sourcetree/sourcetreeitem.cpp b/src/sourcetree/sourcetreeitem.cpp index 1a8728931..d2cd3adc1 100644 --- a/src/sourcetree/sourcetreeitem.cpp +++ b/src/sourcetree/sourcetreeitem.cpp @@ -139,8 +139,8 @@ SourceTreeItem::onPlaylistsDeleted( const QList& playlists ) if ( type == SourcesModel::PlaylistSource && ptr == qlonglong( pl->data() ) ) { - m_playlists.removeAll( p ); item->removeRow( i ); + m_playlists.removeAll( p ); } } } @@ -244,8 +244,8 @@ SourceTreeItem::onDynamicPlaylistsDeleted( const QList< dynplaylist_ptr >& playl //qDebug() << "Deleting dynamic playlist:" << pl->isNull(); if ( type == SourcesModel::DynamicPlaylistSource && ptr == qlonglong( pl->data() ) ) { - m_dynplaylists.removeAll( p ); item->removeRow( i ); + m_dynplaylists.removeAll( p ); } } } From 3b40b7bcb242fb8537b3b76fd47d6f3c2dade9cd Mon Sep 17 00:00:00 2001 From: Christian Muehlhaeuser Date: Mon, 21 Mar 2011 03:23:52 +0100 Subject: [PATCH 055/329] * Break loops if we found the right SourceTreeItem. --- src/sourcetree/sourcetreeitem.cpp | 10 ++++++++++ 1 file changed, 10 insertions(+) diff --git a/src/sourcetree/sourcetreeitem.cpp b/src/sourcetree/sourcetreeitem.cpp index d2cd3adc1..f2e8e7be2 100644 --- a/src/sourcetree/sourcetreeitem.cpp +++ b/src/sourcetree/sourcetreeitem.cpp @@ -141,6 +141,7 @@ SourceTreeItem::onPlaylistsDeleted( const QList& playlists ) { item->removeRow( i ); m_playlists.removeAll( p ); + break; } } } @@ -165,6 +166,7 @@ SourceTreeItem::onPlaylistLoaded( Tomahawk::PlaylistRevision revision ) { pi->setEnabled( true ); m_current_revisions.insert( pl->data()->guid(), revision.revisionguid ); + break; } } } @@ -188,7 +190,10 @@ SourceTreeItem::onPlaylistChanged() playlist_ptr* pl = reinterpret_cast(piptr); if ( ptr == qlonglong( pl->data() ) ) + { pi->setText( pl->data()->title() ); + break; + } } if ( type == SourcesModel::DynamicPlaylistSource ) { @@ -196,7 +201,10 @@ SourceTreeItem::onPlaylistChanged() dynplaylist_ptr* pl = reinterpret_cast(piptr); if ( ptr == qlonglong( pl->data() ) ) + { pi->setText( pl->data()->title() ); + break; + } } } } @@ -246,6 +254,7 @@ SourceTreeItem::onDynamicPlaylistsDeleted( const QList< dynplaylist_ptr >& playl { item->removeRow( i ); m_dynplaylists.removeAll( p ); + break; } } } @@ -270,6 +279,7 @@ SourceTreeItem::onDynamicPlaylistLoaded( DynamicPlaylistRevision revision ) { pi->setEnabled( true ); m_current_dynamic_revisions.insert( pl->data()->guid(), revision.revisionguid ); + break; } } } From 0dec904df67609f6b5f9590b70ed34897012c67a Mon Sep 17 00:00:00 2001 From: Dominik Schmidt Date: Mon, 21 Mar 2011 03:49:35 +0100 Subject: [PATCH 056/329] Make it possible to build the windows installer from installed tomahawk --- admin/win/nsi/tomahawk.nsi | 37 +++++++++++++++++++++++++------------ 1 file changed, 25 insertions(+), 12 deletions(-) diff --git a/admin/win/nsi/tomahawk.nsi b/admin/win/nsi/tomahawk.nsi index 13a055394..0377d3b4e 100755 --- a/admin/win/nsi/tomahawk.nsi +++ b/admin/win/nsi/tomahawk.nsi @@ -249,14 +249,32 @@ Section "Tomahawk Player" SEC_TOMAHAWK_PLAYER SetDetailsPrint listonly SetOutPath "$INSTDIR" - ;Main executable. - File "${BUILD_PATH}\tomahawk.exe" + !ifdef INSTALL_PATH + ;Main executable. + File "${INSTALL_PATH}\bin\tomahawk.exe" - ;Plugins - File "${BUILD_PATH}\src\libtomahawk\libtomahawklib.dll" - File "${BUILD_PATH}\libtomahawk_sipjabber.dll" - File "${BUILD_PATH}\libtomahawk_siptwitter.dll" - File "${BUILD_PATH}\libtomahawk_sipzeroconf.dll" + File "${INSTALL_PATH}\lib\librtaudio.dll" + File "${INSTALL_PATH}\lib\libqxtweb-standalone.dll" + File "${INSTALL_PATH}\lib\libtomahawk_jdns.dll" + File "${INSTALL_PATH}\lib\libtomahawk_qtweetlib.dll" + File "${INSTALL_PATH}\lib\libtomahawklib.dll" + File "${INSTALL_PATH}\lib\libtomahawk_sipjabber.dll" + File "${INSTALL_PATH}\lib\libtomahawk_siptwitter.dll" + File "${INSTALL_PATH}\lib\libtomahawk_sipzeroconf.dll" + !endif + !ifndef INSTALL_PATH + ;Main executable. + File "${BUILD_PATH}\tomahawk.exe" + + File "${BUILD_PATH}\thirdparty\rtaudio\librtaudio.dll" + File "${BUILD_PATH}\thirdparty\qxt\qxtweb-standalone\libqxtweb-standalone.dll" + File "${BUILD_PATH}\thirdparty\jdns\libtomahawk_jdns.dll" + File "${BUILD_PATH}\thirdparty\qtweetlib\libtomahawk_qtweetlib.dll" + File "${BUILD_PATH}\src\libtomahawk\libtomahawklib.dll" + File "${BUILD_PATH}\libtomahawk_sipjabber.dll" + File "${BUILD_PATH}\libtomahawk_siptwitter.dll" + File "${BUILD_PATH}\libtomahawk_sipzeroconf.dll" + !endif ;License & release notes. File "${ROOT_PATH}\LICENSE.txt" @@ -283,7 +301,6 @@ Section "Tomahawk Player" SEC_TOMAHAWK_PLAYER File "${MING_DLL_PATH}\libstdc++-6.dll" ;Audio stuff - File "${BUILD_PATH}\thirdparty\rtaudio\librtaudio.dll" File "${MING_DLL_PATH}\libmad-0.dll" File "${MING_DLL_PATH}\libogg-0.dll" File "${MING_DLL_PATH}\libvorbisfile-3.dll" @@ -305,10 +322,6 @@ Section "Tomahawk Player" SEC_TOMAHAWK_PLAYER File "${MING_LIB}\libclucene-shared.dll" File "${MING_BIN}\libqtsparkle.dll" - - File "${BUILD_PATH}\thirdparty\qxt\qxtweb-standalone\libqxtweb-standalone.dll" - File "${BUILD_PATH}\thirdparty\jdns\libtomahawk_jdns.dll" - File "${BUILD_PATH}\thirdparty\qtweetlib\libtomahawk_qtweetlib.dll" SectionEnd SectionGroup "Shortcuts" From 09fb77bbfd9758fa72be8730e188fc38b9826cac Mon Sep 17 00:00:00 2001 From: Christian Muehlhaeuser Date: Mon, 21 Mar 2011 04:42:11 +0100 Subject: [PATCH 057/329] * Updated OS X build scripts. --- admin/mac/build-release-osx.sh | 4 ++-- admin/mac/deposx.sh | 18 +++++++++--------- 2 files changed, 11 insertions(+), 11 deletions(-) diff --git a/admin/mac/build-release-osx.sh b/admin/mac/build-release-osx.sh index a399398a1..84f569695 100755 --- a/admin/mac/build-release-osx.sh +++ b/admin/mac/build-release-osx.sh @@ -31,7 +31,7 @@ echo "Goes here: $QTDIR" export QMAKESPEC='macx-g++' export QTDIR export VERSION -export QTVERSION='4.7.1' +export QTVERSION='4.7.2' ################################################################################ @@ -43,7 +43,7 @@ CREATEDMG='1' header addQt cd tomahawk.app $ROOT/../admin/mac/add-Qt-to-bundle.sh \ - 'QtCore QtGui QtXml QtNetwork QtSql' + 'QtCore QtGui QtXml QtNetwork QtSql QtXmlPatterns QtWebKit phonon' header deposx $ROOT/../admin/mac/deposx.sh diff --git a/admin/mac/deposx.sh b/admin/mac/deposx.sh index 31966f9ed..8a8332936 100755 --- a/admin/mac/deposx.sh +++ b/admin/mac/deposx.sh @@ -62,7 +62,7 @@ function deposx_change function deplib_change { install_name_tool -change /usr/local/Cellar/liblastfm/0.3.3/lib/liblastfm.0.dylib @executable_path/liblastfm.0.dylib $1 - install_name_tool -change /usr/local/Cellar/qjson/0.7.1/lib/libqjson.0.dylib @executable_path/libqjson.0.dylib $1 + install_name_tool -change /usr/local/Cellar/qjson/0.7.1/lib/libqjson.0.7.1.dylib @executable_path/libqjson.0.7.1.dylib $1 install_name_tool -change /usr/local/lib/libechonest.1.1.dylib @executable_path/libechonest.1.1.dylib $1 install_name_tool -change /usr/local/lib/libclucene-core.0.9.23.dylib @executable_path/libclucene-core.0.9.23.dylib $1 install_name_tool -change /usr/local/lib/libclucene-shared.0.9.23.dylib @executable_path/libclucene-shared.0.9.23.dylib $1 @@ -75,13 +75,13 @@ function deplib_change install_name_tool -change /usr/local/Cellar/flac/1.2.1/lib/libFLAC++.6.dylib @executable_path/libFLAC++.6.dylib $1 install_name_tool -change /usr/local/Cellar/flac/1.2.1/lib/libFLAC.8.dylib @executable_path/libFLAC.8.dylib $1 install_name_tool -change $ORIGROOT/src/libtomahawk/libtomahawklib.dylib @executable_path/libtomahawklib.dylib $1 - install_name_tool -change $ORIGROOT/libsip_jabber.dylib @executable_path/libsip_jabber.dylib $1 - install_name_tool -change $ORIGROOT/libsip_twitter.dylib @executable_path/libsip_twitter.dylib $1 - install_name_tool -change $ORIGROOT/libsip_zeroconf.dylib @executable_path/libsip_zeroconf.dylib $1 + install_name_tool -change $ORIGROOT/libtomahawk_sipjabber.dylib @executable_path/libtomahawk_sipjabber.dylib $1 + install_name_tool -change $ORIGROOT/libtomahawk_siptwitter.dylib @executable_path/libtomahawk_siptwitter.dylib $1 + install_name_tool -change $ORIGROOT/libtomahawk_sipzeroconf.dylib @executable_path/libtomahawk_sipzeroconf.dylib $1 install_name_tool -change $ORIGROOT/thirdparty/jdns/libtomahawk_jdns.dylib @executable_path/libtomahawk_jdns.dylib $1 install_name_tool -change $ORIGROOT/thirdparty/qtweetlib/libtomahawk_qtweetlib.dylib @executable_path/libtomahawk_qtweetlib.dylib $1 - install_name_tool -change libqjson.0.dylib @executable_path/libqjson.0.dylib $1 + install_name_tool -change libqjson.0.7.1.dylib @executable_path/libqjson.0.7.1.dylib $1 install_name_tool -change libechonest.1.1.dylib @executable_path/libechonest.1.1.dylib $1 install_name_tool -change libclucene-core.0.9.23.dylib @executable_path/libclucene-core.0.9.23.dylib $1 install_name_tool -change libclucene-shared.0.9.23.dylib @executable_path/libclucene-shared.0.9.23.dylib $1 @@ -99,7 +99,7 @@ do deplib_change "$x" done -import_lib /usr/local/Cellar/qjson/0.7.1/lib/libqjson.0.dylib +import_lib /usr/local/Cellar/qjson/0.7.1/lib/libqjson.0.7.1.dylib import_lib /usr/local/Cellar/liblastfm/0.3.3/lib/liblastfm.0.dylib import_lib /usr/local/Cellar/gloox/1.0/lib/libgloox.8.dylib import_lib /usr/local/Cellar/taglib/1.6.3/lib/libtag.1.dylib @@ -113,9 +113,9 @@ import_lib /usr/local/lib/libechonest.1.1.dylib import_lib /usr/local/lib/libclucene-core.0.9.23.dylib import_lib /usr/local/lib/libclucene-shared.0.9.23.dylib -import_lib ../../libsip_jabber.dylib -import_lib ../../libsip_twitter.dylib -import_lib ../../libsip_zeroconf.dylib +import_lib ../../libtomahawk_sipjabber.dylib +import_lib ../../libtomahawk_siptwitter.dylib +import_lib ../../libtomahawk_sipzeroconf.dylib import_lib ../../src/libtomahawk/libtomahawklib.dylib import_lib ../../thirdparty/jdns/libtomahawk_jdns.dylib import_lib ../../thirdparty/qtweetlib/libtomahawk_qtweetlib.dylib From c666aa4e916608d219a86b00f88d5c81dd821023 Mon Sep 17 00:00:00 2001 From: Christian Muehlhaeuser Date: Mon, 21 Mar 2011 06:01:53 +0100 Subject: [PATCH 058/329] * Reset the PlaylistModel if we clear it. --- src/libtomahawk/playlist/playlistmodel.cpp | 16 +++++++++++----- 1 file changed, 11 insertions(+), 5 deletions(-) diff --git a/src/libtomahawk/playlist/playlistmodel.cpp b/src/libtomahawk/playlist/playlistmodel.cpp index 89fd32251..210cabf2c 100644 --- a/src/libtomahawk/playlist/playlistmodel.cpp +++ b/src/libtomahawk/playlist/playlistmodel.cpp @@ -75,9 +75,9 @@ PlaylistModel::loadPlaylist( const Tomahawk::playlist_ptr& playlist, bool loadEn if ( rowCount( QModelIndex() ) && loadEntries ) { - emit beginRemoveRows( QModelIndex(), 0, rowCount( QModelIndex() ) - 1 ); + emit beginResetModel(); delete m_rootItem; - emit endRemoveRows(); + emit endResetModel(); m_rootItem = new PlItem( 0, this ); } @@ -143,6 +143,7 @@ PlaylistModel::loadHistory( const Tomahawk::source_ptr& source, unsigned int amo Database::instance()->enqueue( QSharedPointer( cmd ) ); } + void PlaylistModel::clear() { @@ -263,7 +264,6 @@ PlaylistModel::onDataChanged() void PlaylistModel::onRevisionLoaded( Tomahawk::PlaylistRevision revision ) { - qDebug() << "PLAYLIST::onRevisionLoaded"; qDebug() << Q_FUNC_INFO; if ( m_waitForUpdate ) @@ -365,12 +365,18 @@ PlaylistModel::onPlaylistChanged( bool waitForUpdate ) m_waitForUpdate = waitForUpdate; QString newrev = uuid(); - if( dynplaylist_ptr dynplaylist = m_playlist.dynamicCast() ) { + if( dynplaylist_ptr dynplaylist = m_playlist.dynamicCast() ) + { if( dynplaylist->mode() == OnDemand ) + { dynplaylist->createNewRevision( newrev ); + } else if( dynplaylist->mode() == Static ) + { dynplaylist->createNewRevision( newrev, dynplaylist->currentrevision(), dynplaylist->type(), dynplaylist->generator()->controls(), l ); - } else { + } + } else + { m_playlist->createNewRevision( newrev, m_playlist->currentrevision(), l ); } } From f4269f0f3494c1df38e952474f02e5594b9c51ab Mon Sep 17 00:00:00 2001 From: Christian Muehlhaeuser Date: Mon, 21 Mar 2011 06:21:28 +0100 Subject: [PATCH 059/329] * Fixed re-emitting idle() too often in Pipeline. --- src/libtomahawk/pipeline.cpp | 3 ++- src/libtomahawk/playlist/playlistmodel.cpp | 14 ++++---------- 2 files changed, 6 insertions(+), 11 deletions(-) diff --git a/src/libtomahawk/pipeline.cpp b/src/libtomahawk/pipeline.cpp index d6f3e8891..b14bddb16 100644 --- a/src/libtomahawk/pipeline.cpp +++ b/src/libtomahawk/pipeline.cpp @@ -220,7 +220,8 @@ Pipeline::shuntNext() if ( m_queries_pending.isEmpty() ) { - emit idle(); + if ( m_qidsState.isEmpty() ) + emit idle(); return; } diff --git a/src/libtomahawk/playlist/playlistmodel.cpp b/src/libtomahawk/playlist/playlistmodel.cpp index 210cabf2c..e55e0148a 100644 --- a/src/libtomahawk/playlist/playlistmodel.cpp +++ b/src/libtomahawk/playlist/playlistmodel.cpp @@ -75,10 +75,7 @@ PlaylistModel::loadPlaylist( const Tomahawk::playlist_ptr& playlist, bool loadEn if ( rowCount( QModelIndex() ) && loadEntries ) { - emit beginResetModel(); - delete m_rootItem; - emit endResetModel(); - m_rootItem = new PlItem( 0, this ); + clear(); } m_playlist = playlist; @@ -125,10 +122,7 @@ PlaylistModel::loadHistory( const Tomahawk::source_ptr& source, unsigned int amo { if ( rowCount( QModelIndex() ) ) { - emit beginRemoveRows( QModelIndex(), 0, rowCount( QModelIndex() ) - 1 ); - delete m_rootItem; - emit endRemoveRows(); - m_rootItem = new PlItem( 0, this ); + clear(); } m_playlist.clear(); @@ -149,9 +143,9 @@ PlaylistModel::clear() { if ( rowCount( QModelIndex() ) ) { - emit beginRemoveRows( QModelIndex(), 0, rowCount( QModelIndex() ) - 1 ); + emit beginResetModel(); delete m_rootItem; - emit endRemoveRows(); + emit endResetModel(); m_rootItem = new PlItem( 0, this ); } } From 4fe5602c49b02143f9e6523f9b3a7efe080d8945 Mon Sep 17 00:00:00 2001 From: Christian Muehlhaeuser Date: Mon, 21 Mar 2011 07:17:15 +0100 Subject: [PATCH 060/329] * Fixed app bundle issues (blank icon, lowercase name). --- admin/mac/build-release-osx.sh | 16 ++++++++++++---- 1 file changed, 12 insertions(+), 4 deletions(-) diff --git a/admin/mac/build-release-osx.sh b/admin/mac/build-release-osx.sh index 84f569695..a1c863b10 100755 --- a/admin/mac/build-release-osx.sh +++ b/admin/mac/build-release-osx.sh @@ -40,16 +40,24 @@ BUILD='1' NOTQUICK='1' CREATEDMG='1' - header addQt + header "Adding Qt to app bundle" cd tomahawk.app $ROOT/../admin/mac/add-Qt-to-bundle.sh \ 'QtCore QtGui QtXml QtNetwork QtSql QtXmlPatterns QtWebKit phonon' - header deposx + header "Running install_name_tool" $ROOT/../admin/mac/deposx.sh - header "Copying Sparkle pubkey and framework, and qt.conf" + + header "Renaming icon" + mv Contents/Resources/tomahawkSources.icns Contents/Resources/tomahawk.icns + + header "Copying Sparkle pubkey & framework, and qt.conf" cp $ROOT/../admin/mac/sparkle_pub.pem Contents/Resources cp -R /Library/Frameworks/Sparkle.framework Contents/Frameworks cp $ROOT/../admin/mac/qt.conf Contents/Resources - header Done! + header "Renaming app bundle" + cd .. + mv tomahawk.app Tomahawk.app + + header "Done!" From 53ccfcfed3990db015a6325fa8802b5619ccadc1 Mon Sep 17 00:00:00 2001 From: Christian Muehlhaeuser Date: Mon, 21 Mar 2011 07:36:43 +0100 Subject: [PATCH 061/329] * Added DMG creation script, background and stuff. --- admin/mac/DS_Store.in | Bin 0 -> 12292 bytes admin/mac/create-dmg.sh | 53 +++++++++++++++++++++++++++++++++++ admin/mac/dmg_background.png | Bin 0 -> 101411 bytes 3 files changed, 53 insertions(+) create mode 100644 admin/mac/DS_Store.in create mode 100755 admin/mac/create-dmg.sh create mode 100644 admin/mac/dmg_background.png diff --git a/admin/mac/DS_Store.in b/admin/mac/DS_Store.in new file mode 100644 index 0000000000000000000000000000000000000000..a6317e0b4ed6b9d6f51c855a7874ed9ef313ed06 GIT binary patch literal 12292 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 Date: Mon, 21 Mar 2011 09:02:59 +0100 Subject: [PATCH 062/329] * Fixed OS X build scripts. --- admin/mac/DS_Store.in | Bin 12292 -> 15364 bytes admin/mac/Info.plist | 2 +- admin/mac/build-release-osx.sh | 9 ++++++--- admin/mac/create-dmg.sh | 16 +++++++++++----- 4 files changed, 18 insertions(+), 9 deletions(-) diff --git a/admin/mac/DS_Store.in b/admin/mac/DS_Store.in index a6317e0b4ed6b9d6f51c855a7874ed9ef313ed06..bea3a4bcf34a6fff608c393efe7a1e9694e09c4e 100644 GIT binary patch literal 15364 zcmeHM&u<$=6nN%2@Z%qfCF4AA_b}35E4in$UndhapZ&q2e^heJG<-HX#iDKsLG5q^WM(9 z_h#Pb@i(3|0FZ30GzAa?01+#dQUsg(6gp1pp%QR==paFVFbb$i_;BrzE$`q6a0EC4 z90861N8r&w0MBe%#4wY*t$)rOa*f>`Jlmd1;1$DdtI6g5~ zW?b21(s9L@?DjxhSK?kV2y@5r7&S+kab=T9<_?6p194|2?hS=Fv!h?kF$a=ilD8ZI zj=*6A*xfxbuNlRH)^>kCzwZ>sg+QWEx2mqiZlS8FmYqudNCH8j>xmO$NE9QhH&nf3 z)U!rSFIhL2){Xj-T2czKdCSpl*{)ex*>px0WqTb*96e_k8tSXA^$oRTuji$c+uP5l zrYB?5Q`3#f*!E0n3iTJJ8x1;_^sP8_Ch_82PJXlT*4s^%6>*sXdOEUv7oRZ!wYcdf zAbjK{@UztCr_dda^z`;g{R7d#p(md@IXp5tHa4D@q6N9QQ8sB-R}IZDvsz7AR*Qzd zq;4sBX|OdsUy;ko6-||`Wm|c}X5#~`amW3dQhL4BmAg}IkKx$7Y$}R=vuvuRg|#(H zv6m0%E2|bFJ8vtMd40{uO9Pv#QeUXrs-aug71KhwBAceHmzC_DTbf)@v;{qD*tSv0 zOMQ+nu6@x|sGk#esVWy$y&S*%8^bN0d6g*({ekbPhAonK zrOJb#3zweRz|U)g{lC{C`4aGWU4b2)wT&`|g9=cn<2hqkw_ueq-1R``rSlGqZP!U+0zBAGy}D-O;W8?i4S(gxUwaNLQSWKolT zR3A@@yuD4oWI4TfUm~AynRKKAg3F;5(>pLEAFTW6L1;kAdjDT z13rY$U>ClIAK)kW1^yr>NR*6`bHQJOe+KsjB6JH;VN5tBoEFXs=Y*$OjDSZ^r@a}O z6mYqeo;EF8vyl$eqE7dI7AuhOQf=|oiZbVlI7g3486+HH0|2yg_B1A!o$Bu3x= zzl1;YkNE%pIF`ca;0SO8{v!m4FuOFH#mT)ZYUeue^#sJQ-o{D`wQDlzxL`v~Xum)u zKG64y&Tl*HOVK`BP|vus$t3n*IrcvT`2C;0ll|wnu>AhtdXx7^yqbM@_kZV$#~Y3S nM}Q;15#R`L1ULd50geDifFr;W;0SO8I0762jsQpCu|(i6W!HN@ literal 12292 zcmeI0K~EDw6vy8n1{A0jB%mgw@kCHvY=TMCi z9_KP5Q?4fit>90(hSvO6&?$}BN{0{-0zyCt2mvARcM!my%`!V--?yVu3jrbUUlQQ_ zkV9czbh>A?Ngb%n5deA`-A2&PasxW&bkXUa)fj;&>k4gMl{;c6>yG}Mvnx8?v)a0o zGBYENn_0ONiZZjKKWEHI6|I(92nc~u0`~0gm0oU?Yw_Uued>frFT+8RxB0C4rlxhw z&mMl(52E|dI7?6HvHCWZkAh_1MK>1dL9CModOr7k_IJ}=-9~`e_C!HLyD0sy3;Zm@ zI|}yuBe&3gNWDZSVNG|M7`M_-8f@FKacLey@NPvx$HwJLK#DryR5MfY(~eg^i(uoJ z`uK4B>x;W|89!DsH9y#Ev~@Sx@ml5r1`L0%xq5t9O}1Wd)E|{LU;Tg(Ex2rT8eq;l zn};(d2=FQ}e%-dK3EDO}u3-Il-0}+peuc9TW~mukqYTx^r-1gTL2c61rG$3KqZV|7 zu3@Gbs=y+p4tnd0U>-9MvN|vjz)C)eu(r zr(iuO?}>Il;u`MpB^0*Pu$>l~-7t-#f{RiZyXXw9gUc{S4@_Gi+lCG)0$Cr#LomOJ zEv|xl5u<03*3C3|J5AnA(CFBundleSignature tomahawk CFBundleIconFile - tomahawk.icns + Tomahawk.icns CFBundleName Tomahawk LSMinimumSystemVersion diff --git a/admin/mac/build-release-osx.sh b/admin/mac/build-release-osx.sh index a1c863b10..2c9f17b6c 100755 --- a/admin/mac/build-release-osx.sh +++ b/admin/mac/build-release-osx.sh @@ -48,16 +48,19 @@ CREATEDMG='1' header "Running install_name_tool" $ROOT/../admin/mac/deposx.sh - header "Renaming icon" - mv Contents/Resources/tomahawkSources.icns Contents/Resources/tomahawk.icns + header "Renaming icon & copying Info.plist" + mv Contents/Resources/tomahawkSources.icns Contents/Resources/Tomahawk.icns + cp $ROOT/../admin/mac/Info.plist Contents/Info.plist header "Copying Sparkle pubkey & framework, and qt.conf" cp $ROOT/../admin/mac/sparkle_pub.pem Contents/Resources cp -R /Library/Frameworks/Sparkle.framework Contents/Frameworks cp $ROOT/../admin/mac/qt.conf Contents/Resources - header "Renaming app bundle" + header "Creating DMG" cd .. mv tomahawk.app Tomahawk.app + $ROOT/../admin/mac/create-dmg.sh Tomahawk.app + mv Tomahawk.app tomahawk.app header "Done!" diff --git a/admin/mac/create-dmg.sh b/admin/mac/create-dmg.sh index c2b7ce241..74c42ddc0 100755 --- a/admin/mac/create-dmg.sh +++ b/admin/mac/create-dmg.sh @@ -43,11 +43,17 @@ ln -s /Applications "$TMP/Applications" cp -R "$IN" "$TMP" # create -hdiutil create -srcfolder "$TMP" \ - -format UDZO -imagekey zlib-level=9 \ - -scrub \ - "$OUT" \ - || die "Error creating DMG :(" +hdiutil makehybrid -hfs -hfs-volume-name Tomahawk -hfs-openfolder "$TMP" "$TMP" -o tmp.dmg +hdiutil convert -format UDZO -imagekey zlib-level=9 tmp.dmg -o "$OUT" + +# cleanup +rm tmp.dmg + +#hdiutil create -srcfolder "$TMP" \ +# -format UDZO -imagekey zlib-level=9 \ +# -scrub \ +# "$OUT" \ +# || die "Error creating DMG :(" # done ! echo 'DMG size:' `du -hs "$OUT" | awk '{print $1}'` From aedebfe9a9e41f2cdc6e1cc50bdc24046692af52 Mon Sep 17 00:00:00 2001 From: Christian Muehlhaeuser Date: Mon, 21 Mar 2011 09:21:58 +0100 Subject: [PATCH 063/329] * APPLICATION_NAME is now Tomahawk instead of Player. A few more build system fixes. --- CMakeLists.txt | 6 +++--- admin/mac/Info.plist | 4 ++-- admin/mac/build-release-osx.sh | 3 ++- 3 files changed, 7 insertions(+), 6 deletions(-) diff --git a/CMakeLists.txt b/CMakeLists.txt index f20d19228..6552d24e2 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -6,7 +6,7 @@ CMAKE_MINIMUM_REQUIRED( VERSION 2.8 ) ### SET( ORGANIZATION_NAME "Tomahawk" ) SET( ORGANIZATION_DOMAIN "tomahawk-player.org" ) -SET( APPLICATION_NAME "Player" ) +SET( APPLICATION_NAME "Tomahawk" ) SET( VERSION "0.0.1" ) @@ -18,10 +18,10 @@ SET( THIRDPARTY_DIR ${CMAKE_SOURCE_DIR}/thirdparty ) IF( "${gui}" STREQUAL "no" ) ADD_DEFINITIONS( -DENABLE_HEADLESS ) MESSAGE( STATUS "Building in HEADLESS mode ***" ) - FIND_PACKAGE( Qt4 4.6.0 COMPONENTS QtCore QtXml QtNetwork REQUIRED ) + FIND_PACKAGE( Qt4 4.7.0 COMPONENTS QtCore QtXml QtNetwork REQUIRED ) ELSE() MESSAGE( STATUS "Building full GUI version ***" ) - FIND_PACKAGE( Qt4 4.6.0 COMPONENTS QtGui QtCore QtXml QtNetwork REQUIRED ) + FIND_PACKAGE( Qt4 4.7.0 COMPONENTS QtGui QtCore QtXml QtNetwork REQUIRED ) ENDIF() #deps diff --git a/admin/mac/Info.plist b/admin/mac/Info.plist index f43437a4d..e14ff442a 100644 --- a/admin/mac/Info.plist +++ b/admin/mac/Info.plist @@ -5,9 +5,9 @@ CFBundleDevelopmentRegion English CFBundleExecutable - tomahawk + Tomahawk CFBundleIdentifier - org.tomahawk-player.org.Tomahawk + org.tomahawk-player.Tomahawk CFBundleInfoDictionaryVersion 6.0 CFBundlePackageType diff --git a/admin/mac/build-release-osx.sh b/admin/mac/build-release-osx.sh index 2c9f17b6c..c3454be9c 100755 --- a/admin/mac/build-release-osx.sh +++ b/admin/mac/build-release-osx.sh @@ -48,8 +48,9 @@ CREATEDMG='1' header "Running install_name_tool" $ROOT/../admin/mac/deposx.sh - header "Renaming icon & copying Info.plist" + header "Renaming files & copying Info.plist" mv Contents/Resources/tomahawkSources.icns Contents/Resources/Tomahawk.icns + mv Contents/MacOS/tomahawk Contents/MacOS/Tomahawk cp $ROOT/../admin/mac/Info.plist Contents/Info.plist header "Copying Sparkle pubkey & framework, and qt.conf" From 7cb23a39f83aa3b91798a5771b6f46c1b7c7a252 Mon Sep 17 00:00:00 2001 From: Christian Muehlhaeuser Date: Sun, 20 Mar 2011 07:44:10 +0000 Subject: [PATCH 064/329] * Updated version number in tomahawk.nsi. --- admin/win/nsi/tomahawk.nsi | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/admin/win/nsi/tomahawk.nsi b/admin/win/nsi/tomahawk.nsi index 0377d3b4e..8844276c9 100755 --- a/admin/win/nsi/tomahawk.nsi +++ b/admin/win/nsi/tomahawk.nsi @@ -34,7 +34,7 @@ !define VER_MAJOR "0" !define VER_MINOR "0" -!define VER_BUILD "0" +!define VER_BUILD "1" !define VERSION "${VER_MAJOR}.${VER_MINOR}.${VER_BUILD}" From 8e42c8964bee1511fcc43b5d6e92088a3a2ab38c Mon Sep 17 00:00:00 2001 From: Christian Muehlhaeuser Date: Mon, 21 Mar 2011 12:49:44 +0100 Subject: [PATCH 065/329] * Fixed JavaScript errors causing a Pipeline assert. --- src/resolvers/qtscriptresolver.cpp | 2 +- thirdparty/qtweetlib/CMakeLists.txt | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/src/resolvers/qtscriptresolver.cpp b/src/resolvers/qtscriptresolver.cpp index 3b494fd61..b7e7caa40 100644 --- a/src/resolvers/qtscriptresolver.cpp +++ b/src/resolvers/qtscriptresolver.cpp @@ -91,7 +91,7 @@ ScriptEngine::resolve( const Tomahawk::query_ptr& query ) QVariantMap m = mainFrame()->evaluateJavaScript( eval ).toMap(); qDebug() << "JavaScript Result:" << m; - const QString qid = m.value( "qid" ).toString(); + const QString qid = query->id(); const QVariantList reslist = m.value( "results" ).toList(); foreach( const QVariant& rv, reslist ) diff --git a/thirdparty/qtweetlib/CMakeLists.txt b/thirdparty/qtweetlib/CMakeLists.txt index c68f22031..2f93871af 100644 --- a/thirdparty/qtweetlib/CMakeLists.txt +++ b/thirdparty/qtweetlib/CMakeLists.txt @@ -180,6 +180,6 @@ target_link_libraries(tomahawk_qtweetlib INCLUDE( ${CMAKE_CURRENT_SOURCE_DIR}/twitter-api-keys ) -INSTALL(TARGETS tomahawk_qtweetlib DESTINATION lib${LIB_SUFFIX}) +INSTALL(TARGETS tomahawk_qtweetlib DESTINATION lib) From e1c687640e3a2900524e33810ddf276935b335c0 Mon Sep 17 00:00:00 2001 From: Christian Muehlhaeuser Date: Mon, 21 Mar 2011 12:58:50 +0100 Subject: [PATCH 066/329] * Did not plan to commit that. --- thirdparty/qtweetlib/CMakeLists.txt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/thirdparty/qtweetlib/CMakeLists.txt b/thirdparty/qtweetlib/CMakeLists.txt index 2f93871af..c68f22031 100644 --- a/thirdparty/qtweetlib/CMakeLists.txt +++ b/thirdparty/qtweetlib/CMakeLists.txt @@ -180,6 +180,6 @@ target_link_libraries(tomahawk_qtweetlib INCLUDE( ${CMAKE_CURRENT_SOURCE_DIR}/twitter-api-keys ) -INSTALL(TARGETS tomahawk_qtweetlib DESTINATION lib) +INSTALL(TARGETS tomahawk_qtweetlib DESTINATION lib${LIB_SUFFIX}) From ad2bdcc300288be78b17f2d3df132e0c710181e6 Mon Sep 17 00:00:00 2001 From: Christian Muehlhaeuser Date: Mon, 21 Mar 2011 13:09:27 +0100 Subject: [PATCH 067/329] * Do not manually copy Info.plist. --- admin/mac/build-release-osx.sh | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/admin/mac/build-release-osx.sh b/admin/mac/build-release-osx.sh index c3454be9c..f54315ed6 100755 --- a/admin/mac/build-release-osx.sh +++ b/admin/mac/build-release-osx.sh @@ -48,10 +48,10 @@ CREATEDMG='1' header "Running install_name_tool" $ROOT/../admin/mac/deposx.sh - header "Renaming files & copying Info.plist" + header "Renaming files" mv Contents/Resources/tomahawkSources.icns Contents/Resources/Tomahawk.icns mv Contents/MacOS/tomahawk Contents/MacOS/Tomahawk - cp $ROOT/../admin/mac/Info.plist Contents/Info.plist +# cp $ROOT/../admin/mac/Info.plist Contents/Info.plist header "Copying Sparkle pubkey & framework, and qt.conf" cp $ROOT/../admin/mac/sparkle_pub.pem Contents/Resources From 61a3e3a8150b2af001c43186a7ff79e3b59a364c Mon Sep 17 00:00:00 2001 From: Christian Muehlhaeuser Date: Mon, 21 Mar 2011 13:18:03 +0100 Subject: [PATCH 068/329] * Fixed crasher in Result::toVariant(). --- src/libtomahawk/result.cpp | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) diff --git a/src/libtomahawk/result.cpp b/src/libtomahawk/result.cpp index 0b5f1b989..9f655f149 100644 --- a/src/libtomahawk/result.cpp +++ b/src/libtomahawk/result.cpp @@ -94,7 +94,12 @@ Result::toVariant() const m.insert( "artist", artist()->name() ); m.insert( "album", album()->name() ); m.insert( "track", track() ); - m.insert( "source", collection()->source()->friendlyName() ); + + if ( !collection().isNull() ) + m.insert( "source", collection()->source()->friendlyName() ); + else + m.insert( "source", friendlySource() ); + m.insert( "mimetype", mimetype() ); m.insert( "size", size() ); m.insert( "bitrate", bitrate() ); From 1a5d06ed28004d6adc1b4bfcd802bd61507ce1a5 Mon Sep 17 00:00:00 2001 From: Christian Muehlhaeuser Date: Mon, 21 Mar 2011 13:44:27 +0100 Subject: [PATCH 069/329] * Bad idea to create this path on non-Linux. --- src/scrobbler.cpp | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/src/scrobbler.cpp b/src/scrobbler.cpp index f3fdaaf58..f1439fe39 100644 --- a/src/scrobbler.cpp +++ b/src/scrobbler.cpp @@ -68,13 +68,15 @@ Scrobbler::Scrobbler( QObject* parent ) //HACK work around a bug in liblastfm---it doesn't create its config dir, so when it // tries to write the track cache, it fails silently. until we have a fixed version, do this // code taken from Amarok (src/services/lastfm/ScrobblerAdapter.cpp) +#ifdef Q_WS_X11 QString lpath = QDir::home().filePath( ".local/share/Last.fm" ); QDir ldir = QDir( lpath ); if( !ldir.exists() ) { ldir.mkpath( lpath ); } - +#endif + connect( TomahawkSettings::instance(), SIGNAL( changed() ), SLOT( settingsChanged() ), Qt::QueuedConnection ); From 3c936414fd7d8ce4cc4d9a6e5b6e94bca87bec86 Mon Sep 17 00:00:00 2001 From: Christian Muehlhaeuser Date: Mon, 21 Mar 2011 14:17:00 +0100 Subject: [PATCH 070/329] * Fixed QtScriptResolver's durationString parsing. --- src/resolvers/qtscriptresolver.cpp | 8 +++++++- 1 file changed, 7 insertions(+), 1 deletion(-) diff --git a/src/resolvers/qtscriptresolver.cpp b/src/resolvers/qtscriptresolver.cpp index b7e7caa40..c14325f64 100644 --- a/src/resolvers/qtscriptresolver.cpp +++ b/src/resolvers/qtscriptresolver.cpp @@ -104,7 +104,6 @@ ScriptEngine::resolve( const Tomahawk::query_ptr& query ) rp->setArtist( ap ); rp->setAlbum( Tomahawk::Album::get( 0, m.value( "album" ).toString(), ap ) ); rp->setTrack( m.value( "track" ).toString() ); - rp->setDuration( m.value( "duration" ).toUInt() ); rp->setBitrate( m.value( "bitrate" ).toUInt() ); rp->setUrl( m.value( "url" ).toString() ); rp->setSize( m.value( "size" ).toUInt() ); @@ -112,6 +111,13 @@ ScriptEngine::resolve( const Tomahawk::query_ptr& query ) rp->setRID( uuid() ); rp->setFriendlySource( m_parent->name() ); + rp->setDuration( m.value( "duration", 0 ).toUInt() ); + if ( rp->duration() <= 0 && m.contains( "durationString" ) ) + { + QTime time = QTime::fromString( m.value( "durationString" ).toString(), "hh:mm:ss" ); + rp->setDuration( time.secsTo( QTime( 0, 0 ) ) * -1 ); + } + rp->setMimetype( m.value( "mimetype" ).toString() ); if ( rp->mimetype().isEmpty() ) { From 3414f32e9b61049641c5916fdac4a6ca283655d2 Mon Sep 17 00:00:00 2001 From: Christian Muehlhaeuser Date: Mon, 21 Mar 2011 14:26:16 +0100 Subject: [PATCH 071/329] * Support year parameter in QtScriptResolver. --- src/resolvers/qtscriptresolver.cpp | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/src/resolvers/qtscriptresolver.cpp b/src/resolvers/qtscriptresolver.cpp index c14325f64..3d9906365 100644 --- a/src/resolvers/qtscriptresolver.cpp +++ b/src/resolvers/qtscriptresolver.cpp @@ -111,6 +111,13 @@ ScriptEngine::resolve( const Tomahawk::query_ptr& query ) rp->setRID( uuid() ); rp->setFriendlySource( m_parent->name() ); + if ( m.contains( "year" ) ) + { + QVariantMap attr; + attr[ "releaseyear" ] = m.value( "year" ); + rp->setAttributes( attr ); + } + rp->setDuration( m.value( "duration", 0 ).toUInt() ); if ( rp->duration() <= 0 && m.contains( "durationString" ) ) { From 2b5808a35bba236083622be4e4891c45c54a2d2e Mon Sep 17 00:00:00 2001 From: Christian Muehlhaeuser Date: Mon, 21 Mar 2011 16:07:54 +0100 Subject: [PATCH 072/329] * Added new icons. --- data/images/playlist-icon.png | Bin 519 -> 3192 bytes data/images/user-avatar.png | Bin 324 -> 1975 bytes src/libtomahawk/widgets/welcomewidget.cpp | 2 +- src/sourcetree/sourcetreeview.cpp | 2 +- 4 files changed, 2 insertions(+), 2 deletions(-) diff --git a/data/images/playlist-icon.png b/data/images/playlist-icon.png index 99ac953b101e6baaff7ebc228823bd7b7f57f86e..5696348bcd14afd0970258c471ecfef4e3257386 100644 GIT binary patch delta 3186 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 Y08JC;N#i<>Z~y=R07*qoM6N<$f_ydGIsgCw delta 492 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 delta 295 zcmdnae}qY~Gr-TCmrII^fq{Y7)59eQNXvpS2NRII`SC-k3euTUcDoJbCSc1qv;{|NZ?ft{+$P_t(@*f7nbOy}P@+K+mY!WG-)2vHd{? p)7Od$3}X8hKeX;R!^&a6FmYC5drawPixmap( option.rect.adjusted( 10, 12, -option.rect.width() + 42, -12 ), m_playlistIcon ); + painter->drawPixmap( option.rect.adjusted( 10, 13, -option.rect.width() + 48, -13 ), m_playlistIcon ); painter->drawText( option.rect.adjusted( 56, 26, -100, -8 ), index.data( PlaylistWidgetItem::ArtistRole ).toString() ); diff --git a/src/sourcetree/sourcetreeview.cpp b/src/sourcetree/sourcetreeview.cpp index 07267d530..98d9e5b16 100644 --- a/src/sourcetree/sourcetreeview.cpp +++ b/src/sourcetree/sourcetreeview.cpp @@ -542,7 +542,7 @@ SourceDelegate::paint( QPainter* painter, const QStyleOptionViewItem& option, co } QRect iconRect = option.rect.adjusted( 4, 6, -option.rect.width() + option.rect.height() - 12 + 4, -6 ); - painter->drawPixmap( iconRect, QPixmap( RESPATH "images/user-avatar.png" ) ); + painter->drawPixmap( iconRect, QPixmap( RESPATH "images/user-avatar.png" ).scaledToHeight( iconRect.height(), Qt::SmoothTransformation ) ); if ( ( option.state & QStyle::State_Selected ) == QStyle::State_Selected ) { From 218b92da9eba832d8edd47a897eb3ee43d7291f5 Mon Sep 17 00:00:00 2001 From: Christian Muehlhaeuser Date: Mon, 21 Mar 2011 16:16:35 +0100 Subject: [PATCH 073/329] * Fixed Collection & Playlist's ViewPage pixmap. --- data/images/music-icon.png | Bin 0 -> 3945 bytes resources.qrc | 1 + src/libtomahawk/playlist/collectionview.h | 3 ++- src/libtomahawk/playlist/playlistview.h | 3 ++- 4 files changed, 5 insertions(+), 2 deletions(-) create mode 100644 data/images/music-icon.png diff --git a/data/images/music-icon.png b/data/images/music-icon.png new file mode 100644 index 0000000000000000000000000000000000000000..ebbb407d0bde6d465459666ecda5e5160846f5a6 GIT binary patch literal 3945 zcmV-v50>zWP)|1MW99I>-UhmqweqO&4C(YxgabC@9Ablhy&;csZ0!_9pEr_Dl2+=<+GzNh{ z{HQC`N~Kkk6e_jQiVzA&1QAmNNfoQzP|_xZ(s9YVO>*;cY$vv_-@D%RF89vPjL#n5 zN#exI59mna*&WZ^$9a6`JLlddr=Q|Jwp+YF#h? zZjh%b&zL;9JgIXf&SzIB0)1zi7U6R&N0`}ruxD*mEwdKmnoZfAXuL+CI@eLaIxGbl1_I*e~|Bi(SWh zs1(4b^C^JGes<5I=p$=qxe_;#W-Xu*!?|*xS&A%zgL?vK6+SPJ1qonj?ma36NvnSJ zH)WBHQV6H;(1TOt0LJRM6>4Hzz=WA?dgB^*&2y9U*)>z<2!(^m3k{A?$F-pOyF!a0 zA-K;Q(wdBQOKa(C2QRAs=S=|S{Mv{+7#91mmo~qCLL|pBMyRlW$t%6SCc}shJ=HrcZz?7|2`+GE-d?pa|WG&v%IQunpLs2m}h4 zPyZw^MFOB51EZGfpAwM?@Ndf*poPK{2mTHxVj7puo#FX*0p2%~ z@?vm%f5HVSOQR7Bp*aCS=0=GYaK#{rxdR4S0zlG^IMt;g-+@n+1(1QnmILXNV&R*Mgvha1b7Kq0>Ij_az`X%8LM4d2yj@zH}s&#&KI9|8%w0 zf*2#V7=gkg)!+jb3TBG5cO+%y+X5in74c+R2W;*HqCl$z;esm|1KhVjgb-+=77`L9 zNG><9BzrXSjR7i7dzv{b;?NJ>i5ZHX%5dpUoJ}t)A}XBCs3;=m`$r8)r&T+AdU=tM zF`je|jEahIFseAf`{xHe6PC~0!jdk_nJ(yr&0BY8b z?H7LhNh0~X3vvv(0<<88%pbBv0vVswERp?^Kfd1;``({Fv=9)wAV`w`4I<=NfIZ-f z;zDV3G=T?O&ay0{zA;}y?-d#XZQMc2>okR2z z@tq;CQWK)>eF-76JcS>8bS$l|=Bn8hm`H6wd4z2-@l;wM3xeNigQU`jI*0Ph17sL9 z5&NUSCkX_$j)4Sy>$osp*mKr8(;JVT=!sik&{32v+89w$9AqnImNnnLu(~C>ypByP zE8?#3G_2478L7gNyb~cvN{l&GVUGM3nm&E+?0|9q)&p)FZ}uh(t47(%lHIlhJ9?gs zQkf$tu5Z4wubplG`U*a?ve?#kILkXIz!%6PXh(m-L|kL)U0@b1_QIC51_Q->7!uh_ z@0?>!m8QwJ#c^S@q1uBb6DYH-eZZ6gP%dE{%$m31QzM`yMTL3i0je@Xe25Cn%xqu& z`-wgaFuuth*`h|vi*KLv2+wR9rYL8Cm?P2|l6L_J$OB|f&rvvn^i*^YCkX<{NxNKe zFyLwXjEGZB@x%*>fdXRW-2x!m%g|)_6ekJd>sNckZk|&CEdYWa)llU8Cqs?}*pja= zFM?;r>#87L@V z8f|$#e|=4aT&oLIMZ9$YI;|{3kl8cnZW1I7pT(D{vw&1OBQ7Br3k*yxh+JzFAd&+# zx@PWBVTvk59=l9u{7Yg}Z2|UY=>iZFEL=CZbsmjfs|&!yNoIr){i!Ca^cDEkF~O$R zxenL%Oe;B-cBS4ZO&qWLaS#>6#UcL!g-?Hq7B>Pbf>fLKj9ZjIB{;j!zL6B28UZx5 zKBSTr31a5m%j>w=P{r?DQo{mgdqya$j#vo6FW29CN6_ZFrM2Xd zwX?VcG^4gOWG{KCHAEx~3xz+s{~49VzMvbNga)=Osw-jNG3T&*S4MBrNxqKv4ED}Ke&wEXH16^=SRARjDNQe(r06*MI;nZ>wxC6`ciAv z>Rf$DnVZQf^)z3s5h=e;MB7v@@tlR4@J zwBg~^Gnt$`83)W!zG3?@w&O^*T7UFX+(Vs1?)#0JQaDV(Sy{GZKS>@S0q<5;$-e)Y zxn5C2ZB8&>c$dk^!=-9i-eOq#?z^XYZ>nL3#Hr2O z1Fy^O?!W_Te&((P%t3bBjaF~oPmW#~W|}Ndpn3vBV=_7iywzjwgh@Ub3os;bFzL_U z2811lFN&QvCA$bFtIZC$Be9`G;Km5GfnLg?lR$(k)ZnwJ=!%q>534$uCmxe z-IX)T4Nb=cNnN+w%M+Mnf!^~P3*hkDqt6!oz*mroE6}v4&?}Xb;pCW@tN>y%Y{lgq#7} z=G;(MLde8rY(g%*gA*1)R7CN>Yn#<2kN~W~@wEgpJzG!mpf-Hz?R~99qW% ztLkA@h)9HwpWd6T zT;=diKF*$7zd(n+L{oindAM4O6YH1N(mR*bdbP|4etp2utO~_C2S=%OZoo@sHs8Il zMI%^jc?QZ5nY1>QyV%@7j3lEk>^W;I^lD0C&Cc?Zt;tW!qp`<7H!tQhC@2UoMlY%> z5ifsviK|&daeK)pVgxT#X0_AuR4b3U<}D+S`0F=UQm2B97g(^%*+RHsda3p5*Os~C zVDIogE)~fxwG3D2JN>;|+xFD2zhmk2()y+KWp)Q$ zYw5m~(~j(ZbQLKnvJqVsLgV*uJm_aw&{x=*_+t0_cg;EEPv2O%+7*zrUUJ9JEolVl4YI#|e$y4B8YwAfz`b+ezob;cNor^G;F%(^g?WHluraqo&c_T(4lbEl9&9VW>h`t2bm zbBQf-Fi%{j__FEn4_4E~(@JxyO53bXa1?f;o|4U%aVd*Cc* z&2HMbpMQ99L<##>mzv_`x65bia*cZ*yU~zBASm_3)Yn9BTPiL6%GLvl+pFd9%~x1~ z+Vs+Wiw&8N2o2fc2nbCGl>1KgDPaqlMQ*vG{;selc$+-me0=BelgGM-PG+*lN$1=y z&-dinn;AdU=acgJH|2XLonq?bs?JTVzr1zxe%P++>rJOG_kJ(vFJqnS-`?=#o|BH! zuIMP3#Goo2@M3o?u3@*#tsu9|sUT}urTyy=hux8Vr~Apvd(YWh*Ogn4><<-oU!~wT zO1CnX%_tR(H_b3*coSeBvKOYMd+^r|UKFps+oA3?Po}?uOB5!VhSC~N>DwR^_v=#K zx*V$95X~B%?gtQ*O#F2SS2csef&xCLDs0G4#wp}}x04AE~3u~AsK*#s>F3gB_4zzAQq zV3oyZ5RCZ${)ad**whp-uthLUn}kfk&U+r8MJJ)&;r`-W{IPa`R8a6?Ehi!&|M(drE01FL+=i|;voNGN$QXD8v zahDhM7Le--QJ~Q%(i~jrwaEBxP+bmBQP_gkpfh`%V*6PE@xcvf>M;L^dANYEARoUR zovQ&)>>gAj1iw8*Lh}sC$xeNK6#3Ed(Q*0XKLQK@XjEp6;e7<500000NkvXXu0mjf Duk3!1 literal 0 HcmV?d00001 diff --git a/resources.qrc b/resources.qrc index 231eb32b2..ddbc46b96 100644 --- a/resources.qrc +++ b/resources.qrc @@ -73,6 +73,7 @@ ./data/images/home.png ./data/images/back.png ./data/images/forward.png +./data/images/music-icon.png ./data/topbar-radiobuttons.css ./data/icons/tomahawk-icon-16x16.png ./data/icons/tomahawk-icon-32x32.png diff --git a/src/libtomahawk/playlist/collectionview.h b/src/libtomahawk/playlist/collectionview.h index 158e1a327..ea26fc5af 100644 --- a/src/libtomahawk/playlist/collectionview.h +++ b/src/libtomahawk/playlist/collectionview.h @@ -43,7 +43,8 @@ public: virtual QString title() const { return model()->title(); } virtual QString description() const { return model()->description(); } - + virtual QPixmap pixmap() const { return QPixmap( RESPATH "images/music-icon.png" ); } + virtual bool showModes() const { return true; } virtual bool jumpToCurrentTrack(); diff --git a/src/libtomahawk/playlist/playlistview.h b/src/libtomahawk/playlist/playlistview.h index 04c3fef05..3629099b1 100644 --- a/src/libtomahawk/playlist/playlistview.h +++ b/src/libtomahawk/playlist/playlistview.h @@ -43,9 +43,10 @@ public: virtual QWidget* widget() { return this; } virtual PlaylistInterface* playlistInterface() const { return proxyModel(); } - + virtual QString title() const { return playlistModel()->title(); } virtual QString description() const { return m_model->description(); } + virtual QPixmap pixmap() const { return QPixmap( RESPATH "images/playlist-icon.png" ); } virtual bool jumpToCurrentTrack(); From a06b1fdf52430626cf7e25004defc737a095139a Mon Sep 17 00:00:00 2001 From: Christian Muehlhaeuser Date: Mon, 21 Mar 2011 16:52:14 +0100 Subject: [PATCH 074/329] * Error handling in QtScriptResolver. --- src/resolvers/qtscriptresolver.cpp | 8 +++++++- 1 file changed, 7 insertions(+), 1 deletion(-) diff --git a/src/resolvers/qtscriptresolver.cpp b/src/resolvers/qtscriptresolver.cpp index 3d9906365..d4c0cc5f5 100644 --- a/src/resolvers/qtscriptresolver.cpp +++ b/src/resolvers/qtscriptresolver.cpp @@ -37,7 +37,13 @@ QtScriptResolver::QtScriptResolver( const QString& scriptPath ) m_thread->start(); QFile scriptFile( scriptPath ); - scriptFile.open( QIODevice::ReadOnly ); + if ( !scriptFile.open( QIODevice::ReadOnly ) ) + { + qDebug() << Q_FUNC_INFO << "Failed loading JavaScript resolver:" << scriptPath; + deleteLater(); + return; + } + m_engine->mainFrame()->setHtml( "" ); m_engine->mainFrame()->evaluateJavaScript( scriptFile.readAll() ); scriptFile.close(); From 194116c912e2cecd7e23eebbe972e36df2d004f6 Mon Sep 17 00:00:00 2001 From: Leo Franchi Date: Mon, 21 Mar 2011 19:16:43 -0400 Subject: [PATCH 075/329] add --demo parameter that strips out hostnames --- src/libtomahawk/source.cpp | 13 +++++++++---- 1 file changed, 9 insertions(+), 4 deletions(-) diff --git a/src/libtomahawk/source.cpp b/src/libtomahawk/source.cpp index 59ad94688..948b04a7b 100644 --- a/src/libtomahawk/source.cpp +++ b/src/libtomahawk/source.cpp @@ -26,6 +26,7 @@ #include "database/databasecommand_sourceoffline.h" #include "database/databasecommand_logplayback.h" #include "database/database.h" +#include using namespace Tomahawk; @@ -94,17 +95,21 @@ Source::remove() QString Source::friendlyName() const { + QString friendly; if ( m_friendlyname.isEmpty() ) - return m_username; + friendly = m_username; //TODO: this is a terrible assumption, help me clean this up, mighty muesli! if ( m_friendlyname.contains( "@conference.") ) - return QString(m_friendlyname).remove( 0, m_friendlyname.lastIndexOf( "/" )+1 ).append(" via MUC"); + friendly = QString(m_friendlyname).remove( 0, m_friendlyname.lastIndexOf( "/" )+1 ).append(" via MUC"); if ( m_friendlyname.contains( "/tomahawk" ) ) - return m_friendlyname.left( m_friendlyname.indexOf( "/tomahawk" ) ); + friendly = m_friendlyname.left( m_friendlyname.indexOf( "/tomahawk" ) ); - return m_friendlyname; + if( qApp->arguments().contains( "--demo" ) && friendly.contains( '@' ) ) + friendly = friendly.left( friendly.indexOf( '@' ) ); + + return friendly; } From 2ffe51163cf7a981eb6db48cffba9b65b6ad2375 Mon Sep 17 00:00:00 2001 From: Leo Franchi Date: Mon, 21 Mar 2011 21:13:27 -0400 Subject: [PATCH 076/329] Revert "add --demo parameter that strips out hostnames" This reverts commit 194116c912e2cecd7e23eebbe972e36df2d004f6. Hide hostname another way to try to fix J's bizarre issue --- include/tomahawk/tomahawkapp.h | 6 +++++- src/libtomahawk/source.cpp | 13 ++++--------- src/sourcetree/sourcetreeitem.cpp | 13 ++++++++++++- src/tomahawkapp.cpp | 2 ++ 4 files changed, 23 insertions(+), 11 deletions(-) diff --git a/include/tomahawk/tomahawkapp.h b/include/tomahawk/tomahawkapp.h index 7f467ca90..bf83f6e86 100644 --- a/include/tomahawk/tomahawkapp.h +++ b/include/tomahawk/tomahawkapp.h @@ -95,6 +95,9 @@ public: virtual void activate(); virtual bool loadUrl( const QString& url ); + // because QApplication::arguments() is expensive + bool scrubFriendlyName() const { return m_scrubFriendlyName; } + private slots: void setupSIP(); void messageReceived( const QString& ); @@ -117,7 +120,8 @@ private: Servent* m_servent; XMPPBot* m_xmppBot; Tomahawk::ShortcutHandler* m_shortcutHandler; - + bool m_scrubFriendlyName; + #ifdef LIBLASTFM_FOUND Scrobbler* m_scrobbler; #endif diff --git a/src/libtomahawk/source.cpp b/src/libtomahawk/source.cpp index 948b04a7b..59ad94688 100644 --- a/src/libtomahawk/source.cpp +++ b/src/libtomahawk/source.cpp @@ -26,7 +26,6 @@ #include "database/databasecommand_sourceoffline.h" #include "database/databasecommand_logplayback.h" #include "database/database.h" -#include using namespace Tomahawk; @@ -95,21 +94,17 @@ Source::remove() QString Source::friendlyName() const { - QString friendly; if ( m_friendlyname.isEmpty() ) - friendly = m_username; + return m_username; //TODO: this is a terrible assumption, help me clean this up, mighty muesli! if ( m_friendlyname.contains( "@conference.") ) - friendly = QString(m_friendlyname).remove( 0, m_friendlyname.lastIndexOf( "/" )+1 ).append(" via MUC"); + return QString(m_friendlyname).remove( 0, m_friendlyname.lastIndexOf( "/" )+1 ).append(" via MUC"); if ( m_friendlyname.contains( "/tomahawk" ) ) - friendly = m_friendlyname.left( m_friendlyname.indexOf( "/tomahawk" ) ); + return m_friendlyname.left( m_friendlyname.indexOf( "/tomahawk" ) ); - if( qApp->arguments().contains( "--demo" ) && friendly.contains( '@' ) ) - friendly = friendly.left( friendly.indexOf( '@' ) ); - - return friendly; + return m_friendlyname; } diff --git a/src/sourcetree/sourcetreeitem.cpp b/src/sourcetree/sourcetreeitem.cpp index f2e8e7be2..c9ec16101 100644 --- a/src/sourcetree/sourcetreeitem.cpp +++ b/src/sourcetree/sourcetreeitem.cpp @@ -43,7 +43,18 @@ SourceTreeItem::SourceTreeItem( const source_ptr& source, QObject* parent ) : QObject( parent ) , m_source( source ) { - QStandardItem* item = new QStandardItem( source.isNull() ? "Super Collection" : source->friendlyName() ); + QString name; + if( source.isNull() ) + name = tr( "Super Collection" ); + else + { + if( TomahawkApp::instance()->scrubFriendlyName() && source->friendlyName().contains( '@' ) ) + name = source->friendlyName().left( source->friendlyName().indexOf( '@' ) ); + else + name = source->friendlyName(); + } + + QStandardItem* item = new QStandardItem( name ); item->setIcon( QIcon( RESPATH "images/user-avatar.png" ) ); item->setEditable( false ); item->setData( SourcesModel::CollectionSource, Type ); diff --git a/src/tomahawkapp.cpp b/src/tomahawkapp.cpp index cfc6459e3..02dcb40d0 100644 --- a/src/tomahawkapp.cpp +++ b/src/tomahawkapp.cpp @@ -145,6 +145,7 @@ TomahawkApp::TomahawkApp( int& argc, char *argv[] ) , m_sipHandler( 0 ) , m_servent( 0 ) , m_shortcutHandler( 0 ) + , m_scrubFriendlyName( false ) , m_mainwindow( 0 ) , m_infoSystem( 0 ) { @@ -190,6 +191,7 @@ TomahawkApp::TomahawkApp( int& argc, char *argv[] ) qDebug() << "Init Echonest Factory."; GeneratorFactory::registerFactory( "echonest", new EchonestFactory ); + m_scrubFriendlyName = arguments().contains( "--demo" ); // Register shortcut handler for this platform #ifdef Q_WS_MAC m_shortcutHandler = new MacShortcutHandler( this ); From c15c99cfa93229e2a40f9252123a08428319021d Mon Sep 17 00:00:00 2001 From: Jeff Mitchell Date: Tue, 22 Mar 2011 10:12:46 -0400 Subject: [PATCH 077/329] Sync with upstream qtweetlib --- .../qtweetlib/qtweetlib/src/qtweetconvert.cpp | 1 + .../qtweetlib/src/qtweetfriendshipdestroy.cpp | 4 +- .../qtweetlib/src/qtweetsearchresult.cpp | 2 +- .../qtweetlib/src/qtweetuserstream.cpp | 171 ++++++++++-------- .../qtweetlib/src/qtweetuserstream.h | 25 ++- 5 files changed, 123 insertions(+), 80 deletions(-) diff --git a/thirdparty/qtweetlib/qtweetlib/src/qtweetconvert.cpp b/thirdparty/qtweetlib/qtweetlib/src/qtweetconvert.cpp index 4fce208cc..5e8ecf3f3 100644 --- a/thirdparty/qtweetlib/qtweetlib/src/qtweetconvert.cpp +++ b/thirdparty/qtweetlib/qtweetlib/src/qtweetconvert.cpp @@ -61,6 +61,7 @@ QTweetStatus QTweetConvert::variantMapToStatus(const QVariantMap &var) status.setId(var["id"].toLongLong()); status.setInReplyToUserId(var["in_reply_to_user_id"].toLongLong()); status.setInReplyToScreenName(var["in_reply_to_screen_name"].toString()); + status.setFavorited(var["favorited"].toBool()); QVariantMap userMap = var["user"].toMap(); QTweetUser user = variantMapToUserInfo(userMap); diff --git a/thirdparty/qtweetlib/qtweetlib/src/qtweetfriendshipdestroy.cpp b/thirdparty/qtweetlib/qtweetlib/src/qtweetfriendshipdestroy.cpp index 1871ed0a2..2eab28264 100644 --- a/thirdparty/qtweetlib/qtweetlib/src/qtweetfriendshipdestroy.cpp +++ b/thirdparty/qtweetlib/qtweetlib/src/qtweetfriendshipdestroy.cpp @@ -64,7 +64,7 @@ void QTweetFriendshipDestroy::unfollow(qint64 userid, bool includeEntities) QNetworkRequest req(url); - QByteArray oauthHeader = oauthTwitter()->generateAuthorizationHeader(url, OAuth::GET); + QByteArray oauthHeader = oauthTwitter()->generateAuthorizationHeader(url, OAuth::DELETE); req.setRawHeader(AUTH_HEADER, oauthHeader); QNetworkReply *reply = oauthTwitter()->networkAccessManager()->deleteResource(req); @@ -92,7 +92,7 @@ void QTweetFriendshipDestroy::unfollow(const QString &screenName, bool includeEn QNetworkRequest req(url); - QByteArray oauthHeader = oauthTwitter()->generateAuthorizationHeader(url, OAuth::GET); + QByteArray oauthHeader = oauthTwitter()->generateAuthorizationHeader(url, OAuth::DELETE); req.setRawHeader(AUTH_HEADER, oauthHeader); QNetworkReply *reply = oauthTwitter()->networkAccessManager()->deleteResource(req); diff --git a/thirdparty/qtweetlib/qtweetlib/src/qtweetsearchresult.cpp b/thirdparty/qtweetlib/qtweetlib/src/qtweetsearchresult.cpp index 834af4e60..6e264e588 100644 --- a/thirdparty/qtweetlib/qtweetlib/src/qtweetsearchresult.cpp +++ b/thirdparty/qtweetlib/qtweetlib/src/qtweetsearchresult.cpp @@ -69,7 +69,7 @@ void QTweetSearchResult::setCreatedAt(const QDateTime &dateTime) void QTweetSearchResult::setCreatedAt(const QString &twitterDate) { QString dateString = twitterDate.left(3) + ' ' + twitterDate.mid(8, 3) + ' ' + - twitterDate.mid(5, 2) + ' ' + twitterDate.mid(13, 4); + twitterDate.mid(5, 2) + ' ' + twitterDate.mid(12, 4); QString timeString = twitterDate.mid(17, 8); QDate date = QDate::fromString(dateString); diff --git a/thirdparty/qtweetlib/qtweetlib/src/qtweetuserstream.cpp b/thirdparty/qtweetlib/qtweetlib/src/qtweetuserstream.cpp index 7e2526e46..a3d63b697 100644 --- a/thirdparty/qtweetlib/qtweetlib/src/qtweetuserstream.cpp +++ b/thirdparty/qtweetlib/qtweetlib/src/qtweetuserstream.cpp @@ -33,14 +33,28 @@ #define TWITTER_USERSTREAM_URL "https://userstream.twitter.com/2/user.json" +// ### TODO User Agent or X-User-Agent + /** * Constructor */ QTweetUserStream::QTweetUserStream(QObject *parent) : - QObject(parent), m_oauthTwitter(0), m_reply(0), m_backofftimer(new QTimer(this)) + QObject(parent), m_oauthTwitter(0), m_reply(0), + m_backofftimer(new QTimer(this)), + m_timeoutTimer(new QTimer(this)), + m_streamTryingReconnect(false) { + m_backofftimer->setInterval(20000); m_backofftimer->setSingleShot(true); connect(m_backofftimer, SIGNAL(timeout()), this, SLOT(startFetching())); + + m_timeoutTimer->setInterval(90000); + connect(m_timeoutTimer, SIGNAL(timeout()), this, SLOT(replyTimeout())); + +#ifdef STREAM_LOGGER + m_streamLog.setFileName("streamlog.txt"); + m_streamLog.open(QIODevice::WriteOnly | QIODevice::Text); +#endif } /** @@ -59,49 +73,6 @@ OAuthTwitter* QTweetUserStream::oauthTwitter() const return m_oauthTwitter; } -/** - * Called when there is network error - */ -void QTweetUserStream::replyError(QNetworkReply::NetworkError code) -{ - qDebug() << "Reply error: " << code; - - // ### TODO: determine network error codes, assumptions here - - if (code < 200) { - //linear backoff - if (m_backofftimer->interval() < 250) { - m_backofftimer->setInterval(250); - } else { - int nextLinInterval = m_backofftimer->interval() + 250; - - if (nextLinInterval > 16000) //cap - nextLinInterval = 16000; - - m_backofftimer->setInterval(nextLinInterval); - } - - m_backofftimer->start(); - return; - } - - if (code > 200) { - //exp. backoff - if (m_backofftimer->interval() < 10000) { - m_backofftimer->setInterval(10000); - } else { - int nextExpInterval = 2 * m_backofftimer->interval(); - - if (nextExpInterval > 240000) - nextExpInterval = 240000; - - m_backofftimer->setInterval(240000); - } - - m_backofftimer->start(); - } -} - /** * Starts fetching user stream */ @@ -122,7 +93,9 @@ void QTweetUserStream::startFetching() m_reply = m_oauthTwitter->networkAccessManager()->get(req); connect(m_reply, SIGNAL(finished()), this, SLOT(replyFinished())); connect(m_reply, SIGNAL(readyRead()), this, SLOT(replyReadyRead())); - connect(m_reply, SIGNAL(error(QNetworkReply::NetworkError)), this, SLOT(replyError(QNetworkReply::NetworkError))); + + connect(m_reply, SIGNAL(readyRead()), m_timeoutTimer, SLOT(start())); + connect(m_reply, SIGNAL(finished()), m_timeoutTimer, SLOT(stop())); } /** @@ -130,16 +103,40 @@ void QTweetUserStream::startFetching() */ void QTweetUserStream::replyFinished() { - if (!m_reply->error()) { //no error, reconnect + qDebug() << "User stream closed "; - //sligh delay for reconnect - m_backofftimer->setInterval(250); + m_streamTryingReconnect = true; + + if (!m_reply->error()) { //no error, reconnect + qDebug() << "No error, reconnect"; + + m_reply->deleteLater(); + m_reply = 0; + + startFetching(); + } else { //error + qDebug() << "Error: " << m_reply->error() << ", " << m_reply->errorString(); + + m_reply->deleteLater(); + m_reply = 0; + + //if (m_backofftimer->interval() < 20001) { + // m_backofftimer->start(); + // return; + //} + + //increase back off interval + int nextInterval = 2 * m_backofftimer->interval(); + + if (nextInterval > 300000) { + m_backofftimer->setInterval(300000); + emit failureConnect(); + } + + m_backofftimer->setInterval(nextInterval); m_backofftimer->start(); - m_reply->deleteLater(); - m_reply = 0; - } else { //error, delete QNetworkReply - m_reply->deleteLater(); - m_reply = 0; + + qDebug() << "Exp backoff interval: " << nextInterval; } } @@ -147,30 +144,53 @@ void QTweetUserStream::replyReadyRead() { QByteArray response = m_reply->readAll(); - //reset timer - m_backofftimer->setInterval(0); +#ifdef STREAM_LOGGER + m_streamLog.write(response); + m_streamLog.write("\n"); +#endif - //split to response to delimited and not delimited part - int lastCarrReturn = response.lastIndexOf('\r'); - QByteArray rightNotDelimitedPart = response.mid(lastCarrReturn + 1); - QByteArray leftDelimitedPart = response.left(lastCarrReturn); - - //prepend to left previous not delimited response - leftDelimitedPart = leftDelimitedPart.prepend(m_cashedResponse); - - QList elements = leftDelimitedPart.split('\r'); - - for (int i = 0; i < elements.size(); ++i) { - if (elements.at(i) != QByteArray(1, '\n')) { - emit stream(elements.at(i)); - parseStream(elements.at(i)); - } + if (m_streamTryingReconnect) { + emit reconnected(); + m_streamTryingReconnect = false; } - if (rightNotDelimitedPart != QByteArray(1, '\n')) - m_cashedResponse = rightNotDelimitedPart; - else - m_cashedResponse.clear(); + //set backoff timer to initial interval + m_backofftimer->setInterval(20000); + + QByteArray responseWithPreviousCache = response.prepend(m_cachedResponse); + + int start = 0; + int end; + + while ((end = responseWithPreviousCache.indexOf('\r', start)) != -1) { + if (start != end) { + QByteArray element = responseWithPreviousCache.mid(start, end - start); + + if (!element.isEmpty()) { + emit stream(element); + parseStream(element); + } + } + + int skip = (response.at(end + 1) == QLatin1Char('\n')) ? 2 : 1; + start = end + skip; + } + + //undelimited part just cache it + m_cachedResponse.clear(); + + if (start != responseWithPreviousCache.size()) { + QByteArray element = responseWithPreviousCache.mid(start); + if (!element.isEmpty()) + m_cachedResponse = element; + } +} + +void QTweetUserStream::replyTimeout() +{ + qDebug() << "Timeout connection"; + + m_reply->abort(); } void QTweetUserStream::parseStream(const QByteArray& data) @@ -187,6 +207,9 @@ void QTweetUserStream::parsingFinished(const QVariant &json, bool ok, const QStr { if (!ok) { qDebug() << "JSON parsing error: " << errorMsg; +#ifdef STREAM_LOGGER + m_streamLog.write("***** JSON parsing error *****\n"); +#endif return; } diff --git a/thirdparty/qtweetlib/qtweetlib/src/qtweetuserstream.h b/thirdparty/qtweetlib/qtweetlib/src/qtweetuserstream.h index c7f1e3bc6..114cd758f 100644 --- a/thirdparty/qtweetlib/qtweetlib/src/qtweetuserstream.h +++ b/thirdparty/qtweetlib/qtweetlib/src/qtweetuserstream.h @@ -25,6 +25,10 @@ #include #include "qtweetlib_global.h" +#ifdef STREAM_LOGGER + #include +#endif + class QNetworkAccessManager; class QNetworkReply; class OAuthTwitter; @@ -49,7 +53,6 @@ signals: * Emits stream elements */ void stream(const QByteArray& ); - /** * Emits tweets (parsed) elements from stream */ @@ -68,14 +71,24 @@ signals: * Emits deletion of status in the stream */ void deleteStatusStream(qint64 id, qint64 userid); + /** + * Emited when user stream is reconnected after failure + * Usefull when user stream connection fails to fetch missed tweets with REST API + */ + void reconnected(); + /** + * Emited when user stream doesn't connect and backoff timer reaches maximum value (300 seconds) + * Usefull when users stream fails to revert to REST API + */ + void failureConnect(); public slots: void startFetching(); private slots: - void replyError(QNetworkReply::NetworkError code); void replyFinished(); void replyReadyRead(); + void replyTimeout(); void parsingFinished(const QVariant& json, bool ok, const QString& errorMsg); private: @@ -84,10 +97,16 @@ private: void parseDirectMessage(const QVariantMap& streamObject); void parseDeleteStatus(const QVariantMap& streamObject); + QByteArray m_cachedResponse; OAuthTwitter *m_oauthTwitter; QNetworkReply *m_reply; QTimer *m_backofftimer; - QByteArray m_cashedResponse; + QTimer *m_timeoutTimer; + bool m_streamTryingReconnect; + +#ifdef STREAM_LOGGER + QFile m_streamLog; +#endif }; #endif // QTWEETUSERSTREAM_H From 458551d78a3bb6b0730c64ad99995e5c3569d60c Mon Sep 17 00:00:00 2001 From: Leo Franchi Date: Tue, 22 Mar 2011 13:01:28 -0400 Subject: [PATCH 078/329] Add new icons, and also use the new icon in stations and auto playlists --- data/images/music-icon.png | Bin 3945 -> 5865 bytes data/images/playlist-icon.png | Bin 3192 -> 4913 bytes data/images/user-avatar.png | Bin 1975 -> 3640 bytes .../playlist/dynamic/widgets/DynamicWidget.h | 1 + 4 files changed, 1 insertion(+) diff --git a/data/images/music-icon.png b/data/images/music-icon.png index ebbb407d0bde6d465459666ecda5e5160846f5a6..67e75a0b0ffa69c89c9d1d6a78f846f78946b89a 100644 GIT binary patch delta 5864 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 Duk3!1 diff --git a/data/images/playlist-icon.png b/data/images/playlist-icon.png index 5696348bcd14afd0970258c471ecfef4e3257386..658b5bc510416f88d379509d548973715ccd8515 100644 GIT binary patch delta 4905 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 delta 1962 zcmV;b2UYmE9JddUBYyx1a7bBm0008T0008T0g2Xs?*IS*8FWQhbW?9;ba!ELWdK2B zZ(?O2No`?gWm08fWO;GPWjp`?2SiCkK~#9!>|51U>|}7tV0BgeM;F2B zB3L_u^+td%g7aqJ`|qk184MW=84MYm&GgudEXIuX{$s zBjrEw>r*XQ^NP#&eqlCLV;9KFkpWG?t@Z)zK$&lTa$ctbCyx}oVSwsq5ayd(@tQRk z&^3cRK>%X4pD~qX&O4^N5pB=&FxyxA11LW^Ko2V} ze-EGsNVLfX*9_uLuy_V1JvSKuA}tM2|CWO%L2(a(%D^QMR@^VdH#K}Kr^bK^)EtR6 zm;!ZN@r|d!+hkSQSn@mv_G1=dJuFcts? z^12X#TYt!P3$uX9FoDGDO8~%5jR6+uUqRpm^}s_%Xar#Rxv@(`b(xCcXlXEJ7YM+J z04p2>Pz9{CRum05pgDF6)gexp?PlpXDIJ9>)<3!%n^z!z7TY-J0@nV+zSRH*BxX>*?9`zmG8RcxxjF0JiILcaY|eA zNv~AnG6GaWl`jMc$rD7g#AQD3np3Of*UaArfP>||?3k5@pcjgy-L38)nboZnb|eqL zflv6E*sxt30|#g%D9FeJ#K50lsg=O7YLv0U)l_ zlF*@zMQQ~juc7V^5bH7v;o}y)e9dtoxd4B>2 zK@|&N+astcI?12T2|z^$?_ahhobCOQIp6PFV%+qQ(%67bkgUQ1N;2V|Sy-P3fEcXX zQ~yx#Ea|Rq+U4r76EAu~#&J5<6<;)LKD6@WsS%6~M2!)KfvC#w58V}ABoUf50#yGZTg7fzAPmjmga zc4s90t#Ewi2QHE3Dw`wD6S~b7hB7-g1Imn%L5i7yZjNL~DXO_4eOnAa zBL_g61?>UM46OsFc!Dki2v&Lz!4-;2EWs+3G$Qa2EfhnasRvR%@1B1gi+=%y;=vik z;<5@gEl}M-QA0KarQ&P;fjLqy$}v*_^UR8zHV}-YrRMQj#q)Czv{QojG<+|>4K+=N z0#H+T{|>9Iq_@o!I->-IbVqYkcZcy@+80ikp`GCJ1VjVW01JAbGE}xu7c6uO1I5w- zKvrq3nvtS0bTj~r(uW)B?tkx-vid=(AyxrQwpEvzBTPmSnj;G~N)7OtlR=e6M+%k6 zq>)v+ge;PzXa$pD!^lFYdzp*29d3Gr3w8)8W(?3=ri#4x%4g=l_d@}=I)MR3=?dD_ z9y$3MRA1nr42vT!aoCT+-D?ZH!q8E>fbXdbeN^lV9`^`MruQ3#C4Zi9v$gFB7DHR+ z)d?w(>&fT>8KsJAQnv3NwMS4Cpzq1|X6QU{iV_T&0;F9Sp%pAg`K>U@%n_u_|Cn}~ z=qbmp0ZystPH}9USal;-L(>EDiV?KKJaw-ratb3uCPn?wHuy3C;;zkin(k53z#ZgM zbU;P#=RigI%M36)V}F04xU=&#$*`kwQ*l=qv~PGJ-h3@`u!KHaC>6gE>?kvHJoQw0 z4>~~BgM!-E6cGI0C^LkGI^z`asek_P`U0#8eM$qY0W)zT zB^|E*>RVZ_jJI-y@#K6D-F;6Q4owqHl6wJW>mNEUj#Sl8uu>+I-`b2{L5c*$%+zKG w?NWLR>F9d|SR-f&p;!DT%#Y(e9{&n30QxygyeKt~rT_o{07*qoM6N<$g0F0a7ytkO diff --git a/src/libtomahawk/playlist/dynamic/widgets/DynamicWidget.h b/src/libtomahawk/playlist/dynamic/widgets/DynamicWidget.h index 7ba3e5ed4..34ae1237f 100644 --- a/src/libtomahawk/playlist/dynamic/widgets/DynamicWidget.h +++ b/src/libtomahawk/playlist/dynamic/widgets/DynamicWidget.h @@ -77,6 +77,7 @@ public: virtual QString title() const { return m_model->title(); } virtual QString description() const { return m_model->description(); } + virtual QPixmap pixmap() const { return QPixmap( RESPATH "images/playlist-icon.png" ); } virtual bool jumpToCurrentTrack(); From 1211bf23b6b4e220e52c789417988fc4aa3daaa5 Mon Sep 17 00:00:00 2001 From: Jeff Mitchell Date: Tue, 22 Mar 2011 14:36:42 -0400 Subject: [PATCH 079/329] Revamp twitter auth mechanism, which will hopefully remove false-positive auth success and allow de-authing --- src/sip/twitter/twitter.cpp | 24 +++- src/sip/twitter/twitter.h | 1 + src/sip/twitter/twitterconfigwidget.cpp | 114 +++++++++++++----- src/sip/twitter/twitterconfigwidget.h | 10 +- .../tomahawk-custom/tomahawkoauthtwitter.cpp | 12 +- .../tomahawk-custom/tomahawkoauthtwitter.h | 3 + 6 files changed, 126 insertions(+), 38 deletions(-) diff --git a/src/sip/twitter/twitter.cpp b/src/sip/twitter/twitter.cpp index 3588802b7..3c92b6f8b 100644 --- a/src/sip/twitter/twitter.cpp +++ b/src/sip/twitter/twitter.cpp @@ -61,10 +61,22 @@ TwitterPlugin::TwitterPlugin() m_connectTimer.start(); } +void +TwitterPlugin::configDialogAuthedSignalSlot( bool authed ) +{ + m_isAuthed = authed; + if ( !authed ) + { + TomahawkSettings::instance()->setTwitterScreenName( QString() ); + TomahawkSettings::instance()->setTwitterOAuthToken( QString() ); + TomahawkSettings::instance()->setTwitterOAuthTokenSecret( QString() ); + } +} + bool TwitterPlugin::isValid() { - return m_isAuthed || !m_cachedPeers.isEmpty(); + return m_isAuthed; } const QString @@ -89,11 +101,11 @@ QWidget* TwitterPlugin::configWidget() { m_configWidget = new TwitterConfigWidget( this ); + connect( m_configWidget, SIGNAL( twitterAuthed(bool) ), SLOT( configDialogAuthedSignalSlot(bool) ) ); + return m_configWidget; } - - bool TwitterPlugin::connectPlugin( bool /*startup*/ ) { @@ -282,7 +294,10 @@ TwitterPlugin::friendsTimelineStatuses( const QList< QTweetStatus > &statuses ) qDebug() << "TwitterPlugin parsed node " << node << " out of the tweet"; if ( status.user().screenName() == myScreenName && node == Database::instance()->dbid() ) + { + qDebug() << "My screen name and my dbid found; ignoring"; continue; + } QHash< QString, QVariant > peerData; if( m_cachedPeers.contains( status.user().screenName() ) ) @@ -354,7 +369,10 @@ TwitterPlugin::mentionsStatuses( const QList< QTweetStatus > &statuses ) qDebug() << "TwitterPlugin parsed node " << node << " out of the tweet"; if ( status.user().screenName() == myScreenName && node == Database::instance()->dbid() ) + { + qDebug() << "My screen name and my dbid found; ignoring"; continue; + } QHash< QString, QVariant > peerData; if( m_cachedPeers.contains( status.user().screenName() ) ) diff --git a/src/sip/twitter/twitter.h b/src/sip/twitter/twitter.h index 9c0c0fd8f..3b03440af 100644 --- a/src/sip/twitter/twitter.h +++ b/src/sip/twitter/twitter.h @@ -75,6 +75,7 @@ public slots: } private slots: + void configDialogAuthedSignalSlot( bool authed ); void connectAuthVerifyReply( const QTweetUser &user ); void checkTimerFired(); void connectTimerFired(); diff --git a/src/sip/twitter/twitterconfigwidget.cpp b/src/sip/twitter/twitterconfigwidget.cpp index 70c16a512..dbf484652 100644 --- a/src/sip/twitter/twitterconfigwidget.cpp +++ b/src/sip/twitter/twitterconfigwidget.cpp @@ -37,25 +37,29 @@ TwitterConfigWidget::TwitterConfigWidget(SipPlugin* plugin, QWidget *parent) : ui->setupUi(this); connect(ui->twitterAuthenticateButton, SIGNAL(pressed()), - this, SLOT(authenticateTwitter())); + this, SLOT(authDeauthTwitter())); connect(ui->twitterTweetGotTomahawkButton, SIGNAL(pressed()), this, SLOT(startPostGotTomahawkStatus())); TomahawkSettings* s = TomahawkSettings::instance(); - if ( s->twitterOAuthToken().isEmpty() || s->twitterOAuthTokenSecret().isEmpty() ) + if ( s->twitterOAuthToken().isEmpty() || s->twitterOAuthTokenSecret().isEmpty() || s->twitterScreenName().isEmpty() ) { ui->twitterStatusLabel->setText("Status: No saved credentials"); ui->twitterAuthenticateButton->setText( "Authenticate" ); ui->twitterInstructionsInfoLabel->setVisible( false ); ui->twitterTweetGotTomahawkButton->setVisible( false ); + + emit twitterAuthed( false ); } else { ui->twitterStatusLabel->setText("Status: Credentials saved"); - ui->twitterAuthenticateButton->setText( "Re-authenticate" ); + ui->twitterAuthenticateButton->setText( "De-authenticate" ); ui->twitterInstructionsInfoLabel->setVisible( true ); ui->twitterTweetGotTomahawkButton->setVisible( true ); + + emit twitterAuthed( true ); } } @@ -65,6 +69,15 @@ TwitterConfigWidget::~TwitterConfigWidget() delete ui; } +void +TwitterConfigWidget::authDeauthTwitter() +{ + if ( ui->twitterAuthenticateButton->text() == "Authenticate" ) + authenticateTwitter(); + else + deauthenticateTwitter(); +} + void TwitterConfigWidget::authenticateTwitter() { @@ -72,46 +85,80 @@ TwitterConfigWidget::authenticateTwitter() TomahawkOAuthTwitter *twitAuth = new TomahawkOAuthTwitter( this ); twitAuth->setNetworkAccessManager( TomahawkUtils::nam() ); twitAuth->authorizePin(); - if ( !twitAuth->oauthToken().isEmpty() && !twitAuth->oauthTokenSecret().isEmpty() ) - { - TomahawkSettings* s = TomahawkSettings::instance(); - s->setTwitterOAuthToken( twitAuth->oauthToken() ); - s->setTwitterOAuthTokenSecret( twitAuth->oauthTokenSecret() ); - ui->twitterStatusLabel->setText("Status: Credentials saved"); - ui->twitterAuthenticateButton->setText( "Re-authenticate" ); - - ui->twitterInstructionsInfoLabel->setVisible( true ); - ui->twitterTweetGotTomahawkButton->setVisible( true ); - - TomahawkSettings::instance()->setTwitterCachedFriendsSinceId( 0 ); - TomahawkSettings::instance()->setTwitterCachedMentionsSinceId( 0 ); - m_plugin->connectPlugin( false ); - - } - else - { - TomahawkSettings* s = TomahawkSettings::instance(); - s->setTwitterOAuthToken( QString() ); - s->setTwitterOAuthTokenSecret( QString() ); - ui->twitterStatusLabel->setText("Status: No saved credentials"); - ui->twitterAuthenticateButton->setText( "Authenticate" ); - - ui->twitterInstructionsInfoLabel->setVisible( false ); - ui->twitterTweetGotTomahawkButton->setVisible( false ); - - QMessageBox::critical( 0, QString("Tweetin' Error"), QString("There was an error validating your authentication") ); - } + + TomahawkSettings* s = TomahawkSettings::instance(); + s->setTwitterOAuthToken( twitAuth->oauthToken() ); + s->setTwitterOAuthTokenSecret( twitAuth->oauthTokenSecret() ); + + QTweetAccountVerifyCredentials *credVerifier = new QTweetAccountVerifyCredentials( twitAuth, this ); + connect( credVerifier, SIGNAL( parsedUser( const QTweetUser & ) ), SLOT( authenticateVerifyReply( const QTweetUser & ) ) ); + connect( credVerifier, SIGNAL( error( QTweetNetBase::ErrorCode, QString ) ), SLOT( authenticateVerifyError( QTweetNetBase::ErrorCode, QString ) ) ); + credVerifier->verify(); } +void +TwitterConfigWidget::authenticateVerifyReply( const QTweetUser &user ) +{ + qDebug() << Q_FUNC_INFO; + if ( user.id() == 0 ) + { + QMessageBox::critical( 0, QString("Tweetin' Error"), QString("The credentials could not be verified.\nYou may wish to try re-authenticating.") ); + emit twitterAuthed( false ); + return; + } + + TomahawkSettings* s = TomahawkSettings::instance(); + s->setTwitterScreenName( user.screenName() ); + s->setTwitterCachedFriendsSinceId( 0 ); + s->setTwitterCachedMentionsSinceId( 0 ); + + ui->twitterStatusLabel->setText("Status: Credentials saved"); + ui->twitterAuthenticateButton->setText( "De-authenticate" ); + ui->twitterInstructionsInfoLabel->setVisible( true ); + ui->twitterTweetGotTomahawkButton->setVisible( true ); + + m_plugin->connectPlugin( false ); + + emit twitterAuthed( true ); +} + +void +TwitterConfigWidget::authenticateVerifyError( QTweetNetBase::ErrorCode code, const QString &errorMsg ) +{ + qDebug() << Q_FUNC_INFO; + qDebug() << "Error validating credentials, error code is " << code << ", error message is " << errorMsg; + ui->twitterStatusLabel->setText("Status: Error validating credentials"); + emit twitterAuthed( false ); + return; +} + + +void +TwitterConfigWidget::deauthenticateTwitter() +{ + qDebug() << Q_FUNC_INFO; + TomahawkSettings* s = TomahawkSettings::instance(); + s->setTwitterOAuthToken( QString() ); + s->setTwitterOAuthTokenSecret( QString() ); + s->setTwitterScreenName( QString() ); + + ui->twitterStatusLabel->setText("Status: No saved credentials"); + ui->twitterAuthenticateButton->setText( "Authenticate" ); + ui->twitterInstructionsInfoLabel->setVisible( false ); + ui->twitterTweetGotTomahawkButton->setVisible( false ); + + emit twitterAuthed( false ); +} void TwitterConfigWidget::startPostGotTomahawkStatus() { qDebug() << "Posting Got Tomahawk status"; TomahawkSettings* s = TomahawkSettings::instance(); - if ( s->twitterOAuthToken().isEmpty() || s->twitterOAuthTokenSecret().isEmpty() ) + if ( s->twitterOAuthToken().isEmpty() || s->twitterOAuthTokenSecret().isEmpty() || s->twitterScreenName().isEmpty() ) { QMessageBox::critical( 0, QString("Tweetin' Error"), QString("Your saved credentials could not be loaded.\nYou may wish to try re-authenticating.") ); + emit twitterAuthed( false ); return; } TomahawkOAuthTwitter *twitAuth = new TomahawkOAuthTwitter( this ); @@ -130,6 +177,7 @@ TwitterConfigWidget::postGotTomahawkStatusAuthVerifyReply( const QTweetUser &use if ( user.id() == 0 ) { QMessageBox::critical( 0, QString("Tweetin' Error"), QString("Your saved credentials could not be verified.\nYou may wish to try re-authenticating.") ); + emit twitterAuthed( false ); return; } TomahawkSettings* s = TomahawkSettings::instance(); diff --git a/src/sip/twitter/twitterconfigwidget.h b/src/sip/twitter/twitterconfigwidget.h index 8e1d3ed19..5521c7f7b 100644 --- a/src/sip/twitter/twitterconfigwidget.h +++ b/src/sip/twitter/twitterconfigwidget.h @@ -40,14 +40,22 @@ public: explicit TwitterConfigWidget(SipPlugin* plugin = 0, QWidget *parent = 0); ~TwitterConfigWidget(); +signals: + void twitterAuthed( bool authed ); + private slots: - void authenticateTwitter(); + void authDeauthTwitter(); void startPostGotTomahawkStatus(); + void authenticateVerifyReply( const QTweetUser &user ); + void authenticateVerifyError( QTweetNetBase::ErrorCode code, const QString &errorMsg ); void postGotTomahawkStatusAuthVerifyReply( const QTweetUser &user ); void postGotTomahawkStatusUpdateReply( const QTweetStatus &status ); void postGotTomahawkStatusUpdateError( QTweetNetBase::ErrorCode, const QString &errorMsg ); private: + void authenticateTwitter(); + void deauthenticateTwitter(); + Ui::TwitterConfigWidget *ui; SipPlugin *m_plugin; }; diff --git a/thirdparty/qtweetlib/tomahawk-custom/tomahawkoauthtwitter.cpp b/thirdparty/qtweetlib/tomahawk-custom/tomahawkoauthtwitter.cpp index 580ba6daf..0d4c3e06b 100644 --- a/thirdparty/qtweetlib/tomahawk-custom/tomahawkoauthtwitter.cpp +++ b/thirdparty/qtweetlib/tomahawk-custom/tomahawkoauthtwitter.cpp @@ -1,5 +1,6 @@ #include "tomahawkoauthtwitter.h" #include +#include TomahawkOAuthTwitter::TomahawkOAuthTwitter( QObject* parent ) : OAuthTwitter( parent ) @@ -7,7 +8,8 @@ TomahawkOAuthTwitter::TomahawkOAuthTwitter( QObject* parent ) } -int TomahawkOAuthTwitter::authorizationWidget() +int +TomahawkOAuthTwitter::authorizationWidget() { bool ok; int i = QInputDialog::getInt(0, QString( "Twitter PIN" ), QString( "After authenticating on Twitter's web site,\nenter the displayed PIN number here:" ), 0, 0, 2147483647, 1, &ok); @@ -16,3 +18,11 @@ int TomahawkOAuthTwitter::authorizationWidget() return 0; } + +void +TomahawkOAuthTwitter::error() +{ + qDebug() << Q_FUNC_INFO; + setOAuthToken( QString().toLatin1() ); + setOAuthTokenSecret( QString().toLatin1() ); +} diff --git a/thirdparty/qtweetlib/tomahawk-custom/tomahawkoauthtwitter.h b/thirdparty/qtweetlib/tomahawk-custom/tomahawkoauthtwitter.h index 22690ac3c..8336f4158 100644 --- a/thirdparty/qtweetlib/tomahawk-custom/tomahawkoauthtwitter.h +++ b/thirdparty/qtweetlib/tomahawk-custom/tomahawkoauthtwitter.h @@ -16,6 +16,9 @@ public: protected: virtual int authorizationWidget(); + +private slots: + void error(); }; #endif From 18453f886f7274b831f55341c5ca28f3e5ad1f9b Mon Sep 17 00:00:00 2001 From: Leo Franchi Date: Tue, 22 Mar 2011 15:59:46 -0400 Subject: [PATCH 080/329] check for valid shared pointer here --- src/libtomahawk/playlist/trackproxymodel.cpp | 3 +++ 1 file changed, 3 insertions(+) diff --git a/src/libtomahawk/playlist/trackproxymodel.cpp b/src/libtomahawk/playlist/trackproxymodel.cpp index e271bf40b..3b66b70de 100644 --- a/src/libtomahawk/playlist/trackproxymodel.cpp +++ b/src/libtomahawk/playlist/trackproxymodel.cpp @@ -161,6 +161,9 @@ TrackProxyModel::filterAcceptsRow( int sourceRow, const QModelIndex& sourceParen return false; const Tomahawk::query_ptr& q = pi->query(); + if( q.isNull() ) // uh oh? filter out invalid queries i guess + return false; + Tomahawk::result_ptr r; if ( q->numResults() ) r = q->results().first(); From bd54916fe3089e3e43a3b636774c514466da938b Mon Sep 17 00:00:00 2001 From: Leo Franchi Date: Tue, 22 Mar 2011 18:36:07 -0400 Subject: [PATCH 081/329] set dangling pointer to 0 so if a proxymodel gets called during resetting, it can tell if a plitem is gone --- src/libtomahawk/playlist/playlistmodel.cpp | 1 + src/libtomahawk/playlist/playlistmodel.h | 2 +- 2 files changed, 2 insertions(+), 1 deletion(-) diff --git a/src/libtomahawk/playlist/playlistmodel.cpp b/src/libtomahawk/playlist/playlistmodel.cpp index e55e0148a..8e4d24ae6 100644 --- a/src/libtomahawk/playlist/playlistmodel.cpp +++ b/src/libtomahawk/playlist/playlistmodel.cpp @@ -145,6 +145,7 @@ PlaylistModel::clear() { emit beginResetModel(); delete m_rootItem; + m_rootItem = 0; emit endResetModel(); m_rootItem = new PlItem( 0, this ); } diff --git a/src/libtomahawk/playlist/playlistmodel.h b/src/libtomahawk/playlist/playlistmodel.h index b7657b02f..e69bf59e1 100644 --- a/src/libtomahawk/playlist/playlistmodel.h +++ b/src/libtomahawk/playlist/playlistmodel.h @@ -51,7 +51,7 @@ public: Tomahawk::playlist_ptr playlist() const { return m_playlist; } - void loadPlaylist( const Tomahawk::playlist_ptr& playlist, bool loadEntries = true ); + virtual void loadPlaylist( const Tomahawk::playlist_ptr& playlist, bool loadEntries = true ); void loadHistory( const Tomahawk::source_ptr& source, unsigned int amount = 50 ); void clear(); From cc379b8ffe4f9e30ecc494a4912d8930d5649551 Mon Sep 17 00:00:00 2001 From: Leo Franchi Date: Tue, 22 Mar 2011 21:35:35 -0400 Subject: [PATCH 082/329] use new images in toolbar too --- data/images/back.png | Bin 9960 -> 14022 bytes data/images/forward.png | Bin 9668 -> 14456 bytes data/images/home.png | Bin 3682 -> 5344 bytes 3 files changed, 0 insertions(+), 0 deletions(-) diff --git a/data/images/back.png b/data/images/back.png index 88db1dc23691f750f221ed8ef117214a42a1a0a6..3c9be3ebe3f639578038a8361d65887f85cc3ef4 100644 GIT binary patch literal 14022 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 literal 9960 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 P|6|Zl(NV5ZL`VD|&wewg diff --git a/data/images/forward.png b/data/images/forward.png index 3e0c424763c163dbc633d437d87e99bd8136f205..4b9ede5c510ced4fe09bade5420d7349066a3ec6 100644 GIT binary patch literal 14456 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 KMNUMnLSTaET>Xsz literal 9668 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 diff --git a/data/images/home.png b/data/images/home.png index e36f187f6c60028b52c601eaba6dcb04834f8b8f..5f86690c9885eb645483820b09bc76b0460d4769 100644 GIT binary patch literal 5344 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 AegFUf From 0691ad62529ea506337b9d0e2252ab51afa79828 Mon Sep 17 00:00:00 2001 From: Leo Franchi Date: Tue, 22 Mar 2011 22:29:41 -0400 Subject: [PATCH 083/329] set title on super collection when showing it for the first time --- src/libtomahawk/playlist/playlistmanager.cpp | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/src/libtomahawk/playlist/playlistmanager.cpp b/src/libtomahawk/playlist/playlistmanager.cpp index ce9a48af3..31570a41f 100644 --- a/src/libtomahawk/playlist/playlistmanager.cpp +++ b/src/libtomahawk/playlist/playlistmanager.cpp @@ -357,12 +357,12 @@ PlaylistManager::showSuperCollection() toAdd << source->collection(); m_superAlbumModel->addCollection( source->collection() ); } - - m_superCollectionFlatModel->setTitle( tr( "All available tracks" ) ); - m_superAlbumModel->setTitle( tr( "All available albums" ) ); } m_superCollectionFlatModel->addCollections( toAdd ); - + + m_superCollectionFlatModel->setTitle( tr( "All available tracks" ) ); + m_superAlbumModel->setTitle( tr( "All available albums" ) ); + if ( m_currentMode == 0 ) { setPage( m_superCollectionView ); From 068391cd5814c6784d26dc03db17d6df3e6fb057 Mon Sep 17 00:00:00 2001 From: Leo Franchi Date: Wed, 23 Mar 2011 07:46:12 -0400 Subject: [PATCH 084/329] expect urls of form tomahawk://load/?xspf= --- src/tomahawkapp.cpp | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/tomahawkapp.cpp b/src/tomahawkapp.cpp index 02dcb40d0..eb178ede0 100644 --- a/src/tomahawkapp.cpp +++ b/src/tomahawkapp.cpp @@ -505,8 +505,8 @@ TomahawkApp::loadUrl( const QString& url ) if( url.contains( "tomahawk://" ) ) { QString cmd = url.mid( 11 ); qDebug() << "tomahawk!s" << cmd; - if( cmd.startsWith( "load/" ) ) { - cmd = cmd.mid( 5 ); + if( cmd.startsWith( "load/?" ) ) { + cmd = cmd.mid( 6 ); qDebug() << "loading.." << cmd; if( cmd.startsWith( "xspf=" ) ) { XSPFLoader* l = new XSPFLoader( true, this ); From f9954dbb7e5db728d374c3ed136eee208cf68bcc Mon Sep 17 00:00:00 2001 From: Leo Franchi Date: Wed, 23 Mar 2011 17:56:37 -0400 Subject: [PATCH 085/329] don't show an error on loading an empty station --- src/libtomahawk/playlist/dynamic/DynamicModel.cpp | 3 +++ src/libtomahawk/playlist/dynamic/DynamicView.cpp | 6 +++--- .../playlist/dynamic/widgets/DynamicWidget.cpp | 8 ++++---- 3 files changed, 10 insertions(+), 7 deletions(-) diff --git a/src/libtomahawk/playlist/dynamic/DynamicModel.cpp b/src/libtomahawk/playlist/dynamic/DynamicModel.cpp index 3ce8fa2b9..dfbf72960 100644 --- a/src/libtomahawk/playlist/dynamic/DynamicModel.cpp +++ b/src/libtomahawk/playlist/dynamic/DynamicModel.cpp @@ -54,6 +54,9 @@ DynamicModel::loadPlaylist( const Tomahawk::dynplaylist_ptr& playlist ) connect( m_playlist->generator().data(), SIGNAL( nextTrackGenerated( Tomahawk::query_ptr ) ), this, SLOT( newTrackGenerated( Tomahawk::query_ptr ) ) ); PlaylistModel::loadPlaylist( m_playlist, m_playlist->mode() == Static ); + + if( m_playlist->mode() == OnDemand ) + emit trackCountChanged( rowCount( QModelIndex() ) ); } QString diff --git a/src/libtomahawk/playlist/dynamic/DynamicView.cpp b/src/libtomahawk/playlist/dynamic/DynamicView.cpp index eb70bb6b8..dffee69f1 100644 --- a/src/libtomahawk/playlist/dynamic/DynamicView.cpp +++ b/src/libtomahawk/playlist/dynamic/DynamicView.cpp @@ -131,10 +131,10 @@ DynamicView::onTrackCountChanged( unsigned int tracks ) if ( tracks == 0 && !m_working ) { if( m_onDemand ) { - if( m_readOnly ) - overlay()->setText( tr( "Press play to begin listening to this custom station!" ) ); + if( !m_readOnly ) + overlay()->setText( tr( "Add some filters above to seed this station!" ) ); else - overlay()->setText( tr( "Add some filters above, and press play to begin listening to this custom station!" ) ); + return; // when viewing a read-only station, don't show anything } else if( m_readOnly ) overlay()->setText( tr( "Press Generate to get started!" ) ); diff --git a/src/libtomahawk/playlist/dynamic/widgets/DynamicWidget.cpp b/src/libtomahawk/playlist/dynamic/widgets/DynamicWidget.cpp index 4615b37b5..a6853cee3 100644 --- a/src/libtomahawk/playlist/dynamic/widgets/DynamicWidget.cpp +++ b/src/libtomahawk/playlist/dynamic/widgets/DynamicWidget.cpp @@ -153,12 +153,12 @@ DynamicWidget::loadDynamicPlaylist( const Tomahawk::dynplaylist_ptr& playlist ) m_layout->insertWidget( 0, m_controls ); } + if( m_playlist->mode() == OnDemand && !m_playlist->generator()->controls().isEmpty() ) + showPreview(); + if( !m_playlist.isNull() ) m_controls->setControls( m_playlist, m_playlist->author()->isLocal() ); - if( m_playlist->mode() == OnDemand ) - showPreview(); - connect( m_playlist->generator().data(), SIGNAL( generated( QList ) ), this, SLOT( tracksGenerated( QList ) ) ); connect( m_playlist.data(), SIGNAL( dynamicRevisionLoaded( Tomahawk::DynamicPlaylistRevision ) ), this, SLOT( onRevisionLoaded( Tomahawk::DynamicPlaylistRevision ) ) ); connect( m_playlist->generator().data(), SIGNAL( error( QString, QString ) ), this, SLOT( generatorError( QString, QString ) ) ); @@ -360,7 +360,7 @@ DynamicWidget::showPreview() { if( m_playlist->mode() == OnDemand && !m_runningOnDemand && m_model->rowCount( QModelIndex() ) == 0 ) { // if this is a not running station, preview matching tracks generate( 20 ); // ask for more, we'll filter how many we actually want -} + } } From ae879aff933bfea1eb3fafa4037589cbfb2febf4 Mon Sep 17 00:00:00 2001 From: Christian Muehlhaeuser Date: Thu, 24 Mar 2011 14:32:18 +0100 Subject: [PATCH 086/329] * Fixed pipeline so it not calls onResolvingFinished more than once. --- src/libtomahawk/pipeline.cpp | 135 +++++++++++------- src/libtomahawk/pipeline.h | 3 + .../playlist/dynamic/DynamicModel.cpp | 4 +- 3 files changed, 90 insertions(+), 52 deletions(-) diff --git a/src/libtomahawk/pipeline.cpp b/src/libtomahawk/pipeline.cpp index b14bddb16..3ffc4a154 100644 --- a/src/libtomahawk/pipeline.cpp +++ b/src/libtomahawk/pipeline.cpp @@ -151,7 +151,6 @@ Pipeline::resolve( QID qid, bool prioritized ) void Pipeline::reportResults( QID qid, const QList< result_ptr >& results ) { - int state = 0; { QMutexLocker lock( &m_mut ); @@ -168,40 +167,32 @@ Pipeline::reportResults( QID qid, const QList< result_ptr >& results ) Q_ASSERT( false ); return; } - - state = m_qidsState.value( qid ) - 1; - if ( state ) - { - qDebug() << Q_FUNC_INFO << "replacing" << qid << state; - m_qidsState.insert( qid, state ); - } - else - { - qDebug() << Q_FUNC_INFO << "removing" << qid << state; - m_qidsState.remove( qid ); - } - - if ( !results.isEmpty() ) - { - //qDebug() << Q_FUNC_INFO << qid; - //qDebug() << "solved query:" << (qlonglong)q.data() << q->toString(); - - const query_ptr& q = m_qids.value( qid ); - q->addResults( results ); - - foreach( const result_ptr& r, q->results() ) - { - m_rids.insert( r->id(), r ); - } - } } - - if ( state == 0 ) + + const query_ptr& q = m_qids.value( qid ); + if ( !results.isEmpty() ) + { + //qDebug() << Q_FUNC_INFO << qid; + //qDebug() << "solved query:" << (qlonglong)q.data() << q->toString(); + + q->addResults( results ); + + foreach( const result_ptr& r, q->results() ) + { + m_rids.insert( r->id(), r ); + } + + if ( q->solved() ) + q->onResolvingFinished(); + } + + if ( decQIDState( q ) == 0 ) { // All resolvers have reported back their results for this query now - const query_ptr& q = m_qids.value( qid ); qDebug() << "Finished resolving:" << q->toString(); - q->onResolvingFinished(); + + if ( !q->solved() ) + q->onResolvingFinished(); shuntNext(); } @@ -225,6 +216,7 @@ Pipeline::shuntNext() return; } + qDebug() << Q_FUNC_INFO << m_qidsState.count(); // Check if we are ready to dispatch more queries if ( m_qidsState.count() >= CONCURRENT_QUERIES ) return; @@ -238,26 +230,35 @@ Pipeline::shuntNext() } if ( !q.isNull() ) + { + incQIDState( q ); shunt( q ); // bump into next stage of pipeline (highest weights are 100) + } } void Pipeline::shunt( const query_ptr& q ) { + qDebug() << Q_FUNC_INFO << q->solved() << q->toString() << q->id(); + unsigned int lastweight = 0; + unsigned int lasttimeout = 0; + if ( q->solved() ) { qDebug() << "Query solved, pipeline aborted:" << q->toString() << "numresults:" << q->results().length(); - shuntNext(); + QList< result_ptr > rl; + reportResults( q->id(), rl ); return; } - unsigned int lastweight = 0; - unsigned int lasttimeout = 0; + int thisResolver = 0; + int i = 0; foreach( Resolver* r, m_resolvers ) { + i++; if ( r->weight() >= q->lastPipelineWeight() ) continue; @@ -276,20 +277,7 @@ Pipeline::shunt( const query_ptr& q ) // resolvers aren't allowed to block in this call: qDebug() << "Dispatching to resolver" << r->name(); - { - QMutexLocker lock( &m_mut ); - int state = 0; - qDebug() << "Checking qidsstate:" << q->id(); - - if ( m_qidsState.contains( q->id() ) ) - { - state = m_qidsState.value( q->id() ); - } - -// qDebug() << Q_FUNC_INFO << "inserting to qidsstate:" << q->id() << state + 1; - m_qidsState.insert( q->id(), state + 1 ); - } - + thisResolver = i; r->resolve( q ); } else @@ -299,13 +287,21 @@ Pipeline::shunt( const query_ptr& q ) if ( lastweight > 0 ) { q->setLastPipelineWeight( lastweight ); - //qDebug() << "Shunting in" << lasttimeout << "ms, q:" << q->toString(); - new FuncTimeout( lasttimeout, boost::bind( &Pipeline::shunt, this, q ) ); + + if ( thisResolver < m_resolvers.count() ) + { + incQIDState( q ); + qDebug() << "Shunting in" << lasttimeout << "ms, q:" << q->toString(); + new FuncTimeout( lasttimeout, boost::bind( &Pipeline::shunt, this, q ) ); + } } else { //qDebug() << "Reached end of pipeline for:" << q->toString(); // reached end of pipeline + QList< result_ptr > rl; + reportResults( q->id(), rl ); + return; } shuntNext(); @@ -320,3 +316,42 @@ Pipeline::resolverSorter( const Resolver* left, const Resolver* right ) else return left->weight() > right->weight(); } + + +int +Pipeline::incQIDState( const Tomahawk::query_ptr& query ) +{ + QMutexLocker lock( &m_mut ); + + int state = 1; + if ( m_qidsState.contains( query->id() ) ) + { + state = m_qidsState.value( query->id() ) + 1; + } + + qDebug() << Q_FUNC_INFO << "inserting to qidsstate:" << query->id() << state; + m_qidsState.insert( query->id(), state ); + + return state; +} + + +int +Pipeline::decQIDState( const Tomahawk::query_ptr& query ) +{ + QMutexLocker lock( &m_mut ); + + int state = m_qidsState.value( query->id() ) - 1; + if ( state ) + { + qDebug() << Q_FUNC_INFO << "replacing" << query->id() << state; + m_qidsState.insert( query->id(), state ); + } + else + { + qDebug() << Q_FUNC_INFO << "removing" << query->id() << state; + m_qidsState.remove( query->id() ); + } + + return state; +} diff --git a/src/libtomahawk/pipeline.h b/src/libtomahawk/pipeline.h index 8ed0456bb..2c31bc28b 100644 --- a/src/libtomahawk/pipeline.h +++ b/src/libtomahawk/pipeline.h @@ -79,6 +79,9 @@ private slots: void indexReady(); private: + int incQIDState( const Tomahawk::query_ptr& query ); + int decQIDState( const Tomahawk::query_ptr& query ); + QList< Resolver* > m_resolvers; QMap< QID, unsigned int > m_qidsState; diff --git a/src/libtomahawk/playlist/dynamic/DynamicModel.cpp b/src/libtomahawk/playlist/dynamic/DynamicModel.cpp index dfbf72960..89f3cd36c 100644 --- a/src/libtomahawk/playlist/dynamic/DynamicModel.cpp +++ b/src/libtomahawk/playlist/dynamic/DynamicModel.cpp @@ -184,8 +184,8 @@ DynamicModel::filterUnresolved( const QList< query_ptr >& entries ) foreach( const query_ptr& q, entries ) { connect( q.data(), SIGNAL( resolvingFinished( bool ) ), this, SLOT( filteringTrackResolved( bool ) ) ); - Pipeline::instance()->resolve( q ); - } + } + Pipeline::instance()->resolve( entries, true ); } void From c35d77891bdd53e7489a556721e2007ee9594318 Mon Sep 17 00:00:00 2001 From: Christian Muehlhaeuser Date: Thu, 24 Mar 2011 14:50:47 +0100 Subject: [PATCH 087/329] * This should fix stations ignoring playable but not perfectly solved queries. --- .../playlist/dynamic/DynamicModel.cpp | 38 ++++++++----------- .../playlist/dynamic/DynamicModel.h | 1 - 2 files changed, 15 insertions(+), 24 deletions(-) diff --git a/src/libtomahawk/playlist/dynamic/DynamicModel.cpp b/src/libtomahawk/playlist/dynamic/DynamicModel.cpp index 89f3cd36c..43fceeaac 100644 --- a/src/libtomahawk/playlist/dynamic/DynamicModel.cpp +++ b/src/libtomahawk/playlist/dynamic/DynamicModel.cpp @@ -81,7 +81,6 @@ DynamicModel::newTrackGenerated( const Tomahawk::query_ptr& query ) { if( m_onDemandRunning ) { connect( query.data(), SIGNAL( resolvingFinished( bool ) ), this, SLOT( trackResolveFinished( bool ) ) ); - connect( query.data(), SIGNAL( solvedStateChanged( bool ) ), this, SLOT( trackResolved( bool ) ) ); append( query ); } @@ -106,31 +105,12 @@ DynamicModel::changeStation() m_playlist->generator()->startOnDemand(); } - -void -DynamicModel::trackResolved( bool resolved ) -{ - if( !resolved ) - return; - - Query* q = qobject_cast(sender()); - qDebug() << "Got successful resolved track:" << q->track() << q->artist() << m_lastResolvedRow << m_currentAttempts; - - if( m_currentAttempts > 0 ) { - qDebug() << "EMITTING AN ASK FOR COLLAPSE:" << m_lastResolvedRow << m_currentAttempts; - emit collapseFromTo( m_lastResolvedRow, m_currentAttempts ); - } - m_currentAttempts = 0; - m_searchingForNext = false; - - emit checkForOverflow(); -} - void DynamicModel::trackResolveFinished( bool success ) { - if( !success ) { // if it was successful, we've already gotten a trackResolved() signal - Query* q = qobject_cast(sender()); + Query* q = qobject_cast(sender()); + + if( !q->playable() ) { qDebug() << "Got not resolved track:" << q->track() << q->artist() << m_lastResolvedRow << m_currentAttempts; m_currentAttempts++; @@ -143,6 +123,18 @@ DynamicModel::trackResolveFinished( bool success ) emit trackGenerationFailure( tr( "Could not find a playable track.\n\nPlease change the filters or try again." ) ); } } + else { + qDebug() << "Got successful resolved track:" << q->track() << q->artist() << m_lastResolvedRow << m_currentAttempts; + + if( m_currentAttempts > 0 ) { + qDebug() << "EMITTING AN ASK FOR COLLAPSE:" << m_lastResolvedRow << m_currentAttempts; + emit collapseFromTo( m_lastResolvedRow, m_currentAttempts ); + } + m_currentAttempts = 0; + m_searchingForNext = false; + + emit checkForOverflow(); + } } diff --git a/src/libtomahawk/playlist/dynamic/DynamicModel.h b/src/libtomahawk/playlist/dynamic/DynamicModel.h index 1b8a0fade..a3cdc5f65 100644 --- a/src/libtomahawk/playlist/dynamic/DynamicModel.h +++ b/src/libtomahawk/playlist/dynamic/DynamicModel.h @@ -66,7 +66,6 @@ private slots: void newTrackGenerated( const Tomahawk::query_ptr& query ); void trackResolveFinished( bool ); - void trackResolved( bool ); void newTrackLoading(); void filteringTrackResolved( bool successful ); From f0db24aa070b0270bdb31cb5ee733da31fb0e111 Mon Sep 17 00:00:00 2001 From: Leo Franchi Date: Thu, 24 Mar 2011 10:55:55 -0400 Subject: [PATCH 088/329] only add tracks to a station if it is active try harder to not overlay spinner over text --- .../playlist/dynamic/DynamicView.cpp | 1 - .../dynamic/widgets/DynamicWidget.cpp | 23 +++++++++++-------- .../playlist/dynamic/widgets/DynamicWidget.h | 3 ++- 3 files changed, 15 insertions(+), 12 deletions(-) diff --git a/src/libtomahawk/playlist/dynamic/DynamicView.cpp b/src/libtomahawk/playlist/dynamic/DynamicView.cpp index dffee69f1..05b2c9c9d 100644 --- a/src/libtomahawk/playlist/dynamic/DynamicView.cpp +++ b/src/libtomahawk/playlist/dynamic/DynamicView.cpp @@ -144,7 +144,6 @@ DynamicView::onTrackCountChanged( unsigned int tracks ) overlay()->show(); } else { - m_working = false; overlay()->hide(); } } diff --git a/src/libtomahawk/playlist/dynamic/widgets/DynamicWidget.cpp b/src/libtomahawk/playlist/dynamic/widgets/DynamicWidget.cpp index a6853cee3..75fc46351 100644 --- a/src/libtomahawk/playlist/dynamic/widgets/DynamicWidget.cpp +++ b/src/libtomahawk/playlist/dynamic/widgets/DynamicWidget.cpp @@ -50,6 +50,7 @@ DynamicWidget::DynamicWidget( const Tomahawk::dynplaylist_ptr& playlist, QWidget , m_layout( new QVBoxLayout ) , m_resolveOnNextLoad( false ) , m_seqRevLaunched( 0 ) + , m_activePlaylist( false ) , m_setup( 0 ) , m_runningOnDemand( false ) , m_controlsChanged( false ) @@ -95,7 +96,7 @@ DynamicWidget::DynamicWidget( const Tomahawk::dynplaylist_ptr& playlist, QWidget connect( m_controls, SIGNAL( controlsChanged() ), this, SLOT( controlsChanged() ), Qt::QueuedConnection ); connect( AudioEngine::instance(), SIGNAL( started( Tomahawk::result_ptr ) ), this, SLOT( trackStarted() ) ); - connect( AudioEngine::instance(), SIGNAL( playlistChanged( PlaylistInterface* ) ), this, SLOT( playlistStopped( PlaylistInterface* ) ) ); + connect( AudioEngine::instance(), SIGNAL( playlistChanged( PlaylistInterface* ) ), this, SLOT( playlistChanged( PlaylistInterface* ) ) ); } DynamicWidget::~DynamicWidget() @@ -214,15 +215,17 @@ DynamicWidget::layoutFloatingWidgets() } void -DynamicWidget::playlistStopped( PlaylistInterface* pl ) +DynamicWidget::playlistChanged( PlaylistInterface* pl ) { - if( pl == static_cast< PlaylistInterface* >( m_view->proxyModel() ) ) // same playlist, so don't stop - return; - - // user started playing something somewhere else, so give it a rest - if( m_runningOnDemand ) { - stopStation( false ); - m_model->clear(); + if( pl == static_cast< PlaylistInterface* >( m_view->proxyModel() ) ) { // same playlist + m_activePlaylist = true; + } else { + m_activePlaylist = false; + + // user started playing something somewhere else, so give it a rest + if( m_runningOnDemand ) { + stopStation( false ); + } } } @@ -257,7 +260,7 @@ DynamicWidget::stationFailed( const QString& msg ) void DynamicWidget::trackStarted() { - if( isVisible() && !m_playlist.isNull() && + if( m_activePlaylist && !m_playlist.isNull() && m_playlist->mode() == OnDemand && !m_runningOnDemand ) { startStation(); diff --git a/src/libtomahawk/playlist/dynamic/widgets/DynamicWidget.h b/src/libtomahawk/playlist/dynamic/widgets/DynamicWidget.h index 34ae1237f..441db1e13 100644 --- a/src/libtomahawk/playlist/dynamic/widgets/DynamicWidget.h +++ b/src/libtomahawk/playlist/dynamic/widgets/DynamicWidget.h @@ -91,7 +91,7 @@ public slots: void trackStarted(); void stationFailed( const QString& ); - void playlistStopped( PlaylistInterface* ); + void playlistChanged( PlaylistInterface* ); void tracksAdded(); private slots: @@ -110,6 +110,7 @@ private: QVBoxLayout* m_layout; bool m_resolveOnNextLoad; int m_seqRevLaunched; // if we shoot off multiple createRevision calls, we don'y want to set one of the middle ones + bool m_activePlaylist; // loading animation LoadingSpinner* m_loading; From 192a9ac380ea38bfd1d752354017f39b77fdf7ac Mon Sep 17 00:00:00 2001 From: Jeff Mitchell Date: Thu, 24 Mar 2011 12:37:26 -0400 Subject: [PATCH 089/329] Bring in QJson's ldflags to actually be able to get the right link information --- src/libtomahawk/CMakeLists.txt | 1 + 1 file changed, 1 insertion(+) diff --git a/src/libtomahawk/CMakeLists.txt b/src/libtomahawk/CMakeLists.txt index d93171a9d..2a617f299 100644 --- a/src/libtomahawk/CMakeLists.txt +++ b/src/libtomahawk/CMakeLists.txt @@ -392,6 +392,7 @@ target_link_libraries( tomahawklib # External deps ${TAGLIB_LIBRARIES} + ${QJSON_LDFLAGS} ${QJSON_LIBRARIES} ${CLUCENE_LIBRARIES} ${LIBECHONEST_LIBRARY} From 4558fd7e227b7a7d424b0980fc55e146354b49dc Mon Sep 17 00:00:00 2001 From: Christian Muehlhaeuser Date: Thu, 24 Mar 2011 19:35:49 +0100 Subject: [PATCH 090/329] * Fixed linking QJson on OS X. --- src/libtomahawk/CMakeLists.txt | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/src/libtomahawk/CMakeLists.txt b/src/libtomahawk/CMakeLists.txt index 2a617f299..5dec43bd7 100644 --- a/src/libtomahawk/CMakeLists.txt +++ b/src/libtomahawk/CMakeLists.txt @@ -333,6 +333,7 @@ IF( WIN32 ) ${OS_SPECIFIC_LINK_LIBRARIES} # Thirdparty ${CMAKE_BINARY_DIR}/thirdparty/rtaudio/librtaudio.dll + ${QJSON_LDFLAGS} # System "iphlpapi.a" "ws2_32.dll" @@ -354,6 +355,7 @@ IF( APPLE ) SET( OS_SPECIFIC_LINK_LIBRARIES ${OS_SPECIFIC_LINK_LIBRARIES} # Thirdparty + ${QJSON_LIBRARIES} rtaudio # System ${COREAUDIO_LIBRARY} @@ -366,6 +368,7 @@ IF( UNIX AND NOT APPLE ) ${OS_SPECIFIC_LINK_LIBRARIES} # Thirdparty alsaplayback + ${QJSON_LDFLAGS} ) ENDIF( UNIX AND NOT APPLE ) @@ -392,8 +395,6 @@ target_link_libraries( tomahawklib # External deps ${TAGLIB_LIBRARIES} - ${QJSON_LDFLAGS} - ${QJSON_LIBRARIES} ${CLUCENE_LIBRARIES} ${LIBECHONEST_LIBRARY} ${QT_LIBRARIES} From 8167a05b913a6d3dcc124adcdeffc56427e8e34f Mon Sep 17 00:00:00 2001 From: Christian Muehlhaeuser Date: Thu, 24 Mar 2011 20:02:57 +0100 Subject: [PATCH 091/329] * Integrated sparkle signing in release script. --- admin/mac/build-release-osx.sh | 10 ++++++++++ admin/mac/sign_bundle.rb | 4 ++-- 2 files changed, 12 insertions(+), 2 deletions(-) diff --git a/admin/mac/build-release-osx.sh b/admin/mac/build-release-osx.sh index f54315ed6..b283a2e44 100755 --- a/admin/mac/build-release-osx.sh +++ b/admin/mac/build-release-osx.sh @@ -16,6 +16,11 @@ function die { } ################################################################################ +if [ -z $1 ] +then + echo This script expects the version number as a parameter, e.g. 1.0.0 + exit 1 +fi ROOT=`pwd` @@ -39,6 +44,7 @@ CLEAN='1' BUILD='1' NOTQUICK='1' CREATEDMG='1' +VERSION=$1 header "Adding Qt to app bundle" cd tomahawk.app @@ -62,6 +68,10 @@ CREATEDMG='1' cd .. mv tomahawk.app Tomahawk.app $ROOT/../admin/mac/create-dmg.sh Tomahawk.app + mv Tomahawk.dmg Tomahawk-$VERSION.dmg + + header "Creating signed Sparkle update" + $ROOT/../admin/mac/sign_bundle.rb $VERSION ~/tomahawk_sparkle_privkey.pem mv Tomahawk.app tomahawk.app header "Done!" diff --git a/admin/mac/sign_bundle.rb b/admin/mac/sign_bundle.rb index 7ffd00062..aaebb9c0c 100755 --- a/admin/mac/sign_bundle.rb +++ b/admin/mac/sign_bundle.rb @@ -6,9 +6,9 @@ if ARGV.length < 2 exit end -tarball = "tomahawk#{ARGV[0]}.tar.bz2" +tarball = "tomahawk-#{ARGV[0]}.tar.bz2" puts "Zipping: #{tarball}..." -`tar jcvf "#{tarball}" tomahawk.app` +`tar jcvf "#{tarball}" Tomahawk.app` puts "Signing..." puts `openssl dgst -sha1 -binary < "#{tarball}" | openssl dgst -dss1 -sign "#{ARGV[1]}" | openssl enc -base64` From 5ce659fb0028b05905fbf9bbc1b3d1a3e061a28a Mon Sep 17 00:00:00 2001 From: Christian Muehlhaeuser Date: Thu, 24 Mar 2011 20:05:33 +0100 Subject: [PATCH 092/329] * Fixed sparkle.rss template. --- admin/mac/sparkle.rss | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/admin/mac/sparkle.rss b/admin/mac/sparkle.rss index 83d400eae..391b34a02 100755 --- a/admin/mac/sparkle.rss +++ b/admin/mac/sparkle.rss @@ -11,7 +11,7 @@ https://github.com/tomahawk-player/tomahawk/raw/master/ChangeLog Fri, 04 Mar 2011 16:05:15 -0500 - + From 6d92d158bb5f93a196e6c3821e07e4ee6d980ff4 Mon Sep 17 00:00:00 2001 From: Jeff Mitchell Date: Thu, 24 Mar 2011 15:54:57 -0400 Subject: [PATCH 093/329] Have proper visibility of the libraries instead of assuming it's only needed on WIN32 --- src/libtomahawk/dllmacro.h | 16 ++++++++-------- thirdparty/qtweetlib/CMakeLists.txt | 4 +--- 2 files changed, 9 insertions(+), 11 deletions(-) diff --git a/src/libtomahawk/dllmacro.h b/src/libtomahawk/dllmacro.h index 550047539..55a2b6ab9 100644 --- a/src/libtomahawk/dllmacro.h +++ b/src/libtomahawk/dllmacro.h @@ -19,14 +19,14 @@ #ifndef DLLMACRO_H #define DLLMACRO_H -#ifdef WIN32 - #ifdef DLLEXPORT_PRO - #define DLLEXPORT __declspec(dllexport) - #else - #define DLLEXPORT __declspec(dllimport) - #endif -#else - #define DLLEXPORT +#include + +#ifndef DLLEXPORT +# if defined (DLLEXPORT_PRO) +# define DLLEXPORT Q_DECL_EXPORT +# else +# define DLLEXPORT Q_DECL_IMPORT +# endif #endif #endif diff --git a/thirdparty/qtweetlib/CMakeLists.txt b/thirdparty/qtweetlib/CMakeLists.txt index c68f22031..820b950b4 100644 --- a/thirdparty/qtweetlib/CMakeLists.txt +++ b/thirdparty/qtweetlib/CMakeLists.txt @@ -9,10 +9,10 @@ INCLUDE( ${QT_USE_FILE} ) add_definitions( ${QT_DEFINITIONS} ) add_definitions( -DQT_SHARED ) +add_definitions( -DQTWEETLIB_MAKEDLL ) if(WIN32) set(PLATFORM_SPECIFIC_LIBS "ws2_32.dll" "advapi32.dll" ) - add_definitions( -DQTWEETLIB_MAKEDLL ) endif(WIN32) set(TOMAHAWK_QTWEETLIB_SOURCES @@ -176,8 +176,6 @@ target_link_libraries(tomahawk_qtweetlib qjson ) -#SET_TARGET_PROPERTIES( tomahawk_qtweetlib PROPERTIES DEFINE_SYMBOL MAKE_QTWEETLIB_LIB ) - INCLUDE( ${CMAKE_CURRENT_SOURCE_DIR}/twitter-api-keys ) INSTALL(TARGETS tomahawk_qtweetlib DESTINATION lib${LIB_SUFFIX}) From 9c60067831af1d10f9dcbb953b3e28a43cadcb5c Mon Sep 17 00:00:00 2001 From: Christian Muehlhaeuser Date: Fri, 25 Mar 2011 00:35:03 +0100 Subject: [PATCH 094/329] * Announce ourselves as Extended Away on jabber. --- src/sip/jabber/jabber_p.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/sip/jabber/jabber_p.cpp b/src/sip/jabber/jabber_p.cpp index d4cbe801c..750546d36 100644 --- a/src/sip/jabber/jabber_p.cpp +++ b/src/sip/jabber/jabber_p.cpp @@ -158,7 +158,7 @@ Jabber_p::go() m_client->disco()->addFeature( "tomahawk:player" ); */ - m_client->setPresence( Presence::Available, 1, "Tomahawk available" ); + m_client->setPresence( Presence::XA, 1, "Tomahawk available" ); // m_client->connect(); // return; From 09a85034e31ccf4b6cb1a4e385d4300a95debbd1 Mon Sep 17 00:00:00 2001 From: Christian Muehlhaeuser Date: Fri, 25 Mar 2011 00:43:23 +0100 Subject: [PATCH 095/329] * Set jabber priority to -127, which seems to be lowest valid one. --- src/sip/jabber/jabber_p.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/sip/jabber/jabber_p.cpp b/src/sip/jabber/jabber_p.cpp index 750546d36..aafd29543 100644 --- a/src/sip/jabber/jabber_p.cpp +++ b/src/sip/jabber/jabber_p.cpp @@ -158,7 +158,7 @@ Jabber_p::go() m_client->disco()->addFeature( "tomahawk:player" ); */ - m_client->setPresence( Presence::XA, 1, "Tomahawk available" ); + m_client->setPresence( Presence::XA, -127, "Tomahawk available" ); // m_client->connect(); // return; From 28a0ac7a2c5d328f8e2a5a6c0e51c3d6fb25f2ca Mon Sep 17 00:00:00 2001 From: Christian Muehlhaeuser Date: Fri, 25 Mar 2011 02:57:26 +0100 Subject: [PATCH 096/329] * Updated changelog for first release. --- ChangeLog | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/ChangeLog b/ChangeLog index ac67013ec..e2d364e02 100644 --- a/ChangeLog +++ b/ChangeLog @@ -1,3 +1,2 @@ Version 0.0.1: - Features: - * Knocks your socks off + * First public release. From c23422ff8e82a4d878b7f5a26b9906eb8b4d6fc5 Mon Sep 17 00:00:00 2001 From: "Guillermo A. Amaral" Date: Fri, 25 Mar 2011 07:58:35 +0800 Subject: [PATCH 097/329] More build fixes --- src/CMakeLists.txt | 1 + thirdparty/qtweetlib/CMakeLists.txt | 1 + 2 files changed, 2 insertions(+) diff --git a/src/CMakeLists.txt b/src/CMakeLists.txt index babe44b77..979c2f9e8 100644 --- a/src/CMakeLists.txt +++ b/src/CMakeLists.txt @@ -168,6 +168,7 @@ IF( APPLE ) ENDIF( APPLE ) IF(GLOOX_FOUND) + INCLUDE_DIRECTORIES( ${GLOOX_INCLUDE_DIR} ) SET( tomahawkHeaders ${tomahawkHeaders} xmppbot/xmppbot.h ) SET( tomahawkSources ${tomahawkSources} xmppbot/xmppbot.cpp ) ENDIF(GLOOX_FOUND) diff --git a/thirdparty/qtweetlib/CMakeLists.txt b/thirdparty/qtweetlib/CMakeLists.txt index c6b21831d..f3beb6c46 100644 --- a/thirdparty/qtweetlib/CMakeLists.txt +++ b/thirdparty/qtweetlib/CMakeLists.txt @@ -181,6 +181,7 @@ endif(APPLE) target_link_libraries(tomahawk_qtweetlib ${QT_LIBRARIES} ${QJSON_FLAGS} + ${QJSON_LIBRARIES} ) INCLUDE( ${CMAKE_CURRENT_SOURCE_DIR}/twitter-api-keys ) From 9048293e88201738412fb6891e5a5b29ead4942b Mon Sep 17 00:00:00 2001 From: Leo Franchi Date: Thu, 24 Mar 2011 22:05:29 -0400 Subject: [PATCH 098/329] Add AUTHORS file. muesli, change it as you see fit. --- AUTHORS | 13 +++++++++++++ 1 file changed, 13 insertions(+) create mode 100644 AUTHORS diff --git a/AUTHORS b/AUTHORS new file mode 100644 index 000000000..2a3a9adf8 --- /dev/null +++ b/AUTHORS @@ -0,0 +1,13 @@ +Tomahawk is primarily authored by: + +* Christian Muehlhaeuser + +Contributors include: + +* Leo Franchi +* Dominik Schmidt +* Jeff Mitchell +* J Herskowitz +* Alejandro Wainzinger +* Harald Sitter +* Steve Robertson From 22b483bd2bc395b07b7ee2a3cca66064d35386a3 Mon Sep 17 00:00:00 2001 From: Christian Muehlhaeuser Date: Fri, 25 Mar 2011 03:06:26 +0100 Subject: [PATCH 099/329] * Added domme's email. --- AUTHORS | 7 +++++-- 1 file changed, 5 insertions(+), 2 deletions(-) diff --git a/AUTHORS b/AUTHORS index 2a3a9adf8..c210243d3 100644 --- a/AUTHORS +++ b/AUTHORS @@ -5,9 +5,12 @@ Tomahawk is primarily authored by: Contributors include: * Leo Franchi -* Dominik Schmidt +* Dominik Schmidt * Jeff Mitchell * J Herskowitz * Alejandro Wainzinger + +Thanks to: + * Harald Sitter -* Steve Robertson +* Steve Robertson From 97bf7a2b4e6908cc574fb6c3568d94edabaa52ef Mon Sep 17 00:00:00 2001 From: Christian Muehlhaeuser Date: Thu, 24 Mar 2011 20:13:10 +0000 Subject: [PATCH 100/329] * Updated beta sparkle feed-templates. --- admin/mac/sparkle-beta.rss | 2 +- admin/win/nsi/revision.txt | 2 +- admin/win/sparklewin-beta.rss | 2 +- admin/win/sparklewin.rss | 2 +- 4 files changed, 4 insertions(+), 4 deletions(-) diff --git a/admin/mac/sparkle-beta.rss b/admin/mac/sparkle-beta.rss index 604fd8dd8..0a38d7ee5 100755 --- a/admin/mac/sparkle-beta.rss +++ b/admin/mac/sparkle-beta.rss @@ -11,7 +11,7 @@ https://github.com/tomahawk-player/tomahawk/raw/master/ChangeLog Fri, 04 Mar 2011 16:05:15 -0500 - + diff --git a/admin/win/nsi/revision.txt b/admin/win/nsi/revision.txt index 615be700b..7730ef7f3 100644 --- a/admin/win/nsi/revision.txt +++ b/admin/win/nsi/revision.txt @@ -1 +1 @@ -85 \ No newline at end of file +89 \ No newline at end of file diff --git a/admin/win/sparklewin-beta.rss b/admin/win/sparklewin-beta.rss index 52e9d0302..9d1f276cc 100644 --- a/admin/win/sparklewin-beta.rss +++ b/admin/win/sparklewin-beta.rss @@ -11,7 +11,7 @@ https://github.com/tomahawk-player/tomahawk/blob/master/ChangeLog Fri, 04 Mar 2011 16:05:15 -0500 - + diff --git a/admin/win/sparklewin.rss b/admin/win/sparklewin.rss index 52e9d0302..cec8b11c6 100644 --- a/admin/win/sparklewin.rss +++ b/admin/win/sparklewin.rss @@ -11,7 +11,7 @@ https://github.com/tomahawk-player/tomahawk/blob/master/ChangeLog Fri, 04 Mar 2011 16:05:15 -0500 - + From 040bcf113ee4563af07d13587d55181485ad718b Mon Sep 17 00:00:00 2001 From: Christian Muehlhaeuser Date: Thu, 24 Mar 2011 20:44:46 +0000 Subject: [PATCH 101/329] * Fixed compiling on Windows. --- admin/win/Toolchain-mingw32-openSUSE.cmake | 1 + admin/win/nsi/revision.txt | 2 +- src/CMakeLists.txt | 3 +-- 3 files changed, 3 insertions(+), 3 deletions(-) diff --git a/admin/win/Toolchain-mingw32-openSUSE.cmake b/admin/win/Toolchain-mingw32-openSUSE.cmake index ceb2ce9be..80a22964d 100644 --- a/admin/win/Toolchain-mingw32-openSUSE.cmake +++ b/admin/win/Toolchain-mingw32-openSUSE.cmake @@ -17,3 +17,4 @@ SET(WINDRES_EXECUTABLE /usr/bin/i686-w64-mingw32-windres) # libs with broken find modules SET(TAGLIB_FOUND true) SET(TAGLIB_LIBRARIES ${CMAKE_FIND_ROOT_PATH}/lib/libtag.dll.a) +SET(TAGLIB_INCLUDES ${CMAKE_FIND_ROOT_PATH}/include/taglib) diff --git a/admin/win/nsi/revision.txt b/admin/win/nsi/revision.txt index 7730ef7f3..0fa6a7b08 100644 --- a/admin/win/nsi/revision.txt +++ b/admin/win/nsi/revision.txt @@ -1 +1 @@ -89 \ No newline at end of file +90 \ No newline at end of file diff --git a/src/CMakeLists.txt b/src/CMakeLists.txt index 979c2f9e8..a28f4d97b 100644 --- a/src/CMakeLists.txt +++ b/src/CMakeLists.txt @@ -137,11 +137,10 @@ INCLUDE_DIRECTORIES( ${THIRDPARTY_DIR}/alsa-playback ${THIRDPARTY_DIR}/rtaudio - ${THIRDPARTY_DIR}/qxt/qxtweb-standalone/qxtweb/ + ${THIRDPARTY_DIR}/qxt/qxtweb-standalone/qxtweb ${THIRDPARTY_DIR}/qtweetlib/qtweetlib/src ${THIRDPARTY_DIR}/qtweetlib/tomahawk-custom - ${TAGLIB_INCLUDES} ${QJSON_INCLUDE_DIR} ${LIBECHONEST_INCLUDE_DIR} From 32d82159d91e46a4569b62ed3cfdb3e4bb0d9a56 Mon Sep 17 00:00:00 2001 From: "Guillermo A. Amaral" Date: Fri, 25 Mar 2011 04:19:37 +0800 Subject: [PATCH 102/329] Fix to allow building with dependencies that are outside the usual system locations. --- src/CMakeLists.txt | 1 + src/libtomahawk/CMakeLists.txt | 1 + .../playlist/dynamic/echonest/EchonestSteerer.cpp | 2 +- src/musicscanner.h | 5 +++-- thirdparty/qtweetlib/CMakeLists.txt | 3 ++- 5 files changed, 8 insertions(+), 4 deletions(-) diff --git a/src/CMakeLists.txt b/src/CMakeLists.txt index 68b3280bd..babe44b77 100644 --- a/src/CMakeLists.txt +++ b/src/CMakeLists.txt @@ -143,6 +143,7 @@ INCLUDE_DIRECTORIES( ${TAGLIB_INCLUDES} + ${QJSON_INCLUDE_DIR} ${LIBECHONEST_INCLUDE_DIR} ${LIBECHONEST_INCLUDE_DIR}/.. ) diff --git a/src/libtomahawk/CMakeLists.txt b/src/libtomahawk/CMakeLists.txt index 5dec43bd7..f45c391a3 100644 --- a/src/libtomahawk/CMakeLists.txt +++ b/src/libtomahawk/CMakeLists.txt @@ -305,6 +305,7 @@ set( libUI ${libUI} include_directories( . ${CMAKE_CURRENT_BINARY_DIR} ${CMAKE_CURRENT_BINARY_DIR}/.. .. ${CMAKE_CURRENT_SOURCE_DIR} ${QT_INCLUDE_DIR} + ${QJSON_INCLUDE_DIR} ${LIBECHONEST_INCLUDE_DIR} ${LIBECHONEST_INCLUDE_DIR}/.. ${CLUCENE_INCLUDE_DIR} diff --git a/src/libtomahawk/playlist/dynamic/echonest/EchonestSteerer.cpp b/src/libtomahawk/playlist/dynamic/echonest/EchonestSteerer.cpp index b029dcf7d..f8f36a34a 100644 --- a/src/libtomahawk/playlist/dynamic/echonest/EchonestSteerer.cpp +++ b/src/libtomahawk/playlist/dynamic/echonest/EchonestSteerer.cpp @@ -25,7 +25,7 @@ #include #include #include -#include +#include #include #include #include diff --git a/src/musicscanner.h b/src/musicscanner.h index bfde9e075..8fbca2739 100644 --- a/src/musicscanner.h +++ b/src/musicscanner.h @@ -19,8 +19,9 @@ #ifndef MUSICSCANNER_H #define MUSICSCANNER_H -#include -#include +/* taglib */ +#include +#include #include #include diff --git a/thirdparty/qtweetlib/CMakeLists.txt b/thirdparty/qtweetlib/CMakeLists.txt index 820b950b4..df7fe64d4 100644 --- a/thirdparty/qtweetlib/CMakeLists.txt +++ b/thirdparty/qtweetlib/CMakeLists.txt @@ -164,6 +164,7 @@ include_directories( . ${QT_INCLUDE_DIR} ${QT_INCLUDES} + ${QJSON_INCLUDE_DIR} qtweetlib/src ) @@ -173,7 +174,7 @@ ADD_LIBRARY(tomahawk_qtweetlib SHARED ${TOMAHAWK_QTWEETLIB_SOURCES} ${TOMAHAWK_Q target_link_libraries(tomahawk_qtweetlib ${QT_LIBRARIES} - qjson + ${QJSON_LIBRARIES} ) INCLUDE( ${CMAKE_CURRENT_SOURCE_DIR}/twitter-api-keys ) From 8e7c4407cf01d7d702f6255ca050a538137df0f0 Mon Sep 17 00:00:00 2001 From: Jeff Mitchell Date: Thu, 24 Mar 2011 17:36:48 -0400 Subject: [PATCH 103/329] Update twitter to make it more robust --- src/sip/twitter/twitter.cpp | 28 ++++++++++++++++++---------- 1 file changed, 18 insertions(+), 10 deletions(-) diff --git a/src/sip/twitter/twitter.cpp b/src/sip/twitter/twitter.cpp index 3c92b6f8b..4250a6637 100644 --- a/src/sip/twitter/twitter.cpp +++ b/src/sip/twitter/twitter.cpp @@ -451,14 +451,15 @@ TwitterPlugin::directMessages( const QList< QTweetDMStatus > &messages ) QString host = splitList[1].mid( 5 ); QString node = splitList[3].mid( 5 ); QString pkey = splitList[4].mid( 5 ); - qDebug() << "TwitterPlugin found a peerstart message from " << status.senderScreenName() << " with host " << host << " and port " << port << " and pkey " << pkey << " destined for node " << node; - - if ( node != Database::instance()->dbid() ) + QStringList splitNode = node.split(';'); + if ( splitNode.length() != 2 ) { - qDebug() << "Not destined for this node; leaving it alone and not answering"; + qDebug() << "Old-style node info found, ignoring"; continue; } + qDebug() << "TwitterPlugin found a peerstart message from " << status.senderScreenName() << " with host " << host << " and port " << port << " and pkey " << pkey << " and node " << splitNode[0] << " destined for node " << splitNode[1]; + QHash< QString, QVariant > peerData = ( m_cachedPeers.contains( status.senderScreenName() ) ) ? m_cachedPeers[status.senderScreenName()].toHash() : QHash< QString, QVariant >(); @@ -466,12 +467,18 @@ TwitterPlugin::directMessages( const QList< QTweetDMStatus > &messages ) peerData["host"] = QVariant::fromValue< QString >( host ); peerData["port"] = QVariant::fromValue< int >( port ); peerData["pkey"] = QVariant::fromValue< QString >( pkey ); + peerData["node"] = QVariant::fromValue< QString >( splitNode[0] ); peerData["dirty"] = QVariant::fromValue< bool >( true ); QMetaObject::invokeMethod( this, "registerOffer", Q_ARG( QString, status.senderScreenName() ), QGenericArgument( "QHash< QString, QVariant >", (const void*)&peerData ) ); + + if ( Database::instance()->dbid().startsWith( splitNode[1] ) ) + { + qDebug() << "TwitterPlugin found message destined for this node; destroying it"; + if ( !m_directMessageDestroy.isNull() ) + m_directMessageDestroy.data()->destroyMessage( status.id() ); + } - if ( !m_directMessageDestroy.isNull() ) - m_directMessageDestroy.data()->destroyMessage( status.id() ); } TomahawkSettings::instance()->setTwitterCachedDirectMessagesSinceId( m_cachedDirectMessagesSinceId ); @@ -554,10 +561,11 @@ void TwitterPlugin::sendOffer( const QString &screenName, const QHash< QString, QVariant > &peerData ) { qDebug() << Q_FUNC_INFO; - QString offerString = QString( "TOMAHAWKPEER:Host=%1:Port=%2:Node=%3:PKey=%4" ).arg( peerData["ohst"].toString() ) - .arg( peerData["oprt"].toString() ) - .arg( peerData["node"].toString() ) - .arg( peerData["okey"].toString() ); + QString offerString = QString( "TOMAHAWKPEER:Host=%1:Port=%2:Node=%3;%4:PKey=%5" ).arg( peerData["ohst"].toString() ) + .arg( peerData["oprt"].toString() ) + .arg( Database::instance()->dbid() ) + .arg( peerData["node"].toString().left( 8 ) ) + .arg( peerData["okey"].toString() ); qDebug() << "TwitterPlugin sending message to " << screenName << ": " << offerString; if( !m_directMessageNew.isNull() ) m_directMessageNew.data()->post( screenName, offerString ); From 8c73040a0df63d59d035d27a3d47e544246da3ed Mon Sep 17 00:00:00 2001 From: Christian Muehlhaeuser Date: Thu, 24 Mar 2011 22:37:21 +0100 Subject: [PATCH 104/329] * Removed manually adding peers from the menu. --- src/tomahawkwindow.cpp | 4 +++- src/tomahawkwindow.ui | 6 ------ 2 files changed, 3 insertions(+), 7 deletions(-) diff --git a/src/tomahawkwindow.cpp b/src/tomahawkwindow.cpp index 363c4fb53..cc6ea8584 100644 --- a/src/tomahawkwindow.cpp +++ b/src/tomahawkwindow.cpp @@ -254,7 +254,7 @@ TomahawkWindow::setupSignals() //

connect( ui->actionPreferences, SIGNAL( triggered() ), SLOT( showSettingsDialog() ) ); connect( ui->actionToggleConnect, SIGNAL( triggered() ), APP->sipHandler(), SLOT( toggleConnect() ) ); - connect( ui->actionAddPeerManually, SIGNAL( triggered() ), SLOT( addPeerManually() ) ); +// connect( ui->actionAddPeerManually, SIGNAL( triggered() ), SLOT( addPeerManually() ) ); connect( ui->actionRescanCollection, SIGNAL( triggered() ), SLOT( updateCollectionManually() ) ); connect( ui->actionLoadXSPF, SIGNAL( triggered() ), SLOT( loadSpiff() )); connect( ui->actionCreatePlaylist, SIGNAL( triggered() ), SLOT( createPlaylist() )); @@ -353,12 +353,14 @@ TomahawkWindow::addPeerManually() Servent::instance()->connectToPeer( addr, port, key ); } + void TomahawkWindow::pluginMenuAdded( QMenu* menu ) { ui->menuNetwork->addMenu( menu ); } + void TomahawkWindow::pluginMenuRemoved( QMenu* menu ) { diff --git a/src/tomahawkwindow.ui b/src/tomahawkwindow.ui index 94a6cecd1..6b142c616 100644 --- a/src/tomahawkwindow.ui +++ b/src/tomahawkwindow.ui @@ -71,7 +71,6 @@ - @@ -102,11 +101,6 @@ Go &online - - - Add &Peer Manually... - - Add &Friend... From 80223e6b2a66efb95033deb4a274d7c61801c34f Mon Sep 17 00:00:00 2001 From: Jeff Mitchell Date: Thu, 24 Mar 2011 17:40:18 -0400 Subject: [PATCH 105/329] Don't use semicolons with colons, it's easy to miss that they're different...use an asterisk instead --- src/sip/twitter/twitter.cpp | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/sip/twitter/twitter.cpp b/src/sip/twitter/twitter.cpp index 4250a6637..334a6c0f0 100644 --- a/src/sip/twitter/twitter.cpp +++ b/src/sip/twitter/twitter.cpp @@ -451,7 +451,7 @@ TwitterPlugin::directMessages( const QList< QTweetDMStatus > &messages ) QString host = splitList[1].mid( 5 ); QString node = splitList[3].mid( 5 ); QString pkey = splitList[4].mid( 5 ); - QStringList splitNode = node.split(';'); + QStringList splitNode = node.split('*'); if ( splitNode.length() != 2 ) { qDebug() << "Old-style node info found, ignoring"; @@ -561,7 +561,7 @@ void TwitterPlugin::sendOffer( const QString &screenName, const QHash< QString, QVariant > &peerData ) { qDebug() << Q_FUNC_INFO; - QString offerString = QString( "TOMAHAWKPEER:Host=%1:Port=%2:Node=%3;%4:PKey=%5" ).arg( peerData["ohst"].toString() ) + QString offerString = QString( "TOMAHAWKPEER:Host=%1:Port=%2:Node=%3*%4:PKey=%5" ).arg( peerData["ohst"].toString() ) .arg( peerData["oprt"].toString() ) .arg( Database::instance()->dbid() ) .arg( peerData["node"].toString().left( 8 ) ) From 8135c8e6c7134bb7e628ebd0506aad42b0dd62ba Mon Sep 17 00:00:00 2001 From: Christian Muehlhaeuser Date: Fri, 25 Mar 2011 04:33:23 +0100 Subject: [PATCH 106/329] * Fixed sidebar animation on win32. --- src/sourcetree/sourcetreeview.cpp | 2 ++ 1 file changed, 2 insertions(+) diff --git a/src/sourcetree/sourcetreeview.cpp b/src/sourcetree/sourcetreeview.cpp index 98d9e5b16..d235437d1 100644 --- a/src/sourcetree/sourcetreeview.cpp +++ b/src/sourcetree/sourcetreeview.cpp @@ -504,6 +504,8 @@ SourceDelegate::paint( QPainter* painter, const QStyleOptionViewItem& option, co smaller.setPointSize( smaller.pointSize() - 2 ); painter->setFont( smaller ); o.font = smaller; +#else + o.font = painter->font(); #endif if ( ( option.state & QStyle::State_Enabled ) == QStyle::State_Enabled ) From 9c339ac55d7a7417b77b8ace08f9c2d9763baa60 Mon Sep 17 00:00:00 2001 From: Christian Muehlhaeuser Date: Fri, 25 Mar 2011 04:48:09 +0100 Subject: [PATCH 107/329] Revert "* Fixed sidebar animation on win32." This reverts commit 8135c8e6c7134bb7e628ebd0506aad42b0dd62ba. --- src/sourcetree/sourcetreeview.cpp | 2 -- 1 file changed, 2 deletions(-) diff --git a/src/sourcetree/sourcetreeview.cpp b/src/sourcetree/sourcetreeview.cpp index d235437d1..98d9e5b16 100644 --- a/src/sourcetree/sourcetreeview.cpp +++ b/src/sourcetree/sourcetreeview.cpp @@ -504,8 +504,6 @@ SourceDelegate::paint( QPainter* painter, const QStyleOptionViewItem& option, co smaller.setPointSize( smaller.pointSize() - 2 ); painter->setFont( smaller ); o.font = smaller; -#else - o.font = painter->font(); #endif if ( ( option.state & QStyle::State_Enabled ) == QStyle::State_Enabled ) From 4e878a85179da86ef19527a68212ed205c03ef7a Mon Sep 17 00:00:00 2001 From: Christian Muehlhaeuser Date: Thu, 24 Mar 2011 21:41:07 +0000 Subject: [PATCH 108/329] * Added imageformats plugins to installer. --- admin/win/nsi/revision.txt | 2 +- admin/win/nsi/tomahawk.nsi | 7 +++++++ 2 files changed, 8 insertions(+), 1 deletion(-) diff --git a/admin/win/nsi/revision.txt b/admin/win/nsi/revision.txt index 0fa6a7b08..69226f729 100644 --- a/admin/win/nsi/revision.txt +++ b/admin/win/nsi/revision.txt @@ -1 +1 @@ -90 \ No newline at end of file +92 \ No newline at end of file diff --git a/admin/win/nsi/tomahawk.nsi b/admin/win/nsi/tomahawk.nsi index 8844276c9..32e991d17 100755 --- a/admin/win/nsi/tomahawk.nsi +++ b/admin/win/nsi/tomahawk.nsi @@ -23,6 +23,7 @@ !define BUILD_PATH "${ROOT_PATH}\build" !define QT_DLL_PATH "${MING_BIN}" !define SQLITE_DLL_PATH "${MING_LIB}/qt4/plugins/sqldrivers" +!define IMAGEFORMATS_DLL_PATH "${MING_LIB}/qt4/plugins/imageformats" ;----------------------------------------------------------------------------- ; Increment installer revision number as part of this script. @@ -292,6 +293,12 @@ Section "Tomahawk Player" SEC_TOMAHAWK_PLAYER SetOutPath "$INSTDIR\sqldrivers" File "${SQLITE_DLL_PATH}\qsqlite4.dll" SetOutPath "$INSTDIR" + + ;Image plugins + SetOutPath "$INSTDIR\imageformats" + File "${IMAGEFORMATS_DLL_PATH}\qgif4.dll" + File "${IMAGEFORMATS_DLL_PATH}\qjpeg4.dll" + SetOutPath "$INSTDIR" ;Cygwin/c++ stuff ;File "${MING_DLL_PATH}\cygmad-0.dll" From cb5eceabb90182e4ebe4b3a5f5ef4ee7dd1f883c Mon Sep 17 00:00:00 2001 From: Christian Muehlhaeuser Date: Fri, 25 Mar 2011 05:40:52 +0100 Subject: [PATCH 109/329] * Shrink WelcomeWidget's overlay widget. --- src/libtomahawk/widgets/welcomewidget.cpp | 1 + 1 file changed, 1 insertion(+) diff --git a/src/libtomahawk/widgets/welcomewidget.cpp b/src/libtomahawk/widgets/welcomewidget.cpp index d736aa2dc..cc4460895 100644 --- a/src/libtomahawk/widgets/welcomewidget.cpp +++ b/src/libtomahawk/widgets/welcomewidget.cpp @@ -41,6 +41,7 @@ WelcomeWidget::WelcomeWidget( QWidget* parent ) ui->setupUi( this ); ui->playlistWidget->setItemDelegate( new PlaylistDelegate() ); + ui->playlistWidget->overlay()->resize( 380, 86 ); ui->tracksView->overlay()->setEnabled( false ); m_tracksModel = new PlaylistModel( ui->tracksView ); From f72fbce99ec3f1c50c29a882a3be60d37a0f9749 Mon Sep 17 00:00:00 2001 From: Christian Muehlhaeuser Date: Thu, 24 Mar 2011 23:05:24 +0000 Subject: [PATCH 110/329] * Included missing libjpeg.dll in Windows installer. --- admin/win/nsi/revision.txt | 2 +- admin/win/nsi/tomahawk.nsi | 1 + 2 files changed, 2 insertions(+), 1 deletion(-) diff --git a/admin/win/nsi/revision.txt b/admin/win/nsi/revision.txt index 69226f729..bd753ccc4 100644 --- a/admin/win/nsi/revision.txt +++ b/admin/win/nsi/revision.txt @@ -1 +1 @@ -92 \ No newline at end of file +94 \ No newline at end of file diff --git a/admin/win/nsi/tomahawk.nsi b/admin/win/nsi/tomahawk.nsi index 32e991d17..d2681ef8d 100755 --- a/admin/win/nsi/tomahawk.nsi +++ b/admin/win/nsi/tomahawk.nsi @@ -320,6 +320,7 @@ Section "Tomahawk Player" SEC_TOMAHAWK_PLAYER File "${MING_DLL_PATH}\libtag.dll" File "${MING_DLL_PATH}\libgloox-8.dll" File "${MING_DLL_PATH}\libpng15-15.dll" + File "${MING_DLL_PATH}\libjpeg-8.dll" File "${MING_DLL_PATH}\zlib1.dll" File "${MING_DLL_PATH}\libechonest.dll" From 1780781e124e479f686d87855b4c21bfefca697b Mon Sep 17 00:00:00 2001 From: Jeff Mitchell Date: Thu, 24 Mar 2011 19:06:36 -0400 Subject: [PATCH 111/329] Fix qtweetlib qjson linking too --- thirdparty/qtweetlib/CMakeLists.txt | 10 ++++++++-- 1 file changed, 8 insertions(+), 2 deletions(-) diff --git a/thirdparty/qtweetlib/CMakeLists.txt b/thirdparty/qtweetlib/CMakeLists.txt index df7fe64d4..c6b21831d 100644 --- a/thirdparty/qtweetlib/CMakeLists.txt +++ b/thirdparty/qtweetlib/CMakeLists.txt @@ -172,10 +172,16 @@ qt4_wrap_cpp( TOMAHAWK_QTWEETLIB_MOC ${TOMAHAWK_QTWEETLIB_HEADERS} ) ADD_LIBRARY(tomahawk_qtweetlib SHARED ${TOMAHAWK_QTWEETLIB_SOURCES} ${TOMAHAWK_QTWEETLIB_MOC}) +if(APPLE) + SET(QJSON_FLAGS ${QJSON_LIBRARIES}) +else(APPLE) + SET(QJSON_FLAGS ${QJSON_LDFLAGS}) +endif(APPLE) + target_link_libraries(tomahawk_qtweetlib ${QT_LIBRARIES} - ${QJSON_LIBRARIES} -) + ${QJSON_FLAGS} +) INCLUDE( ${CMAKE_CURRENT_SOURCE_DIR}/twitter-api-keys ) From b7a1cb8d993da502ff801dfcc559279422f5905e Mon Sep 17 00:00:00 2001 From: Jeff Mitchell Date: Thu, 24 Mar 2011 19:18:42 -0400 Subject: [PATCH 112/329] Add liblastfm2 and make tomahawk build against it --- CMakeLists.txt | 6 +- src/CMakeLists.txt | 3 +- thirdparty/CMakeLists.txt | 1 + thirdparty/liblastfm2/CMakeLists.txt | 27 + thirdparty/liblastfm2/COPYING | 621 ++++++++++++ thirdparty/liblastfm2/README | 138 +++ thirdparty/liblastfm2/admin/lastfm.h.rb | 5 + thirdparty/liblastfm2/admin/platform.rb | 101 ++ thirdparty/liblastfm2/admin/qpp | 79 ++ thirdparty/liblastfm2/admin/utils.rb | 40 + thirdparty/liblastfm2/admin/which_qmake.rb | 38 + .../cmake/modules/FindLibFFTW3.cmake | 45 + .../cmake/modules/FindLibSamplerate.cmake | 22 + thirdparty/liblastfm2/configure | 127 +++ thirdparty/liblastfm2/demos/demo1.cpp | 95 ++ thirdparty/liblastfm2/demos/demo2.cpp | 114 +++ thirdparty/liblastfm2/demos/demo3.cpp | 63 ++ thirdparty/liblastfm2/demos/demos.pro | 3 + thirdparty/liblastfm2/src/CMakeLists.txt | 114 +++ thirdparty/liblastfm2/src/core/README | 8 + thirdparty/liblastfm2/src/core/UrlBuilder.cpp | 83 ++ thirdparty/liblastfm2/src/core/UrlBuilder.h | 69 ++ thirdparty/liblastfm2/src/core/XmlQuery.cpp | 64 ++ thirdparty/liblastfm2/src/core/XmlQuery.h | 77 ++ thirdparty/liblastfm2/src/core/misc.cpp | 205 ++++ thirdparty/liblastfm2/src/core/misc.h | 111 ++ .../liblastfm2/src/fingerprint/CMakeLists.txt | 32 + .../liblastfm2/src/fingerprint/Collection.cpp | 267 +++++ .../liblastfm2/src/fingerprint/Collection.h | 59 ++ .../liblastfm2/src/fingerprint/EXAMPLE.cpp | 76 ++ .../src/fingerprint/Fingerprint.cpp | 300 ++++++ .../liblastfm2/src/fingerprint/Fingerprint.h | 116 +++ .../src/fingerprint/FingerprintableSource.h | 48 + .../liblastfm2/src/fingerprint/Sha256.cpp | 480 +++++++++ .../liblastfm2/src/fingerprint/Sha256.h | 180 ++++ .../src/fingerprint/contrib/AacSource.cpp | 953 ++++++++++++++++++ .../src/fingerprint/contrib/AacSource.h | 46 + .../src/fingerprint/contrib/AacSource_p.h | 94 ++ .../src/fingerprint/contrib/FlacSource.cpp | 339 +++++++ .../src/fingerprint/contrib/FlacSource.h | 74 ++ .../src/fingerprint/contrib/MadSource.cpp | 514 ++++++++++ .../src/fingerprint/contrib/MadSource.h | 69 ++ .../src/fingerprint/contrib/VorbisSource.cpp | 204 ++++ .../src/fingerprint/contrib/VorbisSource.h | 47 + .../contrib/lastfm-fingerprint.pro | 13 + .../src/fingerprint/contrib/main.cpp | 173 ++++ .../src/fingerprint/fingerprint.pro | 25 + .../src/fingerprint/fplib/CircularArray.h | 292 ++++++ .../src/fingerprint/fplib/Filter.cpp | 128 +++ .../liblastfm2/src/fingerprint/fplib/Filter.h | 47 + .../fplib/FingerprintExtractor.cpp | 786 +++++++++++++++ .../fingerprint/fplib/FingerprintExtractor.h | 77 ++ .../src/fingerprint/fplib/FloatingAverage.h | 106 ++ .../src/fingerprint/fplib/OptFFT.cpp | 411 ++++++++ .../liblastfm2/src/fingerprint/fplib/OptFFT.h | 63 ++ .../src/fingerprint/fplib/fp_helper_fun.h | 443 ++++++++ thirdparty/liblastfm2/src/global.h | 136 +++ thirdparty/liblastfm2/src/lastfm.pro | 20 + .../liblastfm2/src/radio/RadioStation.cpp | 201 ++++ .../liblastfm2/src/radio/RadioStation.h | 123 +++ .../liblastfm2/src/radio/RadioTuner.cpp | 152 +++ thirdparty/liblastfm2/src/radio/RadioTuner.h | 76 ++ .../src/scrobble/Audioscrobbler.cpp | 202 ++++ .../liblastfm2/src/scrobble/Audioscrobbler.h | 75 ++ .../liblastfm2/src/scrobble/ScrobbleCache.cpp | 163 +++ .../liblastfm2/src/scrobble/ScrobbleCache.h | 84 ++ .../liblastfm2/src/scrobble/ScrobblePoint.h | 59 ++ .../liblastfm2/src/types/AbstractType.h | 43 + thirdparty/liblastfm2/src/types/Album.cpp | 87 ++ thirdparty/liblastfm2/src/types/Album.h | 73 ++ thirdparty/liblastfm2/src/types/Artist.cpp | 202 ++++ thirdparty/liblastfm2/src/types/Artist.h | 100 ++ .../liblastfm2/src/types/FingerprintId.cpp | 55 + .../liblastfm2/src/types/FingerprintId.h | 62 ++ thirdparty/liblastfm2/src/types/Mbid.cpp | 36 + thirdparty/liblastfm2/src/types/Mbid.h | 45 + thirdparty/liblastfm2/src/types/Playlist.cpp | 63 ++ thirdparty/liblastfm2/src/types/Playlist.h | 53 + thirdparty/liblastfm2/src/types/Tag.cpp | 77 ++ thirdparty/liblastfm2/src/types/Tag.h | 60 ++ thirdparty/liblastfm2/src/types/Track.cpp | 481 +++++++++ thirdparty/liblastfm2/src/types/Track.h | 323 ++++++ thirdparty/liblastfm2/src/types/User.cpp | 286 ++++++ thirdparty/liblastfm2/src/types/User.h | 181 ++++ thirdparty/liblastfm2/src/types/Xspf.cpp | 49 + thirdparty/liblastfm2/src/types/Xspf.h | 43 + thirdparty/liblastfm2/src/types/mbid_mp3.c | 181 ++++ .../src/ws/InternetConnectionMonitor.cpp | 113 +++ .../src/ws/InternetConnectionMonitor.h | 80 ++ .../src/ws/NetworkAccessManager.cpp | 159 +++ .../liblastfm2/src/ws/NetworkAccessManager.h | 66 ++ .../src/ws/NetworkConnectionMonitor.cpp | 51 + .../src/ws/NetworkConnectionMonitor.h | 46 + .../src/ws/linux/LNetworkConnectionMonitor.h | 54 + .../linux/LNetworkConnectionMonitor_linux.cpp | 86 ++ .../src/ws/mac/MNetworkConnectionMonitor.h | 52 + .../ws/mac/MNetworkConnectionMonitor_mac.cpp | 71 ++ thirdparty/liblastfm2/src/ws/mac/ProxyDict.h | 75 ++ thirdparty/liblastfm2/src/ws/win/ComSetup.h | 63 ++ thirdparty/liblastfm2/src/ws/win/IeSettings.h | 43 + .../liblastfm2/src/ws/win/NdisEvents.cpp | 87 ++ thirdparty/liblastfm2/src/ws/win/NdisEvents.h | 44 + thirdparty/liblastfm2/src/ws/win/Pac.cpp | 128 +++ thirdparty/liblastfm2/src/ws/win/Pac.h | 52 + .../src/ws/win/WNetworkConnectionMonitor.h | 44 + .../ws/win/WNetworkConnectionMonitor_win.cpp | 67 ++ thirdparty/liblastfm2/src/ws/win/WmiSink.cpp | 202 ++++ thirdparty/liblastfm2/src/ws/win/WmiSink.h | 49 + thirdparty/liblastfm2/src/ws/ws.cpp | 268 +++++ thirdparty/liblastfm2/src/ws/ws.h | 161 +++ thirdparty/liblastfm2/tests/TestTrack.h | 35 + thirdparty/liblastfm2/tests/TestUrlBuilder.h | 80 ++ thirdparty/liblastfm2/tests/main.cpp | 22 + thirdparty/liblastfm2/tests/tests.pro | 4 + 114 files changed, 14711 insertions(+), 3 deletions(-) create mode 100644 thirdparty/liblastfm2/CMakeLists.txt create mode 100644 thirdparty/liblastfm2/COPYING create mode 100644 thirdparty/liblastfm2/README create mode 100755 thirdparty/liblastfm2/admin/lastfm.h.rb create mode 100644 thirdparty/liblastfm2/admin/platform.rb create mode 100755 thirdparty/liblastfm2/admin/qpp create mode 100644 thirdparty/liblastfm2/admin/utils.rb create mode 100644 thirdparty/liblastfm2/admin/which_qmake.rb create mode 100644 thirdparty/liblastfm2/cmake/modules/FindLibFFTW3.cmake create mode 100644 thirdparty/liblastfm2/cmake/modules/FindLibSamplerate.cmake create mode 100755 thirdparty/liblastfm2/configure create mode 100644 thirdparty/liblastfm2/demos/demo1.cpp create mode 100644 thirdparty/liblastfm2/demos/demo2.cpp create mode 100644 thirdparty/liblastfm2/demos/demo3.cpp create mode 100644 thirdparty/liblastfm2/demos/demos.pro create mode 100644 thirdparty/liblastfm2/src/CMakeLists.txt create mode 100644 thirdparty/liblastfm2/src/core/README create mode 100644 thirdparty/liblastfm2/src/core/UrlBuilder.cpp create mode 100644 thirdparty/liblastfm2/src/core/UrlBuilder.h create mode 100644 thirdparty/liblastfm2/src/core/XmlQuery.cpp create mode 100644 thirdparty/liblastfm2/src/core/XmlQuery.h create mode 100644 thirdparty/liblastfm2/src/core/misc.cpp create mode 100644 thirdparty/liblastfm2/src/core/misc.h create mode 100644 thirdparty/liblastfm2/src/fingerprint/CMakeLists.txt create mode 100644 thirdparty/liblastfm2/src/fingerprint/Collection.cpp create mode 100644 thirdparty/liblastfm2/src/fingerprint/Collection.h create mode 100644 thirdparty/liblastfm2/src/fingerprint/EXAMPLE.cpp create mode 100644 thirdparty/liblastfm2/src/fingerprint/Fingerprint.cpp create mode 100644 thirdparty/liblastfm2/src/fingerprint/Fingerprint.h create mode 100644 thirdparty/liblastfm2/src/fingerprint/FingerprintableSource.h create mode 100644 thirdparty/liblastfm2/src/fingerprint/Sha256.cpp create mode 100644 thirdparty/liblastfm2/src/fingerprint/Sha256.h create mode 100644 thirdparty/liblastfm2/src/fingerprint/contrib/AacSource.cpp create mode 100644 thirdparty/liblastfm2/src/fingerprint/contrib/AacSource.h create mode 100644 thirdparty/liblastfm2/src/fingerprint/contrib/AacSource_p.h create mode 100644 thirdparty/liblastfm2/src/fingerprint/contrib/FlacSource.cpp create mode 100644 thirdparty/liblastfm2/src/fingerprint/contrib/FlacSource.h create mode 100644 thirdparty/liblastfm2/src/fingerprint/contrib/MadSource.cpp create mode 100644 thirdparty/liblastfm2/src/fingerprint/contrib/MadSource.h create mode 100644 thirdparty/liblastfm2/src/fingerprint/contrib/VorbisSource.cpp create mode 100644 thirdparty/liblastfm2/src/fingerprint/contrib/VorbisSource.h create mode 100644 thirdparty/liblastfm2/src/fingerprint/contrib/lastfm-fingerprint.pro create mode 100644 thirdparty/liblastfm2/src/fingerprint/contrib/main.cpp create mode 100644 thirdparty/liblastfm2/src/fingerprint/fingerprint.pro create mode 100644 thirdparty/liblastfm2/src/fingerprint/fplib/CircularArray.h create mode 100644 thirdparty/liblastfm2/src/fingerprint/fplib/Filter.cpp create mode 100644 thirdparty/liblastfm2/src/fingerprint/fplib/Filter.h create mode 100644 thirdparty/liblastfm2/src/fingerprint/fplib/FingerprintExtractor.cpp create mode 100644 thirdparty/liblastfm2/src/fingerprint/fplib/FingerprintExtractor.h create mode 100644 thirdparty/liblastfm2/src/fingerprint/fplib/FloatingAverage.h create mode 100644 thirdparty/liblastfm2/src/fingerprint/fplib/OptFFT.cpp create mode 100644 thirdparty/liblastfm2/src/fingerprint/fplib/OptFFT.h create mode 100644 thirdparty/liblastfm2/src/fingerprint/fplib/fp_helper_fun.h create mode 100644 thirdparty/liblastfm2/src/global.h create mode 100644 thirdparty/liblastfm2/src/lastfm.pro create mode 100755 thirdparty/liblastfm2/src/radio/RadioStation.cpp create mode 100644 thirdparty/liblastfm2/src/radio/RadioStation.h create mode 100644 thirdparty/liblastfm2/src/radio/RadioTuner.cpp create mode 100644 thirdparty/liblastfm2/src/radio/RadioTuner.h create mode 100644 thirdparty/liblastfm2/src/scrobble/Audioscrobbler.cpp create mode 100644 thirdparty/liblastfm2/src/scrobble/Audioscrobbler.h create mode 100644 thirdparty/liblastfm2/src/scrobble/ScrobbleCache.cpp create mode 100644 thirdparty/liblastfm2/src/scrobble/ScrobbleCache.h create mode 100644 thirdparty/liblastfm2/src/scrobble/ScrobblePoint.h create mode 100755 thirdparty/liblastfm2/src/types/AbstractType.h create mode 100644 thirdparty/liblastfm2/src/types/Album.cpp create mode 100644 thirdparty/liblastfm2/src/types/Album.h create mode 100644 thirdparty/liblastfm2/src/types/Artist.cpp create mode 100644 thirdparty/liblastfm2/src/types/Artist.h create mode 100644 thirdparty/liblastfm2/src/types/FingerprintId.cpp create mode 100644 thirdparty/liblastfm2/src/types/FingerprintId.h create mode 100644 thirdparty/liblastfm2/src/types/Mbid.cpp create mode 100644 thirdparty/liblastfm2/src/types/Mbid.h create mode 100644 thirdparty/liblastfm2/src/types/Playlist.cpp create mode 100644 thirdparty/liblastfm2/src/types/Playlist.h create mode 100644 thirdparty/liblastfm2/src/types/Tag.cpp create mode 100644 thirdparty/liblastfm2/src/types/Tag.h create mode 100644 thirdparty/liblastfm2/src/types/Track.cpp create mode 100644 thirdparty/liblastfm2/src/types/Track.h create mode 100644 thirdparty/liblastfm2/src/types/User.cpp create mode 100644 thirdparty/liblastfm2/src/types/User.h create mode 100644 thirdparty/liblastfm2/src/types/Xspf.cpp create mode 100644 thirdparty/liblastfm2/src/types/Xspf.h create mode 100644 thirdparty/liblastfm2/src/types/mbid_mp3.c create mode 100644 thirdparty/liblastfm2/src/ws/InternetConnectionMonitor.cpp create mode 100644 thirdparty/liblastfm2/src/ws/InternetConnectionMonitor.h create mode 100644 thirdparty/liblastfm2/src/ws/NetworkAccessManager.cpp create mode 100644 thirdparty/liblastfm2/src/ws/NetworkAccessManager.h create mode 100644 thirdparty/liblastfm2/src/ws/NetworkConnectionMonitor.cpp create mode 100644 thirdparty/liblastfm2/src/ws/NetworkConnectionMonitor.h create mode 100644 thirdparty/liblastfm2/src/ws/linux/LNetworkConnectionMonitor.h create mode 100644 thirdparty/liblastfm2/src/ws/linux/LNetworkConnectionMonitor_linux.cpp create mode 100644 thirdparty/liblastfm2/src/ws/mac/MNetworkConnectionMonitor.h create mode 100644 thirdparty/liblastfm2/src/ws/mac/MNetworkConnectionMonitor_mac.cpp create mode 100644 thirdparty/liblastfm2/src/ws/mac/ProxyDict.h create mode 100644 thirdparty/liblastfm2/src/ws/win/ComSetup.h create mode 100644 thirdparty/liblastfm2/src/ws/win/IeSettings.h create mode 100644 thirdparty/liblastfm2/src/ws/win/NdisEvents.cpp create mode 100644 thirdparty/liblastfm2/src/ws/win/NdisEvents.h create mode 100644 thirdparty/liblastfm2/src/ws/win/Pac.cpp create mode 100644 thirdparty/liblastfm2/src/ws/win/Pac.h create mode 100755 thirdparty/liblastfm2/src/ws/win/WNetworkConnectionMonitor.h create mode 100755 thirdparty/liblastfm2/src/ws/win/WNetworkConnectionMonitor_win.cpp create mode 100644 thirdparty/liblastfm2/src/ws/win/WmiSink.cpp create mode 100644 thirdparty/liblastfm2/src/ws/win/WmiSink.h create mode 100644 thirdparty/liblastfm2/src/ws/ws.cpp create mode 100644 thirdparty/liblastfm2/src/ws/ws.h create mode 100644 thirdparty/liblastfm2/tests/TestTrack.h create mode 100644 thirdparty/liblastfm2/tests/TestUrlBuilder.h create mode 100644 thirdparty/liblastfm2/tests/main.cpp create mode 100644 thirdparty/liblastfm2/tests/tests.pro diff --git a/CMakeLists.txt b/CMakeLists.txt index 6552d24e2..14ac26dd4 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -29,8 +29,10 @@ INCLUDE( MacroOptionalFindPackage ) INCLUDE( MacroLogFeature ) # required -macro_optional_find_package(LibLastFm 0.3.3) -macro_log_feature(LIBLASTFM_FOUND "LastFm" "Qt library for the Last.fm webservices" "https://github.com/mxcl/liblastfm" FALSE "" "liblastfm is needed for scrobbling tracks to Last.fm and fetching cover artwork") +#While we distribute our own liblastfm2, don't need to look for it +#macro_optional_find_package(LibLastFm 0.3.3) +#macro_log_feature(LIBLASTFM_FOUND "LastFm" "Qt library for the Last.fm webservices" "https://github.com/mxcl/liblastfm" FALSE "" "liblastfm is needed for scrobbling tracks to Last.fm and fetching cover artwork") +set(LIBLASTFM_FOUND true) macro_optional_find_package(LibEchonest 1.1.1) macro_log_feature(LIBECHONEST_FOUND "Echonest" "Qt library for communicating with The Echo Nest" "http://projects.kde.org/libechonest" TRUE "" "libechonest is needed for dynamic playlists and the infosystem") diff --git a/src/CMakeLists.txt b/src/CMakeLists.txt index babe44b77..d3d50b2cc 100644 --- a/src/CMakeLists.txt +++ b/src/CMakeLists.txt @@ -123,6 +123,7 @@ INCLUDE_DIRECTORIES( . ${TOMAHAWK_INC_DIR} ${CMAKE_CURRENT_BINARY_DIR} + ${CMAKE_BINARY_DIR}/thirdparty/liblastfm2/src audio database @@ -205,7 +206,7 @@ MESSAGE( STATUS "OS_SPECIFIC_LINK_LIBRARIES: ${OS_SPECIFIC_LINK_LIBRARIES}" ) SET(LINK_LIBRARIES "") IF(LIBLASTFM_FOUND) - SET(LINK_LIBRARIES ${LINK_LIBRARIES} ${LIBLASTFM_LIBRARY} ) + SET(LINK_LIBRARIES ${LINK_LIBRARIES} tomahawk_lastfm2 ) ENDIF(LIBLASTFM_FOUND) IF(GLOOX_FOUND) SET(LINK_LIBRARIES ${LINK_LIBRARIES} ${GLOOX_LIBRARIES} ) diff --git a/thirdparty/CMakeLists.txt b/thirdparty/CMakeLists.txt index 2d2836537..f28fdff43 100644 --- a/thirdparty/CMakeLists.txt +++ b/thirdparty/CMakeLists.txt @@ -2,6 +2,7 @@ add_subdirectory( jdns ) add_subdirectory( qtweetlib ) ADD_SUBDIRECTORY( libportfwd ) ADD_SUBDIRECTORY( qxt ) +ADD_SUBDIRECTORY( liblastfm2 ) IF( UNIX AND NOT APPLE ) ADD_SUBDIRECTORY( alsa-playback ) diff --git a/thirdparty/liblastfm2/CMakeLists.txt b/thirdparty/liblastfm2/CMakeLists.txt new file mode 100644 index 000000000..e8b020b78 --- /dev/null +++ b/thirdparty/liblastfm2/CMakeLists.txt @@ -0,0 +1,27 @@ +cmake_minimum_required(VERSION 2.6) + +set( CMAKE_MODULE_PATH + ${CMAKE_MODULE_PATH} + ${CMAKE_CURRENT_SOURCE_DIR}/cmake/modules + ) + +if(${CMAKE_BUILD_TYPE} MATCHES "Release") + add_definitions(-DNDEBUG) +endif(${CMAKE_BUILD_TYPE} MATCHES "Release") + +# Set up definitions and paths +add_definitions(${QT_DEFINITIONS}) +include(${QT_USE_FILE}) + +# Main Library +add_subdirectory(src) + +# Optionally build the fingerprint library +option(BUILD_FINGERPRINT "Build the lastfm-fingerprint library" ON) +find_package(LibSamplerate) +find_package(LibFFTW3) + +if (BUILD_FINGERPRINT AND LIBFFTW3_FOUND AND LIBSAMPLERATE_FOUND) + add_subdirectory(src/fingerprint) +endif (BUILD_FINGERPRINT AND LIBFFTW3_FOUND AND LIBSAMPLERATE_FOUND) + diff --git a/thirdparty/liblastfm2/COPYING b/thirdparty/liblastfm2/COPYING new file mode 100644 index 000000000..94a045322 --- /dev/null +++ b/thirdparty/liblastfm2/COPYING @@ -0,0 +1,621 @@ + GNU GENERAL PUBLIC LICENSE + Version 3, 29 June 2007 + + Copyright (C) 2007 Free Software Foundation, Inc. + Everyone is permitted to copy and distribute verbatim copies + of this license document, but changing it is not allowed. + + Preamble + + The GNU General Public License is a free, copyleft license for +software and other kinds of works. + + The licenses for most software and other practical works are designed +to take away your freedom to share and change the works. By contrast, +the GNU General Public License is intended to guarantee your freedom to +share and change all versions of a program--to make sure it remains free +software for all its users. We, the Free Software Foundation, use the +GNU General Public License for most of our software; it applies also to +any other work released this way by its authors. You can apply it to +your programs, too. + + When we speak of free software, we are referring to freedom, not +price. Our General Public Licenses are designed to make sure that you +have the freedom to distribute copies of free software (and charge for +them if you wish), that you receive source code or can get it if you +want it, that you can change the software or use pieces of it in new +free programs, and that you know you can do these things. + + To protect your rights, we need to prevent others from denying you +these rights or asking you to surrender the rights. Therefore, you have +certain responsibilities if you distribute copies of the software, or if +you modify it: responsibilities to respect the freedom of others. + + For example, if you distribute copies of such a program, whether +gratis or for a fee, you must pass on to the recipients the same +freedoms that you received. You must make sure that they, too, receive +or can get the source code. And you must show them these terms so they +know their rights. + + Developers that use the GNU GPL protect your rights with two steps: +(1) assert copyright on the software, and (2) offer you this License +giving you legal permission to copy, distribute and/or modify it. + + For the developers' and authors' protection, the GPL clearly explains +that there is no warranty for this free software. For both users' and +authors' sake, the GPL requires that modified versions be marked as +changed, so that their problems will not be attributed erroneously to +authors of previous versions. + + Some devices are designed to deny users access to install or run +modified versions of the software inside them, although the manufacturer +can do so. This is fundamentally incompatible with the aim of +protecting users' freedom to change the software. The systematic +pattern of such abuse occurs in the area of products for individuals to +use, which is precisely where it is most unacceptable. Therefore, we +have designed this version of the GPL to prohibit the practice for those +products. If such problems arise substantially in other domains, we +stand ready to extend this provision to those domains in future versions +of the GPL, as needed to protect the freedom of users. + + Finally, every program is threatened constantly by software patents. +States should not allow patents to restrict development and use of +software on general-purpose computers, but in those that do, we wish to +avoid the special danger that patents applied to a free program could +make it effectively proprietary. To prevent this, the GPL assures that +patents cannot be used to render the program non-free. + + The precise terms and conditions for copying, distribution and +modification follow. + + TERMS AND CONDITIONS + + 0. Definitions. + + "This License" refers to version 3 of the GNU General Public License. + + "Copyright" also means copyright-like laws that apply to other kinds of +works, such as semiconductor masks. + + "The Program" refers to any copyrightable work licensed under this +License. Each licensee is addressed as "you". "Licensees" and +"recipients" may be individuals or organizations. + + To "modify" a work means to copy from or adapt all or part of the work +in a fashion requiring copyright permission, other than the making of an +exact copy. The resulting work is called a "modified version" of the +earlier work or a work "based on" the earlier work. + + A "covered work" means either the unmodified Program or a work based +on the Program. + + To "propagate" a work means to do anything with it that, without +permission, would make you directly or secondarily liable for +infringement under applicable copyright law, except executing it on a +computer or modifying a private copy. Propagation includes copying, +distribution (with or without modification), making available to the +public, and in some countries other activities as well. + + To "convey" a work means any kind of propagation that enables other +parties to make or receive copies. Mere interaction with a user through +a computer network, with no transfer of a copy, is not conveying. + + An interactive user interface displays "Appropriate Legal Notices" +to the extent that it includes a convenient and prominently visible +feature that (1) displays an appropriate copyright notice, and (2) +tells the user that there is no warranty for the work (except to the +extent that warranties are provided), that licensees may convey the +work under this License, and how to view a copy of this License. If +the interface presents a list of user commands or options, such as a +menu, a prominent item in the list meets this criterion. + + 1. Source Code. + + The "source code" for a work means the preferred form of the work +for making modifications to it. "Object code" means any non-source +form of a work. + + A "Standard Interface" means an interface that either is an official +standard defined by a recognized standards body, or, in the case of +interfaces specified for a particular programming language, one that +is widely used among developers working in that language. + + The "System Libraries" of an executable work include anything, other +than the work as a whole, that (a) is included in the normal form of +packaging a Major Component, but which is not part of that Major +Component, and (b) serves only to enable use of the work with that +Major Component, or to implement a Standard Interface for which an +implementation is available to the public in source code form. A +"Major Component", in this context, means a major essential component +(kernel, window system, and so on) of the specific operating system +(if any) on which the executable work runs, or a compiler used to +produce the work, or an object code interpreter used to run it. + + The "Corresponding Source" for a work in object code form means all +the source code needed to generate, install, and (for an executable +work) run the object code and to modify the work, including scripts to +control those activities. However, it does not include the work's +System Libraries, or general-purpose tools or generally available free +programs which are used unmodified in performing those activities but +which are not part of the work. For example, Corresponding Source +includes interface definition files associated with source files for +the work, and the source code for shared libraries and dynamically +linked subprograms that the work is specifically designed to require, +such as by intimate data communication or control flow between those +subprograms and other parts of the work. + + The Corresponding Source need not include anything that users +can regenerate automatically from other parts of the Corresponding +Source. + + The Corresponding Source for a work in source code form is that +same work. + + 2. Basic Permissions. + + All rights granted under this License are granted for the term of +copyright on the Program, and are irrevocable provided the stated +conditions are met. This License explicitly affirms your unlimited +permission to run the unmodified Program. The output from running a +covered work is covered by this License only if the output, given its +content, constitutes a covered work. This License acknowledges your +rights of fair use or other equivalent, as provided by copyright law. + + You may make, run and propagate covered works that you do not +convey, without conditions so long as your license otherwise remains +in force. You may convey covered works to others for the sole purpose +of having them make modifications exclusively for you, or provide you +with facilities for running those works, provided that you comply with +the terms of this License in conveying all material for which you do +not control copyright. Those thus making or running the covered works +for you must do so exclusively on your behalf, under your direction +and control, on terms that prohibit them from making any copies of +your copyrighted material outside their relationship with you. + + Conveying under any other circumstances is permitted solely under +the conditions stated below. Sublicensing is not allowed; section 10 +makes it unnecessary. + + 3. Protecting Users' Legal Rights From Anti-Circumvention Law. + + No covered work shall be deemed part of an effective technological +measure under any applicable law fulfilling obligations under article +11 of the WIPO copyright treaty adopted on 20 December 1996, or +similar laws prohibiting or restricting circumvention of such +measures. + + When you convey a covered work, you waive any legal power to forbid +circumvention of technological measures to the extent such circumvention +is effected by exercising rights under this License with respect to +the covered work, and you disclaim any intention to limit operation or +modification of the work as a means of enforcing, against the work's +users, your or third parties' legal rights to forbid circumvention of +technological measures. + + 4. Conveying Verbatim Copies. + + You may convey verbatim copies of the Program's source code as you +receive it, in any medium, provided that you conspicuously and +appropriately publish on each copy an appropriate copyright notice; +keep intact all notices stating that this License and any +non-permissive terms added in accord with section 7 apply to the code; +keep intact all notices of the absence of any warranty; and give all +recipients a copy of this License along with the Program. + + You may charge any price or no price for each copy that you convey, +and you may offer support or warranty protection for a fee. + + 5. Conveying Modified Source Versions. + + You may convey a work based on the Program, or the modifications to +produce it from the Program, in the form of source code under the +terms of section 4, provided that you also meet all of these conditions: + + a) The work must carry prominent notices stating that you modified + it, and giving a relevant date. + + b) The work must carry prominent notices stating that it is + released under this License and any conditions added under section + 7. This requirement modifies the requirement in section 4 to + "keep intact all notices". + + c) You must license the entire work, as a whole, under this + License to anyone who comes into possession of a copy. This + License will therefore apply, along with any applicable section 7 + additional terms, to the whole of the work, and all its parts, + regardless of how they are packaged. This License gives no + permission to license the work in any other way, but it does not + invalidate such permission if you have separately received it. + + d) If the work has interactive user interfaces, each must display + Appropriate Legal Notices; however, if the Program has interactive + interfaces that do not display Appropriate Legal Notices, your + work need not make them do so. + + A compilation of a covered work with other separate and independent +works, which are not by their nature extensions of the covered work, +and which are not combined with it such as to form a larger program, +in or on a volume of a storage or distribution medium, is called an +"aggregate" if the compilation and its resulting copyright are not +used to limit the access or legal rights of the compilation's users +beyond what the individual works permit. Inclusion of a covered work +in an aggregate does not cause this License to apply to the other +parts of the aggregate. + + 6. Conveying Non-Source Forms. + + You may convey a covered work in object code form under the terms +of sections 4 and 5, provided that you also convey the +machine-readable Corresponding Source under the terms of this License, +in one of these ways: + + a) Convey the object code in, or embodied in, a physical product + (including a physical distribution medium), accompanied by the + Corresponding Source fixed on a durable physical medium + customarily used for software interchange. + + b) Convey the object code in, or embodied in, a physical product + (including a physical distribution medium), accompanied by a + written offer, valid for at least three years and valid for as + long as you offer spare parts or customer support for that product + model, to give anyone who possesses the object code either (1) a + copy of the Corresponding Source for all the software in the + product that is covered by this License, on a durable physical + medium customarily used for software interchange, for a price no + more than your reasonable cost of physically performing this + conveying of source, or (2) access to copy the + Corresponding Source from a network server at no charge. + + c) Convey individual copies of the object code with a copy of the + written offer to provide the Corresponding Source. This + alternative is allowed only occasionally and noncommercially, and + only if you received the object code with such an offer, in accord + with subsection 6b. + + d) Convey the object code by offering access from a designated + place (gratis or for a charge), and offer equivalent access to the + Corresponding Source in the same way through the same place at no + further charge. You need not require recipients to copy the + Corresponding Source along with the object code. If the place to + copy the object code is a network server, the Corresponding Source + may be on a different server (operated by you or a third party) + that supports equivalent copying facilities, provided you maintain + clear directions next to the object code saying where to find the + Corresponding Source. Regardless of what server hosts the + Corresponding Source, you remain obligated to ensure that it is + available for as long as needed to satisfy these requirements. + + e) Convey the object code using peer-to-peer transmission, provided + you inform other peers where the object code and Corresponding + Source of the work are being offered to the general public at no + charge under subsection 6d. + + A separable portion of the object code, whose source code is excluded +from the Corresponding Source as a System Library, need not be +included in conveying the object code work. + + A "User Product" is either (1) a "consumer product", which means any +tangible personal property which is normally used for personal, family, +or household purposes, or (2) anything designed or sold for incorporation +into a dwelling. In determining whether a product is a consumer product, +doubtful cases shall be resolved in favor of coverage. For a particular +product received by a particular user, "normally used" refers to a +typical or common use of that class of product, regardless of the status +of the particular user or of the way in which the particular user +actually uses, or expects or is expected to use, the product. A product +is a consumer product regardless of whether the product has substantial +commercial, industrial or non-consumer uses, unless such uses represent +the only significant mode of use of the product. + + "Installation Information" for a User Product means any methods, +procedures, authorization keys, or other information required to install +and execute modified versions of a covered work in that User Product from +a modified version of its Corresponding Source. The information must +suffice to ensure that the continued functioning of the modified object +code is in no case prevented or interfered with solely because +modification has been made. + + If you convey an object code work under this section in, or with, or +specifically for use in, a User Product, and the conveying occurs as +part of a transaction in which the right of possession and use of the +User Product is transferred to the recipient in perpetuity or for a +fixed term (regardless of how the transaction is characterized), the +Corresponding Source conveyed under this section must be accompanied +by the Installation Information. But this requirement does not apply +if neither you nor any third party retains the ability to install +modified object code on the User Product (for example, the work has +been installed in ROM). + + The requirement to provide Installation Information does not include a +requirement to continue to provide support service, warranty, or updates +for a work that has been modified or installed by the recipient, or for +the User Product in which it has been modified or installed. Access to a +network may be denied when the modification itself materially and +adversely affects the operation of the network or violates the rules and +protocols for communication across the network. + + Corresponding Source conveyed, and Installation Information provided, +in accord with this section must be in a format that is publicly +documented (and with an implementation available to the public in +source code form), and must require no special password or key for +unpacking, reading or copying. + + 7. Additional Terms. + + "Additional permissions" are terms that supplement the terms of this +License by making exceptions from one or more of its conditions. +Additional permissions that are applicable to the entire Program shall +be treated as though they were included in this License, to the extent +that they are valid under applicable law. If additional permissions +apply only to part of the Program, that part may be used separately +under those permissions, but the entire Program remains governed by +this License without regard to the additional permissions. + + When you convey a copy of a covered work, you may at your option +remove any additional permissions from that copy, or from any part of +it. (Additional permissions may be written to require their own +removal in certain cases when you modify the work.) You may place +additional permissions on material, added by you to a covered work, +for which you have or can give appropriate copyright permission. + + Notwithstanding any other provision of this License, for material you +add to a covered work, you may (if authorized by the copyright holders of +that material) supplement the terms of this License with terms: + + a) Disclaiming warranty or limiting liability differently from the + terms of sections 15 and 16 of this License; or + + b) Requiring preservation of specified reasonable legal notices or + author attributions in that material or in the Appropriate Legal + Notices displayed by works containing it; or + + c) Prohibiting misrepresentation of the origin of that material, or + requiring that modified versions of such material be marked in + reasonable ways as different from the original version; or + + d) Limiting the use for publicity purposes of names of licensors or + authors of the material; or + + e) Declining to grant rights under trademark law for use of some + trade names, trademarks, or service marks; or + + f) Requiring indemnification of licensors and authors of that + material by anyone who conveys the material (or modified versions of + it) with contractual assumptions of liability to the recipient, for + any liability that these contractual assumptions directly impose on + those licensors and authors. + + All other non-permissive additional terms are considered "further +restrictions" within the meaning of section 10. If the Program as you +received it, or any part of it, contains a notice stating that it is +governed by this License along with a term that is a further +restriction, you may remove that term. If a license document contains +a further restriction but permits relicensing or conveying under this +License, you may add to a covered work material governed by the terms +of that license document, provided that the further restriction does +not survive such relicensing or conveying. + + If you add terms to a covered work in accord with this section, you +must place, in the relevant source files, a statement of the +additional terms that apply to those files, or a notice indicating +where to find the applicable terms. + + Additional terms, permissive or non-permissive, may be stated in the +form of a separately written license, or stated as exceptions; +the above requirements apply either way. + + 8. Termination. + + You may not propagate or modify a covered work except as expressly +provided under this License. Any attempt otherwise to propagate or +modify it is void, and will automatically terminate your rights under +this License (including any patent licenses granted under the third +paragraph of section 11). + + However, if you cease all violation of this License, then your +license from a particular copyright holder is reinstated (a) +provisionally, unless and until the copyright holder explicitly and +finally terminates your license, and (b) permanently, if the copyright +holder fails to notify you of the violation by some reasonable means +prior to 60 days after the cessation. + + Moreover, your license from a particular copyright holder is +reinstated permanently if the copyright holder notifies you of the +violation by some reasonable means, this is the first time you have +received notice of violation of this License (for any work) from that +copyright holder, and you cure the violation prior to 30 days after +your receipt of the notice. + + Termination of your rights under this section does not terminate the +licenses of parties who have received copies or rights from you under +this License. If your rights have been terminated and not permanently +reinstated, you do not qualify to receive new licenses for the same +material under section 10. + + 9. Acceptance Not Required for Having Copies. + + You are not required to accept this License in order to receive or +run a copy of the Program. Ancillary propagation of a covered work +occurring solely as a consequence of using peer-to-peer transmission +to receive a copy likewise does not require acceptance. However, +nothing other than this License grants you permission to propagate or +modify any covered work. These actions infringe copyright if you do +not accept this License. Therefore, by modifying or propagating a +covered work, you indicate your acceptance of this License to do so. + + 10. Automatic Licensing of Downstream Recipients. + + Each time you convey a covered work, the recipient automatically +receives a license from the original licensors, to run, modify and +propagate that work, subject to this License. You are not responsible +for enforcing compliance by third parties with this License. + + An "entity transaction" is a transaction transferring control of an +organization, or substantially all assets of one, or subdividing an +organization, or merging organizations. If propagation of a covered +work results from an entity transaction, each party to that +transaction who receives a copy of the work also receives whatever +licenses to the work the party's predecessor in interest had or could +give under the previous paragraph, plus a right to possession of the +Corresponding Source of the work from the predecessor in interest, if +the predecessor has it or can get it with reasonable efforts. + + You may not impose any further restrictions on the exercise of the +rights granted or affirmed under this License. For example, you may +not impose a license fee, royalty, or other charge for exercise of +rights granted under this License, and you may not initiate litigation +(including a cross-claim or counterclaim in a lawsuit) alleging that +any patent claim is infringed by making, using, selling, offering for +sale, or importing the Program or any portion of it. + + 11. Patents. + + A "contributor" is a copyright holder who authorizes use under this +License of the Program or a work on which the Program is based. The +work thus licensed is called the contributor's "contributor version". + + A contributor's "essential patent claims" are all patent claims +owned or controlled by the contributor, whether already acquired or +hereafter acquired, that would be infringed by some manner, permitted +by this License, of making, using, or selling its contributor version, +but do not include claims that would be infringed only as a +consequence of further modification of the contributor version. For +purposes of this definition, "control" includes the right to grant +patent sublicenses in a manner consistent with the requirements of +this License. + + Each contributor grants you a non-exclusive, worldwide, royalty-free +patent license under the contributor's essential patent claims, to +make, use, sell, offer for sale, import and otherwise run, modify and +propagate the contents of its contributor version. + + In the following three paragraphs, a "patent license" is any express +agreement or commitment, however denominated, not to enforce a patent +(such as an express permission to practice a patent or covenant not to +sue for patent infringement). To "grant" such a patent license to a +party means to make such an agreement or commitment not to enforce a +patent against the party. + + If you convey a covered work, knowingly relying on a patent license, +and the Corresponding Source of the work is not available for anyone +to copy, free of charge and under the terms of this License, through a +publicly available network server or other readily accessible means, +then you must either (1) cause the Corresponding Source to be so +available, or (2) arrange to deprive yourself of the benefit of the +patent license for this particular work, or (3) arrange, in a manner +consistent with the requirements of this License, to extend the patent +license to downstream recipients. "Knowingly relying" means you have +actual knowledge that, but for the patent license, your conveying the +covered work in a country, or your recipient's use of the covered work +in a country, would infringe one or more identifiable patents in that +country that you have reason to believe are valid. + + If, pursuant to or in connection with a single transaction or +arrangement, you convey, or propagate by procuring conveyance of, a +covered work, and grant a patent license to some of the parties +receiving the covered work authorizing them to use, propagate, modify +or convey a specific copy of the covered work, then the patent license +you grant is automatically extended to all recipients of the covered +work and works based on it. + + A patent license is "discriminatory" if it does not include within +the scope of its coverage, prohibits the exercise of, or is +conditioned on the non-exercise of one or more of the rights that are +specifically granted under this License. You may not convey a covered +work if you are a party to an arrangement with a third party that is +in the business of distributing software, under which you make payment +to the third party based on the extent of your activity of conveying +the work, and under which the third party grants, to any of the +parties who would receive the covered work from you, a discriminatory +patent license (a) in connection with copies of the covered work +conveyed by you (or copies made from those copies), or (b) primarily +for and in connection with specific products or compilations that +contain the covered work, unless you entered into that arrangement, +or that patent license was granted, prior to 28 March 2007. + + Nothing in this License shall be construed as excluding or limiting +any implied license or other defenses to infringement that may +otherwise be available to you under applicable patent law. + + 12. No Surrender of Others' Freedom. + + If conditions are imposed on you (whether by court order, agreement or +otherwise) that contradict the conditions of this License, they do not +excuse you from the conditions of this License. If you cannot convey a +covered work so as to satisfy simultaneously your obligations under this +License and any other pertinent obligations, then as a consequence you may +not convey it at all. For example, if you agree to terms that obligate you +to collect a royalty for further conveying from those to whom you convey +the Program, the only way you could satisfy both those terms and this +License would be to refrain entirely from conveying the Program. + + 13. Use with the GNU Affero General Public License. + + Notwithstanding any other provision of this License, you have +permission to link or combine any covered work with a work licensed +under version 3 of the GNU Affero General Public License into a single +combined work, and to convey the resulting work. The terms of this +License will continue to apply to the part which is the covered work, +but the special requirements of the GNU Affero General Public License, +section 13, concerning interaction through a network will apply to the +combination as such. + + 14. Revised Versions of this License. + + The Free Software Foundation may publish revised and/or new versions of +the GNU General Public License from time to time. Such new versions will +be similar in spirit to the present version, but may differ in detail to +address new problems or concerns. + + Each version is given a distinguishing version number. If the +Program specifies that a certain numbered version of the GNU General +Public License "or any later version" applies to it, you have the +option of following the terms and conditions either of that numbered +version or of any later version published by the Free Software +Foundation. If the Program does not specify a version number of the +GNU General Public License, you may choose any version ever published +by the Free Software Foundation. + + If the Program specifies that a proxy can decide which future +versions of the GNU General Public License can be used, that proxy's +public statement of acceptance of a version permanently authorizes you +to choose that version for the Program. + + Later license versions may give you additional or different +permissions. However, no additional obligations are imposed on any +author or copyright holder as a result of your choosing to follow a +later version. + + 15. Disclaimer of Warranty. + + THERE IS NO WARRANTY FOR THE PROGRAM, TO THE EXTENT PERMITTED BY +APPLICABLE LAW. EXCEPT WHEN OTHERWISE STATED IN WRITING THE COPYRIGHT +HOLDERS AND/OR OTHER PARTIES PROVIDE THE PROGRAM "AS IS" WITHOUT WARRANTY +OF ANY KIND, EITHER EXPRESSED OR IMPLIED, INCLUDING, BUT NOT LIMITED TO, +THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR +PURPOSE. THE ENTIRE RISK AS TO THE QUALITY AND PERFORMANCE OF THE PROGRAM +IS WITH YOU. SHOULD THE PROGRAM PROVE DEFECTIVE, YOU ASSUME THE COST OF +ALL NECESSARY SERVICING, REPAIR OR CORRECTION. + + 16. Limitation of Liability. + + IN NO EVENT UNLESS REQUIRED BY APPLICABLE LAW OR AGREED TO IN WRITING +WILL ANY COPYRIGHT HOLDER, OR ANY OTHER PARTY WHO MODIFIES AND/OR CONVEYS +THE PROGRAM AS PERMITTED ABOVE, BE LIABLE TO YOU FOR DAMAGES, INCLUDING ANY +GENERAL, SPECIAL, INCIDENTAL OR CONSEQUENTIAL DAMAGES ARISING OUT OF THE +USE OR INABILITY TO USE THE PROGRAM (INCLUDING BUT NOT LIMITED TO LOSS OF +DATA OR DATA BEING RENDERED INACCURATE OR LOSSES SUSTAINED BY YOU OR THIRD +PARTIES OR A FAILURE OF THE PROGRAM TO OPERATE WITH ANY OTHER PROGRAMS), +EVEN IF SUCH HOLDER OR OTHER PARTY HAS BEEN ADVISED OF THE POSSIBILITY OF +SUCH DAMAGES. + + 17. Interpretation of Sections 15 and 16. + + If the disclaimer of warranty and limitation of liability provided +above cannot be given local legal effect according to their terms, +reviewing courts shall apply local law that most closely approximates +an absolute waiver of all civil liability in connection with the +Program, unless a warranty or assumption of liability accompanies a +copy of the Program in return for a fee. + + END OF TERMS AND CONDITIONS diff --git a/thirdparty/liblastfm2/README b/thirdparty/liblastfm2/README new file mode 100644 index 000000000..d39417ecc --- /dev/null +++ b/thirdparty/liblastfm2/README @@ -0,0 +1,138 @@ +liblastfm +========= +liblastfm is a collection of libraries to help you integrate Last.fm services +into your rich desktop software. It is officially supported software developed +by Last.fm staff. + +Max Howell http://twitter.com/mxcl +Jono Cole http://twitter.com/jonocole +Doug Mansell http://twitter.com/dougma + +Fork it: http://github.com/mxcl/liblastfm + + +Dependencies +============ +liblastfm dynamically links to: + +* Qt 4.4 + http://www.qtsoftware.com +* FFTW 3.2 + Compiled with single precision + http://www.fftw.org +* Secret Rabbit code (aka libsamplerate) + http://www.mega-nerd.com/SRC + +Additionally, to build you will need Ruby and GNU make (or Microsoft nmake). + +Mac OS X +-------- + sudo port selfupdate + sudo port upgrade installed + sudo port install libsamplerate fftw-3 qt4-mac-devel + +qt4-mac-devel will take a long time to build. So you may want to install the +Trolltech binary package instead. + +MacPorts carries liblastfm now, so you may have downloaded this package simply +to perform this next line: + + sudo port install liblastfm + +Linux/*NIX +---------- +Do something like this: + + sudo apt-get install qt4-qmake pkg-config libsamplerate-dev libfftw3-dev ruby g++ libqt4-dev + +Additionally on Linux the configure process requires lsb_release. This is +usually already installed (correct me if I'm wrong). + +Please note, we have only tested on Linux, but we think it'll work on all +varieties of UNIX. If it doesn't, report the bug to mxcl on GitHub. + +Windows +------- +Install Ruby. Install Visual Studio 2005 or higher. Install Qt. Install the +Windows Server 2003 Platform SDK r2: + +http://www.microsoft.com/Downloads/details.aspx?FamilyID=484269e2-3b89-47e3-8eb7-1f2be6d7123a + +Set up your environment variables so all include paths and tools are +available. + +Build and install FFTW and Secret Rabbit Code. + +Open a plain Windows shell (Cygwin will work but we don't recommend it), and +see the next section. + + +Installing liblastfm +==================== + ruby configure --release --prefix /usr/local && make && sudo make install + +Packaging liblastfm +------------------- +DESTDIR is supported. + +liblastfm builds to two dynamic libraries (liblastfm.so and +liblastfm_fingerprint.so). liblastfm.so links only to Qt, but the +fingerprinting part has additional dependencies. So ideally, you would +distribute two packages. + + +Using liblastfm +=============== +We have copied the API at http://last.fm/api onto C++, so like you find +artist.getInfo there you will find an lastfm::Artist::getInfo function in our +C++ API. lastfm is a namespace, Artist a class and getInfo a function. + +Thus the API is quite easy to learn. We suggest installing and checking the +include/lastfm/* directory to find out all capabilities. + +The demos directory shows some further basic usage including Audioscrobbling +and getting metadata for music via our fingerprinting technology. + +You need an API key from http://last.fm/api to use the webservice API. + +Your link line needs to include the following: + + -llastfm -lQtCore -lQtNetwork -lQtXml + +Radio +----- +Please set an identifiable UserAgent on your HTTP requests for the actual MP3s, +in extreme cases we'll contact you directly and demand you do so :P + +HTTP & Networking +----------------- +You can specify your own QNetworkAccessManager derived class for liblastfm to +use with lastfm::setNetworkAccessManager(). Our default is pretty good +though, auto-determining proxy settings on Windows and OS X for instance. + + +Using liblastfm_fingerprint +=========================== +The liblastfm_fingerprint library does not decode audio files. We anticipate +that Phonon will soon do that work for us. In the meantime, sample *Source +files for MP3, Ogg Vorbis, FLAC, and AAC/MP4 are available in +src/fingerprint/contrib. If you want to fingerprint files or get metadata +suggestions, you either need to add the *Source files to your project, or +implement your own. + + +Development +=========== +Public Headers +-------------- +1. Header guards should be prefixed with LASTFM, eg. LASTFM_WS_REPLY_H +2. #includes should be to the system path eg. #include +3. Don't make a header public unless it is absolutely required! +4. To make the header public edit the headers.files line in the pro file + +Private Headers +--------------- +1. For consistency and to make it more obvious it is a private header, don't + prefix the header guard with LASTFM +2. #includes should be the full source tree path, eg. + #include "../core/UrlBuilder.h" diff --git a/thirdparty/liblastfm2/admin/lastfm.h.rb b/thirdparty/liblastfm2/admin/lastfm.h.rb new file mode 100755 index 000000000..1582b7086 --- /dev/null +++ b/thirdparty/liblastfm2/admin/lastfm.h.rb @@ -0,0 +1,5 @@ +#!/usr/bin/ruby +f = File.new(ARGV[0], "w") +Dir["_include/lastfm/*"].each do |h| + f.write %Q{#include "lastfm/#{File.basename h}"\n} +end \ No newline at end of file diff --git a/thirdparty/liblastfm2/admin/platform.rb b/thirdparty/liblastfm2/admin/platform.rb new file mode 100644 index 000000000..54eda1db1 --- /dev/null +++ b/thirdparty/liblastfm2/admin/platform.rb @@ -0,0 +1,101 @@ +# +# platform.rb: naive platform detection for Ruby +# author: Matt Mower +# + +# == Platform +# +# Platform is a simple module which parses the Ruby constant +# RUBY_PLATFORM and works out the OS, it's implementation, +# and the architecture it's running on. +# +# The motivation for writing this was coming across a case where +# +# +if RUBY_PLATFORM =~ /win/+ +# +# didn't behave as expected (i.e. on powerpc-darwin-8.1.0) +# +# It is hoped that providing a library for parsing the platform +# means that we can cover all the cases and have something which +# works reliably 99% of the time. +# +# Please report any anomalies or new combinations to the author(s). +# +# == Use +# +# require "platform" +# +# defines +# +# Platform::OS (:unix,:win32,:vms,:os2) +# Platform::IMPL (:macosx,:linux,:mswin) +# Platform::ARCH (:powerpc,:x86,:alpha) +# +# if an unknown configuration is encountered any (or all) of +# these constant may have the value :unknown. +# +# To display the combination for your setup run +# +# ruby platform.rb +# +module Platform + + if RUBY_PLATFORM =~ /darwin/i + OS = :unix + IMPL = :macosx + elsif RUBY_PLATFORM =~ /linux/i + OS = :unix + IMPL = :linux + elsif RUBY_PLATFORM =~ /freebsd/i + OS = :unix + IMPL = :freebsd + elsif RUBY_PLATFORM =~ /netbsd/i + OS = :unix + IMPL = :netbsd + elsif RUBY_PLATFORM =~ /mswin/i + OS = :win32 + IMPL = :mswin + elsif RUBY_PLATFORM =~ /cygwin/i + OS = :win32 + IMPL = :mswin + elsif RUBY_PLATFORM =~ /mingw/i + OS = :win32 + IMPL = :mingw + elsif RUBY_PLATFORM =~ /bccwin/i + OS = :win32 + IMPL = :bccwin + elsif RUBY_PLATFORM =~ /wince/i + OS = :win32 + IMPL = :wince + elsif RUBY_PLATFORM =~ /vms/i + OS = :vms + IMPL = :vms + elsif RUBY_PLATFORM =~ /os2/i + OS = :os2 + IMPL = :os2 # maybe there is some better choice here? + else + OS = :unknown + IMPL = :unknown + end + + # whither AIX, SOLARIS, and the other unixen? + + if RUBY_PLATFORM =~ /(i\d86)/i + ARCH = :x86 + elsif RUBY_PLATFORM =~ /ia64/i + ARCH = :ia64 + elsif RUBY_PLATFORM =~ /powerpc/i + ARCH = :powerpc + elsif RUBY_PLATFORM =~ /alpha/i + ARCH = :alpha + else + ARCH = :unknown + end + + # What about AMD, Turion, Motorola, etc..? + +end + +if __FILE__ == $0 + puts "Platform OS=#{Platform::OS}, IMPL=#{Platform::IMPL}, ARCH=#{Platform::ARCH}" +end diff --git a/thirdparty/liblastfm2/admin/qpp b/thirdparty/liblastfm2/admin/qpp new file mode 100755 index 000000000..a99c5ce34 --- /dev/null +++ b/thirdparty/liblastfm2/admin/qpp @@ -0,0 +1,79 @@ +#!/usr/bin/ruby +# Usage examples: +# qpp foo.pro => ./_file.qmake +# qpp foo/bar/ => ./_file.qmake +# +cwd = File.dirname( __FILE__ ) +require 'find' +require 'ftools' +require "#{cwd}/platform.rb" + +def find_sources() + Find.find( '.' ) do |path| + if File.directory?( path ) + excludes = ['.svn', 'tests', '_build', 'contrib'] + case Platform::IMPL + when :macosx then excludes << 'win' << 'linux' + when :mswin, :cygwin then excludes << 'mac' << 'linux' + when :linux, :freebsd then excludes << 'win' << 'mac' + else excludes << 'win' << 'mac' << 'linux' + end + Find.prune if excludes.include?( File.basename( path ) ) or (path != "." and not Dir["#{path}/*.pro"].empty? ) + elsif File.file?( path ) + case Platform::IMPL + when :macosx then next if /_(linux|win)\.(cpp|h)$/.match( path ) + when :mswin, :cygwin then next if /_(mac|linux)\.(cpp|h)$/.match( path ) + when :linux, :freebsd then next if /_(mac|win)\.(cpp|h)$/.match( path ) + end + yield( path, File.extname( path ) ) unless File.basename(path) == 'EXAMPLE.cpp' + end + end +end + +########################################################################### impl +sources = Array.new +headers = Array.new +forms = Array.new +resources = Array.new + +abort "usage: qpp file.pro.in" unless File.file? ARGV[0] + +File.open( ARGV[0] ).each_line do |line| + line.chomp! + + matches = /^\s*TEMPLATE += (.*)$/.match( line ) + if !matches.nil? + exit if matches[1].downcase == 'subdirs' + end + + matches = /^\s*VERSION += +((\d\.){0,2}\d)/.match( line ) + if !matches.nil? && !File.file?( "_version.h" ) + File.open( "_version.h", 'w' ) { |f| f.write( "#define VERSION \"#{matches[1]}\"\n" ) } + end +end + +Dir.chdir File.dirname(ARGV[0]) do + find_sources do |path, ext| + path.sub!( /^.\//, '' ) + case ext + when ".h" then headers << path + when ".ui" then forms << path + when ".qrc" then resources << path + when ".cpp" then sources << path + when ".mm" then sources << path if Platform::IMPL == :macosx + when ".m" then sources << path if Platform::IMPL == :macosx + end + end +end + +def write_section( section, array ) + return if array.empty? + print "#{section} +=" + array.each { |path| print " \\\n\t#{path}" } + puts +end + +write_section( "SOURCES", sources ) +write_section( "HEADERS", headers ) +write_section( "FORMS", forms ) +write_section( "RESOURCES", resources ) diff --git a/thirdparty/liblastfm2/admin/utils.rb b/thirdparty/liblastfm2/admin/utils.rb new file mode 100644 index 000000000..ddcb01917 --- /dev/null +++ b/thirdparty/liblastfm2/admin/utils.rb @@ -0,0 +1,40 @@ +cwd = File.dirname( __FILE__ ) +require "#{cwd}/platform.rb" + +def h(s, n) + case Platform::IMPL + when :mswin + puts '==> '+s + else + puts "\033[0;#{n}m==>\033[0;0;1m #{s} \033[0;0m" + end +end + +def h1 s + h(s, 34) +end + +def h2 s + h(s, 33) + yield +end + +def qmake_env(env, qenv) + env=Array.new(1,env) if env.instance_of? String + values=Array.new + env.each { |x| values << ENV[x] if ENV[x] } + if values.size > 0 + "#{qenv} = #{values.join(' ')}\n" + else + nil + end +end + +class PkgConfigNotFound < RuntimeError; end +class PkgNotFound < RuntimeError; end + +def pkgconfig pkg, prettyname + system "pkg-config --exists '#{pkg}'" + raise PkgConfigNotFound if $? == 127 + raise PkgNotFound.new(prettyname) if $? != 0 +end \ No newline at end of file diff --git a/thirdparty/liblastfm2/admin/which_qmake.rb b/thirdparty/liblastfm2/admin/which_qmake.rb new file mode 100644 index 000000000..f621de3e9 --- /dev/null +++ b/thirdparty/liblastfm2/admin/which_qmake.rb @@ -0,0 +1,38 @@ +require "#{File.dirname __FILE__}/platform.rb" + +class QMakeNotFound < RuntimeError; end +class QMakeTooOld < RuntimeError; end + +def which_qmake + args = '-v' + args += ' 2> /dev/null' unless Platform::IMPL == :mswin + + versions = Hash.new + ['qmake','qmake-qt4'].each do |qmake| + begin + /^Using Qt version (\d\.\d\.\d)(-(.+))?/.match( `#{qmake} #{args}` ) + rescue + end + versions[qmake] = $1 unless $1.nil? + end + + raise QMakeNotFound if versions.empty? + + versions.each do |key, v| + i = 1 + j = 0 + v.split( '.' ).reverse.each {|n| j += (n.to_i * i); i *= 100} + versions[key] = j + end + + versions.sort {|a,b| a[1]<=>b[1]} + + versions.each do |k, v| + if v >= 40400 + return k + end + raise QMakeTooOld + end +end + +puts which_qmake if __FILE__ == $0 \ No newline at end of file diff --git a/thirdparty/liblastfm2/cmake/modules/FindLibFFTW3.cmake b/thirdparty/liblastfm2/cmake/modules/FindLibFFTW3.cmake new file mode 100644 index 000000000..fa6419a2b --- /dev/null +++ b/thirdparty/liblastfm2/cmake/modules/FindLibFFTW3.cmake @@ -0,0 +1,45 @@ +# This file is copyrighted under the BSD-license for buildsystem files of KDE +# copyright 2010, Patrick von Reth +# +# +# - Try to find the LIBFFTW3 library +# Once done this will define +# +# LIBFFTW3_FOUND Set to TRUE if LIBFFTW3 librarys and include directory is found +# LIBFFTW3_INCLUDE_DIR The libfftw3 include directory +# LIBFFTW3_LIBRARY The libfftw3 librarys + +if(NOT LIBFFTW3_PRECISION) + message(STATUS "Searching for LIBFFTW3, using default precision float") + set(LIBFFTW3_PRECISION FLOAT) +endif(NOT LIBFFTW3_PRECISION) + +find_path(LIBFFTW3_INCLUDE_DIR fftw3.h) + +if(LIBFFTW3_PRECISION STREQUAL FLOAT) + set(LIBFFTW3_PRECISION_SUFFIX f) +endif(LIBFFTW3_PRECISION STREQUAL FLOAT) + +if(LIBFFTW3_PRECISION STREQUAL DOUBLE) + set(LIBFFTW3_PRECISION_SUFFIX "") +endif(LIBFFTW3_PRECISION STREQUAL DOUBLE) + +if(LIBFFTW3_PRECISION STREQUAL LDOUBLE) + set(LIBFFTW3_PRECISION_SUFFIX l) +endif(LIBFFTW3_PRECISION STREQUAL LDOUBLE) + +find_library(LIBFFTW3_LIBRARY NAMES fftw3${LIBFFTW3_PRECISION_SUFFIX} libfftw3${LIBFFTW3_PRECISION_SUFFIX}-3 fftw3${LIBFFTW3_PRECISION_SUFFIX}-3) + +if(FIND_LIBFFTW3_VERBOSE) + message(STATUS + "LIBFFTW3_PRECISION ${LIBFFTW3_PRECISION}, searched for fftw3${LIBFFTW3_PRECISION_SUFFIX} libfftw3${LIBFFTW3_PRECISION_SUFFIX}-3 fftw3${LIBFFTW3_PRECISION_SUFFIX}-3 + and found ${LIBFFTW3_LIBRARY}" + ) +endif(FIND_LIBFFTW3_VERBOSE) + +if(LIBFFTW3_LIBRARY AND LIBFFTW3_INCLUDE_DIR) + set(LIBFFTW3_FOUND TRUE) + message(STATUS "Found libfftw3 ${LIBFFTW3_LIBRARY}") +else(LIBFFTW3_LIBRARY AND LIBFFTW3_PLUGIN_PATH) + message(STATUS "Could not find libfftw3, get it http://www.fftw.org/") +endif(LIBFFTW3_LIBRARY AND LIBFFTW3_INCLUDE_DIR) diff --git a/thirdparty/liblastfm2/cmake/modules/FindLibSamplerate.cmake b/thirdparty/liblastfm2/cmake/modules/FindLibSamplerate.cmake new file mode 100644 index 000000000..d77536b2f --- /dev/null +++ b/thirdparty/liblastfm2/cmake/modules/FindLibSamplerate.cmake @@ -0,0 +1,22 @@ +# This file is copyrighted under the BSD-license for buildsystem files of KDE +# copyright 2010, Patrick von Reth +# +# +# - Try to find the libsamplerate library +# Once done this will define +# +# LIBSAMPLERATE_FOUND Set to TRUE if libsamplerate librarys and include directory is found +# LIBSAMPLERATE_LIBRARY The libsamplerate librarys +# LIBSAMPLERATE_INCLUDE_DIR The libsamplerate include directory + + +find_library(LIBSAMPLERATE_LIBRARY NAMES samplerate libsamplerate-0 samplerate-0) + +find_path(LIBSAMPLERATE_INCLUDE_DIR samplerate.h) + +if(LIBSAMPLERATE_LIBRARY AND LIBSAMPLERATE_INCLUDE_DIR) + set(LIBSAMPLERATE_FOUND TRUE) + message(STATUS "Found libsamplerate ${LIBSAMPLERATE_LIBRARY}") +else(LIBSAMPLERATE_LIBRARY AND LIBSAMPLERATE_PLUGIN_PATH) + message(STATUS "Could not find libsamplerate, get it http://www.mega-nerd.com/SRC/") +endif(LIBSAMPLERATE_LIBRARY AND LIBSAMPLERATE_INCLUDE_DIR) diff --git a/thirdparty/liblastfm2/configure b/thirdparty/liblastfm2/configure new file mode 100755 index 000000000..757edcbc1 --- /dev/null +++ b/thirdparty/liblastfm2/configure @@ -0,0 +1,127 @@ +#!/usr/bin/ruby +if ARGV.include? '--help' + puts "usage: ./configure [--prefix ] [--release] [--no-strip] [--skip-checks]" + exit +end + +cwd = File.dirname( __FILE__ ) +require "#{cwd}/admin/platform.rb" +require "#{cwd}/admin/which_qmake.rb" +require "#{cwd}/admin/utils.rb" + +begin + IO.read("#{cwd}/src/global.h") =~ /LASTFM_VERSION_STRING\s+"((\d\.)*\d)"/ + abort "Couldn't determine our version!" if $1.nil? + LFM_VERSION=$1 + ENV['LFM_VERSION']=LFM_VERSION + + h1 "Configuring liblastfm-#{LFM_VERSION}..." + + unless ARGV.include? '--skip-checks' + $qmake=which_qmake + pkgconfig 'samplerate', 'libsamplerate' + pkgconfig 'fftw3f', 'fftw' + puts 'Using '+`which #{$qmake}` unless Platform::IMPL == :mswin + else + $qmake='qmake' + end + + h2 'Determining installation prefix' do + if ARGV.include? '--prefix' + n=ARGV.index '--prefix' + ENV['LFM_PREFIX'] = ARGV[n+1] + end + ENV['LFM_PREFIX'] = '/usr/local' if ENV['LFM_PREFIX'].nil? + if File.exists? ENV['LFM_PREFIX'] and !File.directory? ENV['LFM_PREFIX'] + abort "Installation prefix exists but isn't a directory: "+ENV['LFM_PREFIX'] + end + puts "Will install to: "+ENV['LFM_PREFIX'] + end + + h1 'Generating Build System' + + h2 'Generating .qmake.env' do + f = File.new("#{cwd}/.qmake.env", 'w') + f.write qmake_env('CC', 'QMAKE_CC') + f.write qmake_env('CXX', 'QMAKE_CXX') + f.write qmake_env('LDFLAGS', 'QMAKE_LFLAGS_RELEASE') + f.write qmake_env(['CFLAGS', 'CPPFLAGS'], 'QMAKE_CFLAGS_RELEASE') + f.write qmake_env(['CXXFLAGS', 'CPPFLAGS'], 'QMAKE_CXXFLAGS_RELEASE') + f.close + end unless Platform::IMPL == :mswin + + h2 "Running qpp..." do + ['src/lastfm.pro','src/fingerprint/fingerprint.pro'].each do |p| + d="#{cwd}/#{File.dirname p}" + f=File.new "#{d}/_files.qmake", 'w' + f.write `ruby admin/qpp #{p}` + # on Windows VERSION produces lastfm0.dll, the 0 breaks the build + f.puts "VERSION = #{LFM_VERSION}" unless Platform::OS == :win32 + end + end + + h2 "Configuring qmake..." do + args=Array.new + args << '-spec macx-g++' if Platform::IMPL == :macosx + if ARGV.include? '--release' + args << '-config release' + args << '"CONFIG += app_bundle"' if Platform::IMPL == :macosx and ARGV.include? '--bundle' + else + args << '-config debug' + end + if ARGV.include? '--no-strip' + args << '"CONFIG += nostrip"' + end + ENV['LFM_QMAKE'] = "#{$qmake} #{args.join(' ')}" + end + + h2 "Generating Makefile..." do + hs = Array.new + hs << 'global.h' + hs << 'core/UrlBuilder.h' << 'core/XmlQuery.h' << 'core/misc.h' + hs << 'fingerprint/Fingerprint.h' << 'fingerprint/FingerprintableSource.h' + hs << 'radio/RadioStation.h' << 'radio/RadioTuner.h' + hs << 'scrobble/Audioscrobbler.h' << 'scrobble/ScrobblePoint.h' << 'scrobble/ScrobbleCache.h' + hs << 'types/AbstractType.h' << 'types/Track.h' << 'types/Mbid.h' << 'types/Artist.h' << 'types/Album.h' << 'types/FingerprintId.h' << 'types/Playlist.h' << 'types/Tag.h' << 'types/User.h' << 'types/Xspf.h' + hs << 'ws/ws.h' << 'ws/InternetConnectionMonitor.h' << 'ws/NetworkAccessManager.h' + + File.new("#{cwd}/Makefile", 'w').write `ruby admin/Makefile.rb #{hs.join(' ')}` + end + + case Platform::IMPL + when :mswin then make='nmake all' + else make='make' # NOTE only tested with GNU make, sorry :( + end + + puts + puts "Good, your configure is finished! Now type: #{make}" + +rescue QMakeTooOld + puts <<-sput + + Your version of Qt seems to be too old, we require Qt 4.4 or above. + + It is possible you have Qt3 and Qt4 both installed. Locate your Qt4 + installation and ensure it is placed first in the path, eg: + + PATH=/opt/qt4/bin:\$PATH ./configure + + sput + exit 1 +rescue QMakeNotFound + puts "Sorry, qmake was not found, is Qt4 installed?" + exit 2 +rescue PkgNotFound => e + puts <<-sput + + Sorry, we couldn't find #{e}. + You can try to compile anyway by forcing configure to finish: + + ./configure --skip-checks + + sput + exit 3 +rescue PkgConfigNotFound + puts "Sorry, pkg-config could not be found. You should install it!" + exit 4 +end diff --git a/thirdparty/liblastfm2/demos/demo1.cpp b/thirdparty/liblastfm2/demos/demo1.cpp new file mode 100644 index 000000000..b0fd8df99 --- /dev/null +++ b/thirdparty/liblastfm2/demos/demo1.cpp @@ -0,0 +1,95 @@ +/* + This software is in the public domain, furnished "as is", without technical + support, and with no warranty, express or implied, as to its usefulness for + any purpose. +*/ +#include // this includes everything in liblastfm, you may prefer +#include // to just include what you need with your project. Still +#include // we've given you the option. +#include +#include + +class ArtistList : public QListWidget +{ + Q_OBJECT + + QPointer reply; + QString artist; + +public: + ArtistList() + { + connect( this, + SIGNAL(itemActivated( QListWidgetItem* )), + SLOT(onItemActivated( QListWidgetItem* )) ); + } + + void getSimilar( const QString& artist ) + { + this->artist = artist; + setWindowTitle( "Loading " + artist + "..." ); + + // deleting a reply cancels the request and disconnects all signals + delete reply; + reply = lastfm::Artist( artist ).getSimilar(); + connect( reply, SIGNAL(finished()), SLOT(onGotSimilar()) ); + } + +private slots: + void onGotSimilar() + { + QNetworkReply* r = static_cast(sender()); + // always enclose retrieval functions in a try block, as they will + // throw if they can't parse the data + try + { + // you decode the response using the equivalent static function + QMap artists = lastfm::Artist::getSimilar( r ); + + clear(); + + // we iterate backwards because best match is last because the map + // sorts itself by key + QStringListIterator i( artists.values() ); + i.toBack(); + while (i.hasPrevious()) + addItem( i.previous() ); + + setWindowTitle( artist ); + } + catch (std::runtime_error& e) + { + // if getSimilar() failed to parse the QNetworkReply, then e will + // be of type lastfm::ws::ParseError, which derives + // std::runtime_error + qWarning() << e.what(); + } + } + + void onItemActivated( QListWidgetItem* item ) + { + getSimilar( item->text() ); + } +}; + + +int main( int argc, char** argv ) +{ + QApplication app( argc, argv ); + app.setApplicationName( "liblastfm" ); // used to generate UserAgent + + // all you need for non-authenticated webservices is your API key + // this one is a public one, it can only do artist.getSimilar calls, so + // I suggest you don't use it :P + lastfm::ws::ApiKey = "b25b959554ed76058ac220b7b2e0a026"; + + ArtistList artists; + artists.getSimilar( "nirvana" ); + artists.resize( 300, 400 ); // Qt picks truly asanine default sizes for its widgets + artists.show(); + + return app.exec(); +} + + +#include "demo1.moc" diff --git a/thirdparty/liblastfm2/demos/demo2.cpp b/thirdparty/liblastfm2/demos/demo2.cpp new file mode 100644 index 000000000..53c9a7ec9 --- /dev/null +++ b/thirdparty/liblastfm2/demos/demo2.cpp @@ -0,0 +1,114 @@ +/* + This software is in the public domain, furnished "as is", without technical + support, and with no warranty, express or implied, as to its usefulness for + any purpose. +*/ +#include +#include + + +struct MyCoreApp : QCoreApplication +{ + Q_OBJECT + +public: + MyCoreApp( int& argc, char**& argv ) : QCoreApplication( argc, argv ) + {} + +private slots: + void onWsError( lastfm::ws::Error e ) + { + // QNetworkReply will invoke this slot on application level errors + // mostly this is only stuff like Ws::InvalidSessionKey and + // Ws::InvalidApiKey + qWarning() << e; + } +}; + + +int main( int argc, char** argv ) +{ + MyCoreApp app( argc, argv ); + // this is used to generate the UserAgent for webservice requests + // please set it to something sensible in your application + app.setApplicationName( "liblastfm" ); + +////// you'll need to fill these in for this demo to work + lastfm::ws::Username = + lastfm::ws::ApiKey = + lastfm::ws::SharedSecret = + QString password = + +////// Usually you never have to construct an Last.fm WS API call manually + // eg. Track.getTopTags() just returns a QNetworkReply* but authentication is + // different. + // We're using getMobileSession here as we're a console app, but you + // should use getToken if you can as the user will be presented with a + // route that feels my trustworthy to them than entering their password + // into some random app they just downloaded... ;) + QMap params; + params["method"] = "auth.getMobileSession"; + params["username"] = lastfm::ws::Username; + params["authToken"] = lastfm::md5( (lastfm::ws::Username + lastfm::md5( password.toUtf8() )).toUtf8() ); + QNetworkReply* reply = lastfm::ws::post( params ); + + // never do this when an event loop is running it's a real HACK + QEventLoop loop; + loop.connect( reply, SIGNAL(finished()), SLOT(quit()) ); + loop.exec(); + + try + { + ////// Usually there is a convenience function to decode the output from + // ws calls too, but again, authentication is different. We think you + // need to handle it yourselves :P Also conveniently it means you + // can learn more about what our webservices return, eg. this service + // will return an XML document like this: + // + // + // + // mxcl + // d580d57f32848f5dcf574d1ce18d78b2 + // 1 + // + // + // + // If status is not "ok" then this function throws + lastfm::XmlQuery const lfm = lastfm::ws::parse( reply ); + + // replace username; because eg. perhaps the user typed their + // username with the wrong case + lastfm::ws::Username = lfm["session"]["name"].text(); + + // we now have a session key, you should save this, forever! Really. + // DO NOT AUTHENTICATE EVERY TIME THE APP STARTS! You only have to do + // this once. Or again if the user deletes your key on the site. If + // that happens you'll get notification to your onWsError() function, + // see above. + lastfm::ws::SessionKey = lfm["session"]["key"].text(); + + qDebug() << "sk:" << lastfm::ws::SessionKey; + + ////// because the SessionKey is now set, the AuthenticatedUser class will + // work. And we can call authenticated calls + QNetworkReply* reply = lastfm::AuthenticatedUser().getRecommendedArtists(); + + // again, you shouldn't do this.. ;) + QEventLoop loop; + loop.connect( reply, SIGNAL(finished()), SLOT(quit()) ); + loop.exec(); + + // yay, a list rec'd artists to stderr :) + qDebug() << lastfm::Artist::list( reply ); + } + catch (std::runtime_error& e) + { + // lastfm::ws::parse() can throw lastfm::ws::ParseError, this + // exception derives std::runtime_error + qWarning() << e.what(); + return 1; + } +} + + +#include "demo2.moc" diff --git a/thirdparty/liblastfm2/demos/demo3.cpp b/thirdparty/liblastfm2/demos/demo3.cpp new file mode 100644 index 000000000..2bf3c1db3 --- /dev/null +++ b/thirdparty/liblastfm2/demos/demo3.cpp @@ -0,0 +1,63 @@ +/* + This software is in the public domain, furnished "as is", without technical + support, and with no warranty, express or implied, as to its usefulness for + any purpose. +*/ +#include +#include +#include +#include "src/_version.h" + + +struct MyCoreApp : QCoreApplication +{ + Q_OBJECT + +public: + MyCoreApp( int& argc, char** argv ) : QCoreApplication( argc, argv ) + {} + +public slots: + void onStatus( int status ) + { + qDebug() << lastfm::Audioscrobbler::Status(status); + } +}; + + +int main( int argc, char** argv ) +{ + // all 6 of these lines are REQUIRED in order to scrobble + // this demo requires you to fill in the blanks as well... + lastfm::ws::Username = + lastfm::ws::ApiKey = + lastfm::ws::SharedSecret = + lastfm::ws::SessionKey = // you need to auth to get this... try demo2 + QCoreApplication::setApplicationName( "liblastfm" ); + QCoreApplication::setApplicationVersion( VERSION ); + + MyCoreApp app( argc, argv ); + + lastfm::MutableTrack t; + t.setArtist( "Max Howell" ); + t.setTitle( "I Told You Not To Trust Me With Your Daughter" ); + t.setDuration( 30 ); + t.stamp(); //sets track start time + + lastfm::Audioscrobbler as( "ass" ); + as.nowPlaying( t ); + // Audioscrobbler will submit whatever is in the cache when you call submit. + // And the cache is persistent between sessions. So you should cache at the + // scrobble point usually, not before + as.cache( t ); + + //FIXME I don't get it, but the timer never triggers! pls fork and fix! + QTimer::singleShot( 31*1000, &as, SLOT(submit()) ); + + app.connect( &as, SIGNAL(status(int)), SLOT(onStatus(int)) ); + + return app.exec(); +} + + +#include "demo3.moc" diff --git a/thirdparty/liblastfm2/demos/demos.pro b/thirdparty/liblastfm2/demos/demos.pro new file mode 100644 index 000000000..356edff74 --- /dev/null +++ b/thirdparty/liblastfm2/demos/demos.pro @@ -0,0 +1,3 @@ +QT = core gui network xml +LIBS += -llastfm -L$$DESTDIR +SOURCES = demo1.cpp # change to demo2.cpp (etc.) to compile that demo diff --git a/thirdparty/liblastfm2/src/CMakeLists.txt b/thirdparty/liblastfm2/src/CMakeLists.txt new file mode 100644 index 000000000..c7d569442 --- /dev/null +++ b/thirdparty/liblastfm2/src/CMakeLists.txt @@ -0,0 +1,114 @@ +cmake_minimum_required(VERSION 2.6) + +# Macro to copy and rename headers +macro(copy_header from to) + configure_file( + ${CMAKE_CURRENT_SOURCE_DIR}/${from} + ${CMAKE_CURRENT_BINARY_DIR}/lastfm/${to} + COPY_ONLY + ) + install( + FILES ${CMAKE_CURRENT_BINARY_DIR}/lastfm/${to} + DESTINATION include/lastfm/ + ) +endmacro(copy_header) + +# Copy headers +copy_header(core/misc.h misc.h) +copy_header(core/XmlQuery.h XmlQuery) +copy_header(core/UrlBuilder.h UrlBuilder) +copy_header(global.h global.h) +copy_header(radio/RadioTuner.h RadioTuner) +copy_header(radio/RadioStation.h RadioStation) +copy_header(scrobble/Audioscrobbler.h Audioscrobbler) +copy_header(scrobble/ScrobbleCache.h ScrobbleCache) +copy_header(scrobble/ScrobblePoint.h ScrobblePoint) +copy_header(types/AbstractType.h AbstractType) +copy_header(types/Album.h Album) +copy_header(types/Artist.h Artist) +copy_header(types/FingerprintId.h FingerprintId) +copy_header(types/Mbid.h Mbid) +copy_header(types/Playlist.h Playlist) +copy_header(types/Tag.h Tag) +copy_header(types/Track.h Track) +copy_header(types/User.h User) +copy_header(types/User.h UserList) +copy_header(types/Xspf.h Xspf) +copy_header(ws/ws.h ws.h) +copy_header(ws/InternetConnectionMonitor.h InternetConnectionMonitor) +copy_header(ws/NetworkAccessManager.h NetworkAccessManager) + +include_directories(${QT_INCLUDES} ${CMAKE_CURRENT_BINARY_DIR}) + +set(SOURCES + scrobble/ScrobbleCache.cpp + scrobble/Audioscrobbler.cpp + types/FingerprintId.cpp + types/Artist.cpp + types/Tag.cpp + types/Track.cpp + types/User.cpp + types/Xspf.cpp + types/Album.cpp + types/Playlist.cpp + types/Mbid.cpp + radio/RadioTuner.cpp + radio/RadioStation.cpp + core/UrlBuilder.cpp + core/misc.cpp + core/XmlQuery.cpp + ws/NetworkAccessManager.cpp + ws/ws.cpp + ws/InternetConnectionMonitor.cpp + ws/NetworkConnectionMonitor.cpp +) + +set(MOC_HEADERS + scrobble/Audioscrobbler.h + types/Track.h + radio/RadioTuner.h + ws/NetworkConnectionMonitor.h + ws/InternetConnectionMonitor.h + ws/NetworkAccessManager.h +) + +if(UNIX) + if(APPLE) + set(SOURCES ${SOURCES} ws/mac/MNetworkConnectionMonitor_mac.cpp) + set(MOC_HEADERS ${MOC_HEADERS} ws/mac/MNetworkConnectionMonitor.h) + else(APPLE) + set(SOURCES ${SOURCES} ws/linux/LNetworkConnectionMonitor_linux.cpp) + set(MOC_HEADERS ${MOC_HEADERS} ws/linux/LNetworkConnectionMonitor.h) + endif(APPLE) +endif(UNIX) +if(WIN32) + set(SOURCES ${SOURCES} ws/win/WNetworkConnectionMonitor_win.cpp) + set(MOC_HEADERS ${MOC_HEADERS} ws/win/WNetworkConnectionMonitor.h) +endif(WIN32) + +qt4_wrap_cpp(MOC_SOURCES ${MOC_HEADERS}) + +add_library(tomahawk_lastfm2 SHARED + ${SOURCES} + ${MOC_SOURCES} +) + +target_link_libraries(tomahawk_lastfm2 + ${QT_LIBRARIES} + ${QT_QTDBUS_LIBRARY} +) + +set_target_properties(tomahawk_lastfm2 PROPERTIES COMPILE_FLAGS "-DLASTFM_OHAI_QMAKE" ) + +if(APPLE) + target_link_libraries(tomahawk_lastfm2 + /System/Library/Frameworks/CoreFoundation.framework + /System/Library/Frameworks/SystemConfiguration.framework + ) +endif(APPLE) + +install(TARGETS tomahawk_lastfm2 + RUNTIME DESTINATION bin + LIBRARY DESTINATION lib${LIB_SUFFIX} + ARCHIVE DESTINATION lib${LIB_SUFFIX} +) diff --git a/thirdparty/liblastfm2/src/core/README b/thirdparty/liblastfm2/src/core/README new file mode 100644 index 000000000..b725eac20 --- /dev/null +++ b/thirdparty/liblastfm2/src/core/README @@ -0,0 +1,8 @@ +Files in lastfm-core are basically extensions to fundamental Qt classes. +They may be useful to you, but mainly they are here because they are useful to +liblastfm in general. + +A lot of the time they are convenience functions that hopefully at some point +Qt will make obsolete. + + diff --git a/thirdparty/liblastfm2/src/core/UrlBuilder.cpp b/thirdparty/liblastfm2/src/core/UrlBuilder.cpp new file mode 100644 index 000000000..0f451c7e5 --- /dev/null +++ b/thirdparty/liblastfm2/src/core/UrlBuilder.cpp @@ -0,0 +1,83 @@ +/* + Copyright 2009 Last.fm Ltd. + - Primarily authored by Max Howell, Jono Cole and Doug Mansell + + This file is part of liblastfm. + + liblastfm 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. + + liblastfm 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 liblastfm. If not, see . +*/ +#include "UrlBuilder.h" +#include +#include + + +QUrl +lastfm::UrlBuilder::url() const +{ + QUrl url; + url.setScheme( "http" ); + url.setHost( host() ); + url.setEncodedPath( path ); + return url; +} + + +QByteArray //static +lastfm::UrlBuilder::encode( QString s ) +{ + foreach (QChar c, QList() << '&' << '/' << ';' << '+' << '#' << '%') + if (s.contains( c )) + // the middle step may seem odd but this is what the site does + // eg. search for the exact string "Radiohead 2 + 2 = 5" + return QUrl::toPercentEncoding( s ).replace( "%20", "+" ).toPercentEncoding( "", "+" );; + + return QUrl::toPercentEncoding( s.replace( ' ', '+' ), "+" ); +} + + +QString //static +lastfm::UrlBuilder::host( const QLocale& locale ) +{ + switch (locale.language()) + { + case QLocale::Portuguese: return "www.lastfm.com.br"; + case QLocale::Turkish: return "www.lastfm.com.tr"; + case QLocale::French: return "www.lastfm.fr"; + case QLocale::Italian: return "www.lastfm.it"; + case QLocale::German: return "www.lastfm.de"; + case QLocale::Spanish: return "www.lastfm.es"; + case QLocale::Polish: return "www.lastfm.pl"; + case QLocale::Russian: return "www.lastfm.ru"; + case QLocale::Japanese: return "www.lastfm.jp"; + case QLocale::Swedish: return "www.lastfm.se"; + case QLocale::Chinese: return "cn.last.fm"; + default: return "www.last.fm"; + } +} + + +QUrl //static +lastfm::UrlBuilder::localize( QUrl url) +{ + url.setHost( url.host().replace( QRegExp("^(www.)?last.fm"), host() ) ); + return url; +} + + +QUrl //static +lastfm::UrlBuilder::mobilize( QUrl url ) +{ + url.setHost( url.host().replace( QRegExp("^(www.)?last"), "m.last" ) ); + return url; +} diff --git a/thirdparty/liblastfm2/src/core/UrlBuilder.h b/thirdparty/liblastfm2/src/core/UrlBuilder.h new file mode 100644 index 000000000..42014537c --- /dev/null +++ b/thirdparty/liblastfm2/src/core/UrlBuilder.h @@ -0,0 +1,69 @@ +/* + Copyright 2009 Last.fm Ltd. + - Primarily authored by Max Howell, Jono Cole and Doug Mansell + + This file is part of liblastfm. + + liblastfm 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. + + liblastfm 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 liblastfm. If not, see . +*/ +#ifndef LASTFM_URL_BUILDER_H +#define LASTFM_URL_BUILDER_H + +#include +#include +#include +#include + + +namespace lastfm +{ + /** For building www.last.fm urls. We have special rules for encoding and that */ + class LASTFM_DLLEXPORT UrlBuilder + { + QByteArray path; + + public: + /** Careful, the base is not encoded at all, we assume it is ASCII! + * If you need it encoded at all you must use the slash function. + * eg. UrlBuilder( "user" ).slash( "mxcl" ) ==> http://last.fm/user/mxcl + */ + UrlBuilder( const QString& base ) : path( '/' + base.toAscii() ) + {} + + UrlBuilder& slash( const QString& path ) { this->path += '/' + encode( path ); return *this; } + + QUrl url() const; + + /** www.last.fm becomes the local version, eg www.lastfm.de */ + static QUrl localize( QUrl ); + /** www.last.fm becomes m.last.fm, localisation is preserved */ + static QUrl mobilize( QUrl ); + + /** Use this to URL encode any database item (artist, track, album). It + * internally calls UrlEncodeSpecialChars to double encode some special + * symbols according to the same pattern as that used on the website. + * + * &, /, ;, +, # + * + * Use for any urls that go to www.last.fm + * Do not use for ws.audioscrobbler.com + */ + static QByteArray encode( QString ); + + /** returns eg. www.lastfm.de */ + static QString host( const QLocale& = QLocale() ); + }; +} + +#endif diff --git a/thirdparty/liblastfm2/src/core/XmlQuery.cpp b/thirdparty/liblastfm2/src/core/XmlQuery.cpp new file mode 100644 index 000000000..5c68aaa81 --- /dev/null +++ b/thirdparty/liblastfm2/src/core/XmlQuery.cpp @@ -0,0 +1,64 @@ +/* + Copyright 2009 Last.fm Ltd. + - Primarily authored by Max Howell, Jono Cole and Doug Mansell + + This file is part of liblastfm. + + liblastfm 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. + + liblastfm 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 liblastfm. If not, see . +*/ +#include "XmlQuery.h" +#include +using lastfm::XmlQuery; + + +XmlQuery::XmlQuery( const QByteArray& bytes ) +{ + domdoc.setContent(bytes); + e = domdoc.documentElement(); +} + + +XmlQuery +XmlQuery::operator[]( const QString& name ) const +{ + QStringList parts = name.split( ' ' ); + if (parts.size() >= 2) + { + QString tagName = parts[0]; + parts = parts[1].split( '=' ); + QString attributeName = parts.value( 0 ); + QString attributeValue = parts.value( 1 ); + + foreach (XmlQuery e, children( tagName )) + if (e.e.attribute( attributeName ) == attributeValue) + return e; + } + XmlQuery xq( e.firstChildElement( name ), name.toUtf8().data() ); + xq.domdoc = this->domdoc; + return xq; +} + + +QList +XmlQuery::children( const QString& named ) const +{ + QList elements; + QDomNodeList nodes = e.elementsByTagName( named ); + for (int x = 0; x < nodes.count(); ++x) { + XmlQuery xq( nodes.at( x ).toElement() ); + xq.domdoc = this->domdoc; + elements += xq; + } + return elements; +} diff --git a/thirdparty/liblastfm2/src/core/XmlQuery.h b/thirdparty/liblastfm2/src/core/XmlQuery.h new file mode 100644 index 000000000..a3c22d5c2 --- /dev/null +++ b/thirdparty/liblastfm2/src/core/XmlQuery.h @@ -0,0 +1,77 @@ +/* + Copyright 2009 Last.fm Ltd. + - Primarily authored by Max Howell, Jono Cole and Doug Mansell + + This file is part of liblastfm. + + liblastfm 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. + + liblastfm 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 liblastfm. If not, see . +*/ +#ifndef LASTFM_XMLQUERY_H +#define LASTFM_XMLQUERY_H + +#include +#include +#include + +namespace lastfm +{ + /** Qt's XmlQuery implementation is totally unimpressive, so this is a + * hack that feels like jQuery */ + class LASTFM_DLLEXPORT XmlQuery + { + QDomDocument domdoc; + QDomElement e; + + public: + /** we assume the bytearray is an XML document, this object will then + * represent the documentElement of that document, eg. if this is a + * Last.fm webservice response: + * + * XmlQuery xq = lastfm::ws::parse(response); + * qDebug() << xq["artist"].text() + * + * Notice the lfm node is not referenced, that is because it is the + * document-element of the XML document. + */ + XmlQuery( const QByteArray& ); + + XmlQuery( const QDomElement& e, const char* name = "" ) : e( e ) + { + if (e.isNull()) qWarning() << "Expected node absent:" << name; + } + + /** Selects a DIRECT child element, you can specify attributes like so: + * + * e["element"]["element attribute=value"].text(); + */ + XmlQuery operator[]( const QString& name ) const; + QString text() const { return e.text(); } + QString attribute( const QString& name ) const{ return e.attribute( name ); } + + /** selects all children with specified name, recursively */ + QList children( const QString& named ) const; + + operator QDomElement() const { return e; } + }; +} + +inline QDebug operator<<( QDebug d, const lastfm::XmlQuery& xq ) +{ + QString s; + QTextStream t( &s, QIODevice::WriteOnly ); + QDomElement(xq).save( t, 2 ); + return d << s; +} + +#endif diff --git a/thirdparty/liblastfm2/src/core/misc.cpp b/thirdparty/liblastfm2/src/core/misc.cpp new file mode 100644 index 000000000..13b5d16af --- /dev/null +++ b/thirdparty/liblastfm2/src/core/misc.cpp @@ -0,0 +1,205 @@ +/* + Copyright 2009 Last.fm Ltd. + - Primarily authored by Max Howell, Jono Cole and Doug Mansell + + This file is part of liblastfm. + + liblastfm 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. + + liblastfm 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 liblastfm. If not, see . +*/ +#include "misc.h" +#include +#ifdef WIN32 + #include +#endif +#ifdef Q_WS_MAC + #include +#endif + + +#ifdef Q_WS_MAC +QDir +lastfm::dir::bundle() +{ + // Trolltech provided example + CFURLRef appUrlRef = CFBundleCopyBundleURL( CFBundleGetMainBundle() ); + CFStringRef macPath = CFURLCopyFileSystemPath( appUrlRef, kCFURLPOSIXPathStyle ); + QString path = CFStringToQString( macPath ); + CFRelease(appUrlRef); + CFRelease(macPath); + return QDir( path ); +} +#endif + + +static QDir dataDotDot() +{ +#ifdef WIN32 + if ((QSysInfo::WindowsVersion & QSysInfo::WV_DOS_based) == 0) + { + // Use this for non-DOS-based Windowses + char path[MAX_PATH]; + HRESULT h = SHGetFolderPathA( NULL, + CSIDL_LOCAL_APPDATA | CSIDL_FLAG_CREATE, + NULL, + 0, + path ); + if (h == S_OK) + return QString::fromLocal8Bit( path ); + } + return QDir::home(); + +#elif defined(Q_WS_MAC) + + #define EIT( x ) { OSErr err = x; if (err != noErr) throw 1; } + try + { + short vRefNum = 0; + long dirId; + EIT( ::FindFolder( kOnAppropriateDisk, + kApplicationSupportFolderType, + kDontCreateFolder, + &vRefNum, + &dirId ) ); + + // Now we have a vRefNum and a dirID - but *not* an Unix-Path as string. + // Lets make one based from this: + FSSpec fsspec; + EIT( ::FSMakeFSSpec( vRefNum, dirId, NULL, &fsspec ) ); + + // ...and build an FSRef based on thes FSSpec. + FSRef fsref; + EIT( ::FSpMakeFSRef( &fsspec, &fsref ) ); + + // ...then extract the Unix Path as a C-String from the FSRef + unsigned char path[512]; + EIT( ::FSRefMakePath( &fsref, path, 512 ) ); + + return QDir::homePath() + QString::fromUtf8( (char*)path ); + } + catch (int) + { + return QDir::home().filePath( "Library/Application Support" ); + } + +#elif defined(Q_WS_X11) + return QDir::home().filePath( ".local/share" ); + +#else + return QDir::home(); +#endif +} + + +QDir +lastfm::dir::runtimeData() +{ + return dataDotDot().filePath( "Last.fm" ); +} + + +QDir +lastfm::dir::logs() +{ +#ifdef Q_WS_MAC + return QDir::home().filePath( "Library/Logs/Last.fm" ); +#else + return runtimeData(); +#endif +} + + +QDir +lastfm::dir::cache() +{ +#ifdef Q_WS_MAC + return QDir::home().filePath( "Library/Caches/Last.fm" ); +#else + return runtimeData().filePath( "cache" ); +#endif +} + + +#ifdef WIN32 +QDir +lastfm::dir::programFiles() +{ + char path[MAX_PATH]; + + // TODO: this call is dependant on a specific version of shell32.dll. + // Need to degrade gracefully. Need to bundle SHFolder.exe with installer + // and execute it on install for this to work on Win98. + HRESULT h = SHGetFolderPathA( NULL, + CSIDL_PROGRAM_FILES, + NULL, + 0, // current path + path ); + + if (h != S_OK) + { + qCritical() << "Couldn't get Program Files dir. Possibly Win9x?"; + return QDir(); + } + + return QString::fromLocal8Bit( path ); +} +#endif + +#ifdef Q_WS_MAC +CFStringRef +lastfm::QStringToCFString( const QString &s ) +{ + return CFStringCreateWithCharacters( 0, (UniChar*)s.unicode(), s.length() ); +} + +QByteArray +lastfm::CFStringToUtf8( CFStringRef s ) +{ + QByteArray result; + + if (s != NULL) + { + CFIndex length; + length = CFStringGetLength( s ); + length = CFStringGetMaximumSizeForEncoding( length, kCFStringEncodingUTF8 ) + 1; + char* buffer = new char[length]; + + if (CFStringGetCString( s, buffer, length, kCFStringEncodingUTF8 )) + result = QByteArray( buffer ); + else + qWarning() << "CFString conversion failed."; + + delete[] buffer; + } + + return result; +} +#endif + +#if 0 +// this is a Qt implementation I found +QString cfstring2qstring(CFStringRef str) +{ + if(!str) + return QString(); + + CFIndex length = CFStringGetLength(str); + if(const UniChar *chars = CFStringGetCharactersPtr(str)) + return QString((QChar *)chars, length); + UniChar *buffer = (UniChar*)malloc(length * sizeof(UniChar)); + CFStringGetCharacters(str, CFRangeMake(0, length), buffer); + QString ret((QChar *)buffer, length); + free(buffer); + return ret; +} +#endif diff --git a/thirdparty/liblastfm2/src/core/misc.h b/thirdparty/liblastfm2/src/core/misc.h new file mode 100644 index 000000000..3f41ec534 --- /dev/null +++ b/thirdparty/liblastfm2/src/core/misc.h @@ -0,0 +1,111 @@ +/* + Copyright 2009 Last.fm Ltd. + - Primarily authored by Max Howell, Jono Cole and Doug Mansell + + This file is part of liblastfm. + + liblastfm 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. + + liblastfm 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 liblastfm. If not, see . +*/ +#ifndef LASTFM_MISC_H +#define LASTFM_MISC_H + +#include +#include +#include +#include + +#ifdef Q_WS_MAC +typedef const struct __CFString* CFStringRef; +#endif + +namespace lastfm +{ + namespace dir + { + #ifdef Q_WS_WIN + LASTFM_DLLEXPORT QDir programFiles(); + #endif + #ifdef Q_WS_MAC + LASTFM_DLLEXPORT QDir bundle(); + #endif + LASTFM_DLLEXPORT QDir runtimeData(); + LASTFM_DLLEXPORT QDir cache(); + LASTFM_DLLEXPORT QDir logs(); + } + +#ifdef Q_WS_MAC + LASTFM_DLLEXPORT QByteArray CFStringToUtf8( CFStringRef ); + LASTFM_DLLEXPORT CFStringRef QStringToCFString( const QString& ); + inline QString CFStringToQString( CFStringRef s ); +#endif + + inline const char* platform() + { + #ifdef Q_WS_WIN + switch (QSysInfo::WindowsVersion) + { + case QSysInfo::WV_32s: return "Windows 3.1 with Win32s"; + case QSysInfo::WV_95: return "Windows 95"; + case QSysInfo::WV_98: return "Windows 98"; + case QSysInfo::WV_Me: return "Windows Me"; + case QSysInfo::WV_DOS_based: return "MS-DOS-based Windows"; + + case QSysInfo::WV_NT: return "Windows NT"; + case QSysInfo::WV_2000: return "Windows 2000"; + case QSysInfo::WV_XP: return "Windows XP"; + case QSysInfo::WV_2003: return "Windows Server 2003"; + case QSysInfo::WV_VISTA: return "Windows Vista"; + case QSysInfo::WV_NT_based: return "NT-based Windows"; + + case QSysInfo::WV_CE: return "Windows CE"; + case QSysInfo::WV_CENET: return "Windows CE.NET"; + case QSysInfo::WV_CE_based: return "CE-based Windows"; + + default: return "Unknown"; + } + #elif defined Q_WS_MAC + switch (QSysInfo::MacintoshVersion) + { + case QSysInfo::MV_Unknown: return "Unknown Mac"; + case QSysInfo::MV_9: return "Mac OS 9"; + case QSysInfo::MV_10_0: return "Mac OS X 10.0"; + case QSysInfo::MV_10_1: return "Mac OS X 10.1"; + case QSysInfo::MV_10_2: return "Mac OS X 10.2"; + case QSysInfo::MV_10_3: return "Mac OS X 10.3"; + case QSysInfo::MV_10_4: return "Mac OS X 10.4"; + case QSysInfo::MV_10_5: return "Mac OS X 10.5"; + + default: return "Unknown"; + } + #elif defined Q_WS_X11 + return "UNIX X11"; + #else + return "Unknown"; + #endif + } + + inline QString md5( const QByteArray& src ) + { + QByteArray const digest = QCryptographicHash::hash( src, QCryptographicHash::Md5 ); + return QString::fromLatin1( digest.toHex() ).rightJustified( 32, '0' ).toLower(); + } +} + +#ifdef Q_WS_MAC +inline QString lastfm::CFStringToQString( CFStringRef s ) +{ + return QString::fromUtf8( CFStringToUtf8( s ) ); +} +#endif +#endif //LASTFM_MISC_H diff --git a/thirdparty/liblastfm2/src/fingerprint/CMakeLists.txt b/thirdparty/liblastfm2/src/fingerprint/CMakeLists.txt new file mode 100644 index 000000000..eae8397a8 --- /dev/null +++ b/thirdparty/liblastfm2/src/fingerprint/CMakeLists.txt @@ -0,0 +1,32 @@ +cmake_minimum_required(VERSION 2.6) + +include_directories(${LIBFFTW3_INCLUDE_DIRS}) +include_directories(${LIBSAMPLERATE_INCLUDE_DIRS}) +include_directories(${QT_INCLUDES}) +include_directories(${CMAKE_CURRENT_BINARY_DIR}/..) + +link_directories(${LIBFFTW3_LIBRARY_DIRS}) +link_directories(${LIBSAMPLERATE_LIBRARY_DIRS}) + +set(SOURCES + Collection.cpp + Fingerprint.cpp + Sha256.cpp + fplib/Filter.cpp + fplib/FingerprintExtractor.cpp + fplib/OptFFT.cpp +) + +add_library(tomahawk_lastfm2_fingerprint SHARED + ${SOURCES} +) + +target_link_libraries(tomahawk_lastfm2_fingerprint + ${QT_LIBRARIES} + ${QT_QTSQL_LIBRARY} + ${LIBFFTW3_LIBRARY} + ${LIBSAMPLERATE_LIBRARY} + tomahawk_lastfm2 +) + +set_target_properties(tomahawk_lastfm2_fingerprint PROPERTIES COMPILE_FLAGS "-DLASTFM_FINGERPRINT_OHAI_QMAKE" ) diff --git a/thirdparty/liblastfm2/src/fingerprint/Collection.cpp b/thirdparty/liblastfm2/src/fingerprint/Collection.cpp new file mode 100644 index 000000000..214e264e2 --- /dev/null +++ b/thirdparty/liblastfm2/src/fingerprint/Collection.cpp @@ -0,0 +1,267 @@ +/* + Copyright 2009 Last.fm Ltd. + - Primarily authored by Max Howell, Jono Cole and Doug Mansell + + This file is part of liblastfm. + + liblastfm 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. + + liblastfm 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 liblastfm. If not, see . +*/ + +#include "Collection.h" +#include "../core/misc.h" +#include +#include +#include +#include +#include +#include +#include + +static const int k_collectionDbVersion = 1; + +// Singleton instance needs to be initialised +Collection* Collection::s_instance = NULL; + + +Collection::Collection() +{ + m_db = QSqlDatabase::addDatabase( "QSQLITE", "collection" ); + m_db.setDatabaseName( lastfm::dir::runtimeData().filePath( "collection.db" ) ); + + if (!m_db.open()) { + qDebug() << m_db.lastError(); + return; + } + + if (!m_db.isValid()) { + qWarning() << "collection.db connection is not valid"; + return; + } + + if (!m_db.tables().contains( "files" )) + { + qDebug() << "Creating Collection database"; + + query( "CREATE TABLE artists (" + "id INTEGER PRIMARY KEY AUTOINCREMENT," + "serverUid INTEGER," + "lcName TEXT NOT NULL," + "displayName TEXT NOT NULL );" ); + + query( "CREATE TABLE albums (" + "id INTEGER PRIMARY KEY AUTOINCREMENT," + "serverUid INTEGER," + "lcName TEXT NOT NULL," + "displayName TEXT NOT NULL," + "primaryArtist INTEGER NOT NULL );" ); + + query( "CREATE UNIQUE INDEX album_names_idx ON albums ( primaryArtist, lcName );" ); + + query( "CREATE TABLE tracks (" + "id INTEGER PRIMARY KEY AUTOINCREMENT," + "lcName TEXT NOT NULL," + "displayName TEXT NOT NULL," + "primaryArtist INTEGER NOT NULL," + "primaryAlbum INTEGER );" ); + + query( "CREATE UNIQUE INDEX track_names_idx ON tracks ( primaryArtist, lcName );" ); + + query( "CREATE TABLE files (" + "id INTEGER PRIMARY KEY AUTOINCREMENT," + "uri TEXT NOT NULL," + "track INTEGER NOT NULL," + "bitrate INTEGER," + "samplerate INTEGER," + "duration INTEGER," + "filesize INTEGER," + "source INTEGER," + "modificationDate INTEGER," + "lastPlayDate INTEGER," + "playCounter INTEGER," + "mbId VARCHAR( 36 )," + "fpId INTEGER );" ); + + query( "CREATE UNIQUE INDEX files_uri_idx ON files ( uri );" ); + query( "CREATE INDEX files_track_idx ON files ( track );" ); + query( "CREATE INDEX files_fpId_idx ON files ( fpId );" ); + query( "CREATE INDEX files_source_idx ON files ( source );" ); + + query( "CREATE TABLE sources (" + "id INTEGER PRIMARY KEY AUTOINCREMENT," + "name TEXT UNIQUE," + "available INTEGER," + "host TEXT," + "cost INTEGER );" ); + + query( "CREATE TABLE genres (" + "id INTEGER PRIMARY KEY AUTOINCREMENT," + "name TEXT UNIQUE );" ); + + query( "CREATE TABLE labels (" + "id INTEGER PRIMARY KEY AUTOINCREMENT," + "serverUid INTEGER UNIQUE," + "name TEXT );" ); + } + + int const v = version(); + if ( v < k_collectionDbVersion ) + { + qDebug() << "Upgrading Collection::db from" << v << "to" << k_collectionDbVersion; + + /********************************************** + * README!!!!!!! * + * Ensure you use v < x * + * Ensure you do upgrades in ascending order! * + **********************************************/ + + if ( v < 1 ) + { + // Norman discovered that he stored some fpId's wrong prior to 17th December 2007 + // So we have to wipe the fpIds for databases without the metadata table + // we didn't store version information before that, which was a bad decision wasn't it? + + // this will trigger refingerprinting of every track + query( "UPDATE files SET fpId = NULL;" ); + + query( "CREATE TABLE metadata (" + "key TEXT UNIQUE NOT NULL," + "value TEXT );" ); + + query( "INSERT INTO metadata (key, value) VALUES ('version', '1');" ); + } + + + // do last, update DB version number + query( "UPDATE metadata set key='version', value='" + + QString::number( k_collectionDbVersion ) + "';" ); + } +} + + +Collection& //static +Collection::instance() +{ + static QMutex mutex; + QMutexLocker locker( &mutex ); + + if ( !s_instance ) + { + s_instance = new Collection; + qAddPostRoutine(destroy); + } + + return *s_instance; +} + + +void //static +Collection::destroy() +{ + delete s_instance; + QSqlDatabase::removeDatabase( "collection" ); +} + + +int +Collection::version() const +{ + QSqlQuery sql( m_db ); + sql.exec( "SELECT value FROM metadata WHERE key='version';" ); + + if ( sql.next() ) + { + return sql.value( 0 ).toInt(); + } + + return 0; +} + + +bool +Collection::query( const QString& queryToken ) +{ + QSqlQuery query( m_db ); + query.exec( queryToken ); + + if ( query.lastError().isValid() ) + { + qDebug() << "SQL query failed:" << query.lastQuery() << endl + << "SQL error was:" << query.lastError().databaseText() << endl + << "SQL error type:" << query.lastError().type(); + + return false; + } + + return true; +} + + +QString +Collection::fileURI( const QString& filePath ) +{ + QString prefix( "file:/" ); + +#ifdef WIN32 + prefix = "file://"; +#endif + + return prefix + QFileInfo( filePath ).absoluteFilePath(); +} + + +QString +Collection::getFingerprintId( const QString& filePath ) +{ + QSqlQuery query( m_db ); + query.prepare( "SELECT fpId FROM files WHERE uri = :uri" ); + query.bindValue( ":uri", fileURI( filePath ) ); + + query.exec(); + if ( query.lastError().isValid() ) + { + qDebug() << "SQL query failed:" << query.lastQuery() << endl + << "SQL error was:" << query.lastError().databaseText() << endl + << "SQL error type:" << query.lastError().type(); + } + else if (query.next()) + return query.value( 0 ).toString(); + + return ""; +} + + +bool +Collection::setFingerprintId( const QString& filePath, QString fpId ) +{ + bool isNumeric; + int intFpId = fpId.toInt( &isNumeric ); + Q_ASSERT( isNumeric ); + + QSqlQuery query( m_db ); + query.prepare( "REPLACE INTO files ( uri, track, fpId ) VALUES ( :uri, 0, :fpId )" ); + query.bindValue( ":uri", fileURI( filePath ) ); + query.bindValue( ":fpId", intFpId ); + query.exec(); + + if ( query.lastError().isValid() ) + { + qDebug() << "SQL query failed:" << query.lastQuery() << endl + << "SQL error was:" << query.lastError().databaseText() << endl + << "SQL error type:" << query.lastError().type(); + + return false; + } + + return true; +} diff --git a/thirdparty/liblastfm2/src/fingerprint/Collection.h b/thirdparty/liblastfm2/src/fingerprint/Collection.h new file mode 100644 index 000000000..9a1f3bd17 --- /dev/null +++ b/thirdparty/liblastfm2/src/fingerprint/Collection.h @@ -0,0 +1,59 @@ +/* + Copyright 2009 Last.fm Ltd. + - Primarily authored by Max Howell, Jono Cole and Doug Mansell + + This file is part of liblastfm. + + liblastfm 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. + + liblastfm 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 liblastfm. If not, see . +*/ + +/** Class that we use to store fingerprints, basically + */ + +#ifndef COLLECTION_H +#define COLLECTION_H + +#include +#include + + +/** @author: */ +class Collection +{ +public: + static Collection& instance(); + + /** \brief Temp method: Gets a fingerprint id. Returns "" if none found. */ + QString getFingerprintId( const QString& filePath ); + + /** \brief Temp method: Sets a fingerprint id. */ + bool setFingerprintId( const QString& filePath, QString fpId ); + +private: + Collection(); + + /** the database version + * version 0: up until 1.4.1 + * version 1: from 1.4.2 */ + int version() const; + bool query( const QString& queryToken ); + QString fileURI( const QString& filePath ); + + static void destroy(); + + static Collection* s_instance; + QSqlDatabase m_db; +}; + +#endif // COLLECTION_H diff --git a/thirdparty/liblastfm2/src/fingerprint/EXAMPLE.cpp b/thirdparty/liblastfm2/src/fingerprint/EXAMPLE.cpp new file mode 100644 index 000000000..f86d4bbaf --- /dev/null +++ b/thirdparty/liblastfm2/src/fingerprint/EXAMPLE.cpp @@ -0,0 +1,76 @@ +/* + Copyright 2009 Last.fm Ltd. + - Primarily authored by Max Howell, Jono Cole and Doug Mansell + + This file is part of liblastfm. + + liblastfm 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. + + liblastfm 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 liblastfm. If not, see . +*/ +#include +#include +#include +#include + +using namespace lastfm; + + +static void finish( QNetworkReply* reply ) +{ + QEventLoop loop; + loop.connect( reply, SIGNAL(finished()), SLOT(quit()) ); + loop.exec(); +} + + +int main( int argc, char** argv ) +{ + QCoreApplication app( argc, argv ); + + // these fields are required + MutableTrack t; + t.setArtist( "Air" ); + t.setTitle( "Redhead Girl" ); + t.setAlbum( "Pocket Symphony" ); + t.setUrl( QUrl::fromLocalFile( "/Users/mxcl/Music/iTunes/iTunes Music/Air/Pocket Symphony/1-11 Redhead Girl.mp3") ); + + try + { + Fingerprint fp( t ); + + // we cache FingerprintIds in an sqlite3 db, as the generate() function + // is expensive + if (fp.id().isNull()) + { + // this generates the full fingerprint hash, which is about 20kB + fp.generate(); + + // this asks Last.fm for a FingerprintId + // the finish function is a Qt hack to allow syncronous HTTP + finish( fp.submit() ); + + // the decode step sets the FingerprintId + // the FingerprintId is required to obtain suggestions + // id will now be valid, or this function throws + fp.decode( reply ); + } + + finish( fp.id().getSuggestions() ); + + qDebug() << FingerprintId::getSuggestions( reply ); + } + catch (Fingerprint::Error e) + { + qWarning() << e; //TODO enum debug thing + } +} diff --git a/thirdparty/liblastfm2/src/fingerprint/Fingerprint.cpp b/thirdparty/liblastfm2/src/fingerprint/Fingerprint.cpp new file mode 100644 index 000000000..b23805c7d --- /dev/null +++ b/thirdparty/liblastfm2/src/fingerprint/Fingerprint.cpp @@ -0,0 +1,300 @@ +/* + Copyright 2009 Last.fm Ltd. + - Primarily authored by Max Howell, Jono Cole and Doug Mansell + + This file is part of liblastfm. + + liblastfm 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. + + liblastfm 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 liblastfm. If not, see . +*/ + +#include "Fingerprint.h" +#include "FingerprintableSource.h" +#include "Collection.h" +#include "Sha256.h" +#include "fplib/FingerprintExtractor.h" +#include "../ws/ws.h" +#include +#include +#include +#include +#include +#include + +using lastfm::Track; + +static const uint k_bufferSize = 1024 * 8; +static const int k_minTrackDuration = 30; + + +lastfm::Fingerprint::Fingerprint( const Track& t ) + : m_track( t ) + , m_id( -1 ), m_duration( 0 ) + , m_complete( false ) +{ + QString id = Collection::instance().getFingerprintId( t.url().toLocalFile() ); + if (id.size()) { + bool b; + m_id = id.toInt( &b ); + if (!b) m_id = -1; + } +} + + +void +lastfm::Fingerprint::generate( FingerprintableSource* ms ) throw( Error ) +{ + //TODO throw if we can't get required metadata from the track object + +//TODO if (!QFileInfo( path ).isReadable()) +//TODO throw ReadError; + + int sampleRate, bitrate, numChannels; + + if ( !ms ) + throw ReadError; + + try + { + ms->init( m_track.url().toLocalFile() ); + ms->getInfo( m_duration, sampleRate, bitrate, numChannels ); + } + catch (std::exception& e) + { + qWarning() << e.what(); + throw HeadersError; + } + + + if (m_duration < k_minTrackDuration) + throw TrackTooShortError; + + ms->skipSilence(); + + bool fpDone = false; + fingerprint::FingerprintExtractor* extractor; + try + { + extractor = new fingerprint::FingerprintExtractor; + + if (m_complete) + { + extractor->initForFullSubmit( sampleRate, numChannels ); + } + else + { + extractor->initForQuery( sampleRate, numChannels, m_duration ); + + // Skippety skip for as long as the skipper sez (optimisation) + ms->skip( extractor->getToSkipMs() ); + float secsToSkip = extractor->getToSkipMs() / 1000.0f; + fpDone = extractor->process( 0, + (size_t) sampleRate * numChannels * secsToSkip, + false ); + } + } + catch (std::exception& e) + { + qWarning() << e.what(); + throw DecodeError; + } + + const size_t PCMBufSize = 131072; + short* pPCMBuffer = new short[PCMBufSize]; + + while (!fpDone) + { + size_t readData = ms->updateBuffer( pPCMBuffer, PCMBufSize ); + if (readData == 0) + break; + + try + { + fpDone = extractor->process( pPCMBuffer, readData, ms->eof() ); + } + catch ( const std::exception& e ) + { + qWarning() << e.what(); + delete ms; + delete[] pPCMBuffer; + throw InternalError; + } + } + + delete[] pPCMBuffer; + + if (!fpDone) + throw InternalError; + + // We succeeded + std::pair fpData = extractor->getFingerprint(); + + if (fpData.first == NULL || fpData.second == 0) + throw InternalError; + + // Make a deep copy before extractor gets deleted + m_data = QByteArray( fpData.first, fpData.second ); + delete extractor; +} + + +static QString sha256( const QString& path ) +{ + // no clue why this is static, there was no comment when I refactored it + // initially --mxcl + static uint8_t pBuffer[SHA_BUFFER_SIZE+7]; + + unsigned char hash[SHA256_HASH_SIZE]; + + { + QByteArray path8 = QFile::encodeName( path ); + std::ifstream inFile( path8.data(), std::ios::binary); + + SHA256Context sha256; + SHA256Init( &sha256 ); + + uint8_t* pMovableBuffer = pBuffer; + + // Ensure it is on a 64-bit boundary. + INTPTR offs; + if ((offs = reinterpret_cast(pBuffer) & 7L)) + pMovableBuffer += 8 - offs; + + unsigned int len; + + for (;;) + { + inFile.read( reinterpret_cast(pMovableBuffer), SHA_BUFFER_SIZE ); + len = inFile.gcount(); + + if (len == 0) + break; + + SHA256Update( &sha256, pMovableBuffer, len ); + } + + SHA256Final( &sha256, hash ); + } + + QString sha; + for (int i = 0; i < SHA256_HASH_SIZE; ++i) + { + QString hex = QString("%1").arg(uchar(hash[i]), 2, 16, + QChar('0')); + sha.append(hex); + } + + return sha; +} + + +static QByteArray number( uint n ) +{ + return n ? QByteArray::number( n ) : ""; +} + +QNetworkReply* +lastfm::Fingerprint::submit() const +{ + if (m_data.isEmpty()) + return 0; + + //Parameters understood by the server according to the MIR team: + //{ "trackid", "recordingid", "artist", "album", "track", "duration", + // "tracknum", "username", "sha256", "ip", "fpversion", "mbid", + // "filename", "genre", "year", "samplerate", "noupdate", "fulldump" } + + Track const t = m_track; + QString const path = t.url().toLocalFile(); + QFileInfo const fi( path ); + + #define e( x ) QUrl::toPercentEncoding( x ) + QUrl url( "http://www.last.fm/fingerprint/query/" ); + url.addEncodedQueryItem( "artist", e(t.artist()) ); + url.addEncodedQueryItem( "album", e(t.album()) ); + url.addEncodedQueryItem( "track", e(t.title()) ); + url.addEncodedQueryItem( "duration", number( m_duration > 0 ? m_duration : t.duration() ) ); + url.addEncodedQueryItem( "mbid", e(t.mbid()) ); + url.addEncodedQueryItem( "filename", e(fi.completeBaseName()) ); + url.addEncodedQueryItem( "fileextension", e(fi.completeSuffix()) ); + url.addEncodedQueryItem( "tracknum", number( t.trackNumber() ) ); + url.addEncodedQueryItem( "sha256", sha256( path ).toAscii() ); + url.addEncodedQueryItem( "time", number(QDateTime::currentDateTime().toTime_t()) ); + url.addEncodedQueryItem( "fpversion", QByteArray::number((int)fingerprint::FingerprintExtractor::getVersion()) ); + url.addEncodedQueryItem( "fulldump", m_complete ? "true" : "false" ); + url.addEncodedQueryItem( "noupdate", "false" ); + #undef e + + //FIXME: talk to mir about submitting fplibversion + + QNetworkRequest request( url ); + request.setHeader( QNetworkRequest::ContentTypeHeader, "multipart/form-data; boundary=----------------------------8e61d618ca16" ); + + QByteArray bytes; + bytes += "------------------------------8e61d618ca16\r\n"; + bytes += "Content-Disposition: "; + bytes += "form-data; name=\"fpdata\""; + bytes += "\r\n\r\n"; + bytes += m_data; + bytes += "\r\n"; + bytes += "------------------------------8e61d618ca16--\r\n"; + + qDebug() << url; + qDebug() << "Fingerprint size:" << bytes.size() << "bytes"; + + return lastfm::nam()->post( request, bytes ); +} + + +void +lastfm::Fingerprint::decode( QNetworkReply* reply, bool* complete_fingerprint_requested ) throw( Error ) +{ + // The response data will consist of a number and a string. + // The number is the fpid and the string is either FOUND or NEW + // (or NOT FOUND when noupdate was used). NEW means we should + // schedule a full fingerprint. + // + // In the case of an error, there will be no initial number, just + // an error string. + + QString const response( reply->readAll() ); + QStringList const list = response.split( ' ' ); + + QString const fpid = list.value( 0 ); + QString const status = list.value( 1 ); + + if (response.isEmpty() || list.count() < 2 || response == "No response to client error") + goto bad_response; + if (list.count() != 2) + qWarning() << "Response looks bad but continuing anyway:" << response; + + { + // so variables go out of scope before jump to label + // otherwise compiler error on GCC 4.2 + bool b; + uint fpid_as_uint = fpid.toUInt( &b ); + if (!b) goto bad_response; + + Collection::instance().setFingerprintId( m_track.url().toLocalFile(), fpid ); + + if (complete_fingerprint_requested) + *complete_fingerprint_requested = (status == "NEW"); + + m_id = (int)fpid_as_uint; + return; + } + +bad_response: + qWarning() << "Response is bad:" << response; + throw BadResponseError; +} diff --git a/thirdparty/liblastfm2/src/fingerprint/Fingerprint.h b/thirdparty/liblastfm2/src/fingerprint/Fingerprint.h new file mode 100644 index 000000000..b793c4bb9 --- /dev/null +++ b/thirdparty/liblastfm2/src/fingerprint/Fingerprint.h @@ -0,0 +1,116 @@ +/* + Copyright 2009 Last.fm Ltd. + - Primarily authored by Max Howell, Jono Cole and Doug Mansell + + This file is part of liblastfm. + + liblastfm 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. + + liblastfm 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 liblastfm. If not, see . +*/ +#ifndef LASTFM_FINGERPRINT_H +#define LASTFM_FINGERPRINT_H + +#include +#include + + +namespace lastfm +{ + class LASTFM_FINGERPRINT_DLLEXPORT Fingerprint + { + lastfm::Track m_track; + QByteArray m_data; + int m_id; + int m_duration; + + protected: + bool m_complete; + + public: + /** represents a partial fingerprint of 20 seconds of music, this is + * considered 99.9999...9999% unique and so we use it for most stuff as + * it is much quicker than a complete fingerprint, still though, you + * should do the generate step in a thread. */ + Fingerprint( const lastfm::Track& ); + + /** if the id isNull(), then you'll need to do generate, submit and decode */ + FingerprintId id() const { return m_id; } + + /** The actual data that is the fingerprint, this is about 70kB or so, + * there isn't anything in it until you call generate. */ + QByteArray data() const { return m_data; } + + enum Error + { + ReadError = 0, + + /** failed to extract samplerate, bitrate, channels, duration etc */ + HeadersError, + + DecodeError, + + /** there is a minimum track duration for fingerprinting */ + TrackTooShortError, + + /** the fingerprint service went wrong, or we submitted bad data, + * or myabe the request failed, whatever, we couldn't parse the + * result */ + BadResponseError, + + /** sorry, liblastfm sucks, report bug with log! */ + InternalError + }; + + /** This is CPU intensive, do it in a thread in your GUI application */ + void generate( FingerprintableSource* ) throw( Error ); + + /** Submits the fingerprint data to Last.fm in order to get a FingerprintId + * back. You need to wait for the QNetworkReply to finish before you can + * pass it to decode clearly. */ + QNetworkReply* submit() const; + + /** Pass a finished reply from submit(), if the response is sound, id() + * will be valid. Otherwise we will throw. You always get a valid id + * or a throw. + */ + void decode( QNetworkReply*, bool* lastfm_needs_a_complete_fingerprint = 0 ) throw( Error ); + }; + + + class CompleteFingerprint : public Fingerprint + { + public: + CompleteFingerprint( const lastfm::Track& t ) : Fingerprint( t ) + { + m_complete = true; + } + }; +} + + +inline QDebug operator<<( QDebug d, lastfm::Fingerprint::Error e ) +{ + #define CASE(x) case lastfm::Fingerprint::x: return d << #x; + switch (e) + { + CASE(ReadError) + CASE(HeadersError) + CASE(DecodeError) + CASE(TrackTooShortError) + CASE(BadResponseError) + CASE(InternalError) + } + #undef CASE +} + +#endif diff --git a/thirdparty/liblastfm2/src/fingerprint/FingerprintableSource.h b/thirdparty/liblastfm2/src/fingerprint/FingerprintableSource.h new file mode 100644 index 000000000..9954fd368 --- /dev/null +++ b/thirdparty/liblastfm2/src/fingerprint/FingerprintableSource.h @@ -0,0 +1,48 @@ +/* + Copyright 2009 Last.fm Ltd. + Copyright 2009 John Stamp + + This file is part of liblastfm. + + liblastfm 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. + + liblastfm 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 liblastfm. If not, see . +*/ + +#ifndef LASTFM_FINGERPRINTABLE_SOURCE_H +#define LASTFM_FINGERPRINTABLE_SOURCE_H + +#include +#include + +namespace lastfm +{ + class LASTFM_FINGERPRINT_DLLEXPORT FingerprintableSource + { + public: + /** do all initialisation here and throw if there is problems */ + virtual void init( const QString& path ) = 0; + + virtual void getInfo( int& lengthSecs, int& samplerate, int& bitrate, int& nchannels ) = 0; + + /** put a chunk of PCM data in pBuffer, don't exceed size, return the + * number of bytes you put in the buffer */ + virtual int updateBuffer( signed short* buffer, size_t bufferSize ) = 0; + + virtual void skip( const int mSecs ) = 0; + virtual void skipSilence( double silenceThreshold = 0.0001 ) = 0; + + virtual bool eof() const = 0; + }; +} + +#endif diff --git a/thirdparty/liblastfm2/src/fingerprint/Sha256.cpp b/thirdparty/liblastfm2/src/fingerprint/Sha256.cpp new file mode 100644 index 000000000..9be3c18ac --- /dev/null +++ b/thirdparty/liblastfm2/src/fingerprint/Sha256.cpp @@ -0,0 +1,480 @@ +/*- + * Copyright (c) 2001-2003 Allan Saddi + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * THIS SOFTWARE IS PROVIDED BY ALLAN SADDI AND HIS CONTRIBUTORS ``AS IS'' + * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + * ARE DISCLAIMED. IN NO EVENT SHALL ALLAN SADDI OR HIS CONTRIBUTORS BE + * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR + * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF + * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS + * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN + * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) + * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE + * POSSIBILITY OF SUCH DAMAGE. + * + * $Id: sha256.c 680 2003-07-25 21:57:49Z asaddi $ + */ + +/* + * Define WORDS_BIGENDIAN if compiling on a big-endian architecture. + * + * Define SHA256_TEST to test the implementation using the NIST's + * sample messages. The output should be: + * + * ba7816bf 8f01cfea 414140de 5dae2223 b00361a3 96177a9c b410ff61 f20015ad + * 248d6a61 d20638b8 e5c02693 0c3e6039 a33ce459 64ff2167 f6ecedd4 19db06c1 + * cdc76e5c 9914fb92 81a1c7e2 84d73e67 f1809a48 a497200e 046d39cc c7112cd0 + */ + +#ifdef HAVE_CONFIG_H +#include +#endif /* HAVE_CONFIG_H */ + +#if HAVE_INTTYPES_H +# include +#else +# if HAVE_STDINT_H +# include +# endif +#endif + +#include + +#include "Sha256.h" + +#ifndef lint +static const char rcsid[] = + "$Id: sha256.c 680 2003-07-25 21:57:49Z asaddi $"; +#endif /* !lint */ + +#define ROTL(x, n) (((x) << (n)) | ((x) >> (32 - (n)))) +#define ROTR(x, n) (((x) >> (n)) | ((x) << (32 - (n)))) + +#define Ch(x, y, z) ((z) ^ ((x) & ((y) ^ (z)))) +#define Maj(x, y, z) (((x) & ((y) | (z))) | ((y) & (z))) +#define SIGMA0(x) (ROTR((x), 2) ^ ROTR((x), 13) ^ ROTR((x), 22)) +#define SIGMA1(x) (ROTR((x), 6) ^ ROTR((x), 11) ^ ROTR((x), 25)) +#define sigma0(x) (ROTR((x), 7) ^ ROTR((x), 18) ^ ((x) >> 3)) +#define sigma1(x) (ROTR((x), 17) ^ ROTR((x), 19) ^ ((x) >> 10)) + +#define DO_ROUND() { \ + t1 = h + SIGMA1(e) + Ch(e, f, g) + *(Kp++) + *(W++); \ + t2 = SIGMA0(a) + Maj(a, b, c); \ + h = g; \ + g = f; \ + f = e; \ + e = d + t1; \ + d = c; \ + c = b; \ + b = a; \ + a = t1 + t2; \ +} + +static const uint32_t K[64] = { + 0x428a2f98L, 0x71374491L, 0xb5c0fbcfL, 0xe9b5dba5L, + 0x3956c25bL, 0x59f111f1L, 0x923f82a4L, 0xab1c5ed5L, + 0xd807aa98L, 0x12835b01L, 0x243185beL, 0x550c7dc3L, + 0x72be5d74L, 0x80deb1feL, 0x9bdc06a7L, 0xc19bf174L, + 0xe49b69c1L, 0xefbe4786L, 0x0fc19dc6L, 0x240ca1ccL, + 0x2de92c6fL, 0x4a7484aaL, 0x5cb0a9dcL, 0x76f988daL, + 0x983e5152L, 0xa831c66dL, 0xb00327c8L, 0xbf597fc7L, + 0xc6e00bf3L, 0xd5a79147L, 0x06ca6351L, 0x14292967L, + 0x27b70a85L, 0x2e1b2138L, 0x4d2c6dfcL, 0x53380d13L, + 0x650a7354L, 0x766a0abbL, 0x81c2c92eL, 0x92722c85L, + 0xa2bfe8a1L, 0xa81a664bL, 0xc24b8b70L, 0xc76c51a3L, + 0xd192e819L, 0xd6990624L, 0xf40e3585L, 0x106aa070L, + 0x19a4c116L, 0x1e376c08L, 0x2748774cL, 0x34b0bcb5L, + 0x391c0cb3L, 0x4ed8aa4aL, 0x5b9cca4fL, 0x682e6ff3L, + 0x748f82eeL, 0x78a5636fL, 0x84c87814L, 0x8cc70208L, + 0x90befffaL, 0xa4506cebL, 0xbef9a3f7L, 0xc67178f2L +}; + +#ifndef RUNTIME_ENDIAN + +#ifdef WORDS_BIGENDIAN + +#define BYTESWAP(x) (x) +#define BYTESWAP64(x) (x) + +#else /* WORDS_BIGENDIAN */ + +#define BYTESWAP(x) ((ROTR((x), 8) & 0xff00ff00L) | \ + (ROTL((x), 8) & 0x00ff00ffL)) +#define BYTESWAP64(x) _byteswap64(x) + +static inline uint64_t _byteswap64(uint64_t x) +{ + uint32_t a = x >> 32; + uint32_t b = (uint32_t) x; + return ((uint64_t) BYTESWAP(b) << 32) | (uint64_t) BYTESWAP(a); +} + +#endif /* WORDS_BIGENDIAN */ + +#else /* !RUNTIME_ENDIAN */ + +#define BYTESWAP(x) _byteswap(sc->littleEndian, x) +#define BYTESWAP64(x) _byteswap64(sc->littleEndian, x) + +#define _BYTESWAP(x) ((ROTR((x), 8) & 0xff00ff00L) | \ + (ROTL((x), 8) & 0x00ff00ffL)) +#define _BYTESWAP64(x) __byteswap64(x) + +static inline uint64_t __byteswap64(uint64_t x) +{ + uint32_t a = x >> 32; + uint32_t b = (uint32_t) x; + return ((uint64_t) _BYTESWAP(b) << 32) | (uint64_t) _BYTESWAP(a); +} + +static inline uint32_t _byteswap(int littleEndian, uint32_t x) +{ + if (!littleEndian) + return x; + else + return _BYTESWAP(x); +} + +static inline uint64_t _byteswap64(int littleEndian, uint64_t x) +{ + if (!littleEndian) + return x; + else + return _BYTESWAP64(x); +} + +static inline void setEndian(int *littleEndianp) +{ + union { + uint32_t w; + uint8_t b[4]; + } endian; + + endian.w = 1L; + *littleEndianp = endian.b[0] != 0; +} + +#endif /* !RUNTIME_ENDIAN */ + +static const uint8_t padding[64] = { + 0x80, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00 +}; + +void +SHA256Init (SHA256Context *sc) +{ +#ifdef RUNTIME_ENDIAN + setEndian (&sc->littleEndian); +#endif /* RUNTIME_ENDIAN */ + + sc->totalLength = 0LL; + sc->hash[0] = 0x6a09e667L; + sc->hash[1] = 0xbb67ae85L; + sc->hash[2] = 0x3c6ef372L; + sc->hash[3] = 0xa54ff53aL; + sc->hash[4] = 0x510e527fL; + sc->hash[5] = 0x9b05688cL; + sc->hash[6] = 0x1f83d9abL; + sc->hash[7] = 0x5be0cd19L; + sc->bufferLength = 0L; +} + +static void +burnStack (int size) +{ + char buf[128]; + + memset (buf, 0, sizeof (buf)); + size -= sizeof (buf); + if (size > 0) + burnStack (size); +} + +static void +SHA256Guts (SHA256Context *sc, const uint32_t *cbuf) +{ + uint32_t buf[64]; + uint32_t *W, *W2, *W7, *W15, *W16; + uint32_t a, b, c, d, e, f, g, h; + uint32_t t1, t2; + const uint32_t *Kp; + int i; + + W = buf; + + for (i = 15; i >= 0; i--) { + *(W++) = BYTESWAP(*cbuf); + cbuf++; + } + + W16 = &buf[0]; + W15 = &buf[1]; + W7 = &buf[9]; + W2 = &buf[14]; + + for (i = 47; i >= 0; i--) { + *(W++) = sigma1(*W2) + *(W7++) + sigma0(*W15) + *(W16++); + W2++; + W15++; + } + + a = sc->hash[0]; + b = sc->hash[1]; + c = sc->hash[2]; + d = sc->hash[3]; + e = sc->hash[4]; + f = sc->hash[5]; + g = sc->hash[6]; + h = sc->hash[7]; + + Kp = K; + W = buf; + +#ifndef SHA256_UNROLL +#define SHA256_UNROLL 1 +#endif /* !SHA256_UNROLL */ + +#if SHA256_UNROLL == 1 + for (i = 63; i >= 0; i--) + DO_ROUND(); +#elif SHA256_UNROLL == 2 + for (i = 31; i >= 0; i--) { + DO_ROUND(); DO_ROUND(); + } +#elif SHA256_UNROLL == 4 + for (i = 15; i >= 0; i--) { + DO_ROUND(); DO_ROUND(); DO_ROUND(); DO_ROUND(); + } +#elif SHA256_UNROLL == 8 + for (i = 7; i >= 0; i--) { + DO_ROUND(); DO_ROUND(); DO_ROUND(); DO_ROUND(); + DO_ROUND(); DO_ROUND(); DO_ROUND(); DO_ROUND(); + } +#elif SHA256_UNROLL == 16 + for (i = 3; i >= 0; i--) { + DO_ROUND(); DO_ROUND(); DO_ROUND(); DO_ROUND(); + DO_ROUND(); DO_ROUND(); DO_ROUND(); DO_ROUND(); + DO_ROUND(); DO_ROUND(); DO_ROUND(); DO_ROUND(); + DO_ROUND(); DO_ROUND(); DO_ROUND(); DO_ROUND(); + } +#elif SHA256_UNROLL == 32 + for (i = 1; i >= 0; i--) { + DO_ROUND(); DO_ROUND(); DO_ROUND(); DO_ROUND(); + DO_ROUND(); DO_ROUND(); DO_ROUND(); DO_ROUND(); + DO_ROUND(); DO_ROUND(); DO_ROUND(); DO_ROUND(); + DO_ROUND(); DO_ROUND(); DO_ROUND(); DO_ROUND(); + DO_ROUND(); DO_ROUND(); DO_ROUND(); DO_ROUND(); + DO_ROUND(); DO_ROUND(); DO_ROUND(); DO_ROUND(); + DO_ROUND(); DO_ROUND(); DO_ROUND(); DO_ROUND(); + DO_ROUND(); DO_ROUND(); DO_ROUND(); DO_ROUND(); + } +#elif SHA256_UNROLL == 64 + DO_ROUND(); DO_ROUND(); DO_ROUND(); DO_ROUND(); + DO_ROUND(); DO_ROUND(); DO_ROUND(); DO_ROUND(); + DO_ROUND(); DO_ROUND(); DO_ROUND(); DO_ROUND(); + DO_ROUND(); DO_ROUND(); DO_ROUND(); DO_ROUND(); + DO_ROUND(); DO_ROUND(); DO_ROUND(); DO_ROUND(); + DO_ROUND(); DO_ROUND(); DO_ROUND(); DO_ROUND(); + DO_ROUND(); DO_ROUND(); DO_ROUND(); DO_ROUND(); + DO_ROUND(); DO_ROUND(); DO_ROUND(); DO_ROUND(); + DO_ROUND(); DO_ROUND(); DO_ROUND(); DO_ROUND(); + DO_ROUND(); DO_ROUND(); DO_ROUND(); DO_ROUND(); + DO_ROUND(); DO_ROUND(); DO_ROUND(); DO_ROUND(); + DO_ROUND(); DO_ROUND(); DO_ROUND(); DO_ROUND(); + DO_ROUND(); DO_ROUND(); DO_ROUND(); DO_ROUND(); + DO_ROUND(); DO_ROUND(); DO_ROUND(); DO_ROUND(); + DO_ROUND(); DO_ROUND(); DO_ROUND(); DO_ROUND(); + DO_ROUND(); DO_ROUND(); DO_ROUND(); DO_ROUND(); +#else +#error "SHA256_UNROLL must be 1, 2, 4, 8, 16, 32, or 64!" +#endif + + sc->hash[0] += a; + sc->hash[1] += b; + sc->hash[2] += c; + sc->hash[3] += d; + sc->hash[4] += e; + sc->hash[5] += f; + sc->hash[6] += g; + sc->hash[7] += h; +} + +void +SHA256Update (SHA256Context *sc, const void *vdata, uint32_t len) +{ + const uint8_t *data = (const uint8_t*)vdata; + uint32_t bufferBytesLeft; + uint32_t bytesToCopy; + int needBurn = 0; + +#ifdef SHA256_FAST_COPY + if (sc->bufferLength) { + bufferBytesLeft = 64L - sc->bufferLength; + + bytesToCopy = bufferBytesLeft; + if (bytesToCopy > len) + bytesToCopy = len; + + memcpy (&sc->buffer.bytes[sc->bufferLength], data, bytesToCopy); + + sc->totalLength += bytesToCopy * 8L; + + sc->bufferLength += bytesToCopy; + data += bytesToCopy; + len -= bytesToCopy; + + if (sc->bufferLength == 64L) { + SHA256Guts (sc, sc->buffer.words); + needBurn = 1; + sc->bufferLength = 0L; + } + } + + while (len > 63L) { + sc->totalLength += 512L; + + SHA256Guts (sc, data); + needBurn = 1; + + data += 64L; + len -= 64L; + } + + if (len) { + memcpy (&sc->buffer.bytes[sc->bufferLength], data, len); + + sc->totalLength += len * 8L; + + sc->bufferLength += len; + } +#else /* SHA256_FAST_COPY */ + while (len) { + bufferBytesLeft = 64L - sc->bufferLength; + + bytesToCopy = bufferBytesLeft; + if (bytesToCopy > len) + bytesToCopy = len; + + memcpy (&sc->buffer.bytes[sc->bufferLength], data, bytesToCopy); + + sc->totalLength += bytesToCopy * 8L; + + sc->bufferLength += bytesToCopy; + data += bytesToCopy; + len -= bytesToCopy; + + if (sc->bufferLength == 64L) { + SHA256Guts (sc, sc->buffer.words); + needBurn = 1; + sc->bufferLength = 0L; + } + } +#endif /* SHA256_FAST_COPY */ + + if (needBurn) + burnStack (sizeof (uint32_t[74]) + sizeof (uint32_t *[6]) + sizeof (int)); +} + +void +SHA256Final (SHA256Context *sc, uint8_t hash[SHA256_HASH_SIZE]) +{ + uint32_t bytesToPad; + uint64_t lengthPad; + int i; + + bytesToPad = 120L - sc->bufferLength; + if (bytesToPad > 64L) + bytesToPad -= 64L; + + lengthPad = BYTESWAP64(sc->totalLength); + + SHA256Update (sc, padding, bytesToPad); + SHA256Update (sc, &lengthPad, 8L); + + if (hash) { + for (i = 0; i < SHA256_HASH_WORDS; i++) { +#ifdef SHA256_FAST_COPY + *((uint32_t *) hash) = BYTESWAP(sc->hash[i]); +#else /* SHA256_FAST_COPY */ + hash[0] = (uint8_t) (sc->hash[i] >> 24); + hash[1] = (uint8_t) (sc->hash[i] >> 16); + hash[2] = (uint8_t) (sc->hash[i] >> 8); + hash[3] = (uint8_t) sc->hash[i]; +#endif /* SHA256_FAST_COPY */ + hash += 4; + } + } +} + +#ifdef SHA256_TEST + +#include +#include + +int +main (int argc, char *argv[]) +{ + SHA256Context foo; + uint8_t hash[SHA256_HASH_SIZE]; + char buf[1000]; + int i; + + SHA256Init (&foo); + SHA256Update (&foo, "abc", 3); + SHA256Final (&foo, hash); + + for (i = 0; i < SHA256_HASH_SIZE;) { + printf ("%02x", hash[i++]); + if (!(i % 4)) + printf (" "); + } + printf ("\n"); + + SHA256Init (&foo); + SHA256Update (&foo, + "abcdbcdecdefdefgefghfghighijhijkijkljklmklmnlmnomnopnopq", + 56); + SHA256Final (&foo, hash); + + for (i = 0; i < SHA256_HASH_SIZE;) { + printf ("%02x", hash[i++]); + if (!(i % 4)) + printf (" "); + } + printf ("\n"); + + SHA256Init (&foo); + memset (buf, 'a', sizeof (buf)); + for (i = 0; i < 1000; i++) + SHA256Update (&foo, buf, sizeof (buf)); + SHA256Final (&foo, hash); + + for (i = 0; i < SHA256_HASH_SIZE;) { + printf ("%02x", hash[i++]); + if (!(i % 4)) + printf (" "); + } + printf ("\n"); + + exit (0); +} + +#endif /* SHA256_TEST */ diff --git a/thirdparty/liblastfm2/src/fingerprint/Sha256.h b/thirdparty/liblastfm2/src/fingerprint/Sha256.h new file mode 100644 index 000000000..433c8f9a6 --- /dev/null +++ b/thirdparty/liblastfm2/src/fingerprint/Sha256.h @@ -0,0 +1,180 @@ +/*- + * Copyright (c) 2001-2003 Allan Saddi + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * THIS SOFTWARE IS PROVIDED BY ALLAN SADDI AND HIS CONTRIBUTORS ``AS IS'' + * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + * ARE DISCLAIMED. IN NO EVENT SHALL ALLAN SADDI OR HIS CONTRIBUTORS BE + * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR + * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF + * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS + * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN + * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) + * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE + * POSSIBILITY OF SUCH DAMAGE. + * + * $Id: sha256.h 348 2003-02-23 22:12:06Z asaddi $ + */ +// +/////////// EXAMPLE ///////////////////////////////// +// +// SHA256Context sha256; +// SHA256Init (&sha256); +// +// uint8_t* pBuffer = new uint8_t[SHA_BUFFER_SIZE + 7]; +// // Ensure it is on a 64-bit boundary. +// INTPTR offs; +// if ((offs = reinterpret_cast(pBuffer) & 7L)) +// pBuffer += 8 - offs; +// +// unsigned int len; +// +// ifstream inFile("test.txt", ios::binary); +// +// for (;;) +// { +// inFile.read( reinterpret_cast(pBuffer), SHA_BUFFER_SIZE ); +// len = inFile.gcount(); +// +// if ( len == 0) +// break; +// +// SHA256Update (&sha256, pBuffer, len); +// } +// +// uint8_t hash[SHA256_HASH_SIZE]; +// SHA256Final (&sha256, hash); +// +// cout << "Hash: "; +// for (int i = 0; i < SHA256_HASH_SIZE; ++i) +// printf ("%02x", hash[i]); +// cout << endl; + + +#ifndef _SHA256_H +#define _SHA256_H + +// ---------------------------------------------------------------------------- +// ---------------------------------------------------------------------------- + +/* Define to 1 if you have the header file. */ +#ifndef WIN32 +#define HAVE_INTTYPES_H 1 +#endif + +/* Define to 1 if you have the header file. */ +#define HAVE_MEMORY_H 1 + +/* Define to 1 if you have the header file. */ +#ifndef WIN32 +#define HAVE_STDINT_H 1 +#endif + +/* Define to 1 if you have the header file. */ +#define HAVE_STDLIB_H 1 + +/* Define to 1 if you have the header file. */ +#define HAVE_STRINGS_H 1 + +/* Define to 1 if you have the header file. */ +#define HAVE_STRING_H 1 + +/* Define to 1 if you have the `strerror' function. */ +#ifndef WIN32 +#define HAVE_STRERROR 1 +#endif + +/* Define to 1 if you have the header file. */ +#define HAVE_SYS_STAT_H 1 + +/* Define to 1 if you have the header file. */ +#ifndef WIN32 +#define HAVE_SYS_TYPES_H 1 +#endif + +/* Define to 1 if you have the header file. */ +#ifndef WIN32 +#define HAVE_UNISTD_H 1 +#endif + +/* Define to 1 if you have the ANSI C header files. */ +#define STDC_HEADERS 1 + +/* Define as `__inline' if that's what the C compiler calls it, or to nothing + if it is not supported. */ +#ifdef WIN32 +#define inline __inline +#endif + +/* Define to `unsigned' if does not define. */ +/* #undef size_t */ + +#ifdef WIN32 +#define uint64_t unsigned __int64 +#define uint32_t unsigned int +#define uint8_t unsigned char +#endif // WIN32 + +#ifdef WIN32 +#define INTPTR intptr_t +#else +#define INTPTR long +#endif + +#define SHA_BUFFER_SIZE 65536 + +// ---------------------------------------------------------------------------- +// ---------------------------------------------------------------------------- + + +#if HAVE_INTTYPES_H +# include +#else +# if HAVE_STDINT_H +# include +# endif +#endif + +#define SHA256_HASH_SIZE 32 + +/* Hash size in 32-bit words */ +#define SHA256_HASH_WORDS 8 + +struct _SHA256Context { + uint64_t totalLength; + uint32_t hash[SHA256_HASH_WORDS]; + uint32_t bufferLength; + union { + uint32_t words[16]; + uint8_t bytes[64]; + } buffer; +#ifdef RUNTIME_ENDIAN + int littleEndian; +#endif /* RUNTIME_ENDIAN */ +}; + +typedef struct _SHA256Context SHA256Context; + +#ifdef __cplusplus +extern "C" { +#endif + +void SHA256Init (SHA256Context *sc); +void SHA256Update (SHA256Context *sc, const void *data, uint32_t len); +void SHA256Final (SHA256Context *sc, uint8_t hash[SHA256_HASH_SIZE]); + +#ifdef __cplusplus +} +#endif + +#endif /* !_SHA256_H */ diff --git a/thirdparty/liblastfm2/src/fingerprint/contrib/AacSource.cpp b/thirdparty/liblastfm2/src/fingerprint/contrib/AacSource.cpp new file mode 100644 index 000000000..77bacd343 --- /dev/null +++ b/thirdparty/liblastfm2/src/fingerprint/contrib/AacSource.cpp @@ -0,0 +1,953 @@ +/* + Copyright 2009 Last.fm Ltd. + Copyright 2009 John Stamp + Portions Copyright 2003-2005 M. Bakker, Nero AG, http://www.nero.com + - Adapted from main.c found in the FAAD2 source tarball. + + This file is part of liblastfm. + + liblastfm 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. + + liblastfm 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 liblastfm. If not, see . +*/ +#include "AacSource.h" +#include "AacSource_p.h" + +#include +#include +#include +#include +#include +#include +#include +#include + + +//////////////////////////////////////////////////////////////////////// +// +// AAC_File +// +//////////////////////////////////////////////////////////////////////// +AAC_File::AAC_File(const QString& fileName, int headerType) + : m_fileName(fileName) + , m_inBuf(NULL) + , m_inBufSize(0) + , m_decoder(0) + , m_overflow(static_cast(malloc( sizeof(unsigned char) * 1024 ))) + , m_overflowSize(0) + , m_header(headerType) +{ +} + + +AAC_File::~AAC_File() +{ + // common + if ( m_decoder ) + { + NeAACDecClose( m_decoder ); + m_decoder = NULL; + } + if ( m_inBuf ) + { + free( m_inBuf ); + m_inBufSize = 0; + m_inBuf = NULL; + } + if ( m_overflow ) + { + free( m_overflow ); + m_overflowSize = 0; + m_overflow = NULL; + } +} + + + +//////////////////////////////////////////////////////////////////////// +// +// AAC with ADTS or ADIF headers +// +//////////////////////////////////////////////////////////////////////// + + +#define MAX_CHANNELS 6 // Output will get mixed down to 2 channels +#define ADTS_HEADER_SIZE 8 + +static int adts_sample_rates[] = +{ + 96000, + 88200, + 64000, + 48000, + 44100, + 32000, + 24000, + 22050, + 16000, + 12000, + 11025, + 8000, + 7350, + 0, + 0, + 0 +}; + +AAC_ADTS_File::AAC_ADTS_File( const QString& fileName, int headerType ) : AAC_File(fileName, headerType) + , m_file( NULL ) + , m_adifSamplerate( 0 ) + , m_adifChannels( 0 ) +{ +} + + +AAC_ADTS_File::~AAC_ADTS_File() +{ + if ( m_file ) + { + fclose( m_file ); + } +} + + +void AAC_ADTS_File::fillBuffer( FILE*& fp, unsigned char*& buf, size_t& bufSize, const size_t bytesConsumed ) +{ + size_t bread; + + if ( bytesConsumed > 0 ) + { + if ( bufSize ) + memmove( (void*)buf, (void*)(buf + bytesConsumed), bufSize*sizeof(unsigned char) ); + + bread = fread( (void*)(buf + bufSize), 1, bytesConsumed, fp ); + bufSize += bread; + + if ( bufSize > 3 ) + { + if ( memcmp( buf, "TAG", 3 ) == 0 ) + bufSize = 0; + } + if ( bufSize > 11 ) + { + if ( memcmp( buf, "LYRICSBEGIN", 11 ) == 0 ) + bufSize = 0; + } + if ( bufSize > 8 ) + { + if ( memcmp( buf, "APETAGEX", 8 ) == 0 ) + bufSize = 0; + } + } +} + + +void AAC_ADTS_File::parse( FILE*& fp, unsigned char*& buf, size_t& bufSize, int &bitrate, double &length ) +{ + unsigned int frames, frame_length = 0; + int t_framelength = 0; + int samplerate = 0; + double frames_per_sec, bytes_per_frame; + + // Read all frames to ensure correct time and bitrate + for ( frames = 0; /* */; frames++ ) + { + fillBuffer( fp, buf, bufSize, frame_length ); + + if ( bufSize > 7 ) + { + /* check syncword */ + if ( !( (buf[0] == 0xFF) && ((buf[1] & 0xF6) == 0xF0) ) ) + break; + + if ( frames == 0 ) + samplerate = adts_sample_rates[ (buf[2] & 0x3c) >> 2 ]; + + frame_length = ( ((buf[3] & 0x3) << 11) + | ((buf[4]) << 3) + | (buf[5] >> 5) ); + + t_framelength += frame_length - ADTS_HEADER_SIZE; + + if ( frame_length > bufSize ) + break; + + bufSize -= frame_length; + } + else + { + break; + } + } + + frames_per_sec = samplerate / 1024.0; + + if ( frames != 0 ) + bytes_per_frame = t_framelength / frames; + else + bytes_per_frame = 0; + + bitrate = static_cast(8 * bytes_per_frame * frames_per_sec + 0.5); + + if ( frames_per_sec != 0 ) + length = frames / frames_per_sec; + else + length = 1; +} + + +int32_t AAC_ADTS_File::commonSetup( FILE*& fp, NeAACDecHandle& decoder, unsigned char*& buf, size_t& bufSize, uint32_t& samplerate, uint8_t& channels ) +{ + samplerate = 0; + channels = 0; + + fp = fopen(QFile::encodeName(m_fileName), "rb" ); + if( !fp ) + { + std::cerr << "ERROR: Failed to open " << strerror( errno ) << std::endl; + return -1; + } + + if ( !(buf = static_cast( malloc(FAAD_MIN_STREAMSIZE*MAX_CHANNELS)) ) ) + { + std::cerr << "Memory allocation error" << std::endl; + fclose ( fp ); + return -1; + } + + memset( buf, 0, FAAD_MIN_STREAMSIZE*MAX_CHANNELS ); + + bufSize = fread( buf, 1, FAAD_MIN_STREAMSIZE * MAX_CHANNELS, fp ); + + int tagsize = 0; + if ( !memcmp( buf, "ID3", 3 ) ) + { + /* high bit is not used */ + tagsize = (buf[6] << 21) | (buf[7] << 14) | + (buf[8] << 7) | (buf[9] << 0); + + tagsize += 10; + bufSize -= tagsize; + fillBuffer( fp, buf, bufSize, tagsize ); + } + + decoder = NeAACDecOpen(); + + /* Set configuration */ + NeAACDecConfigurationPtr config; + config = NeAACDecGetCurrentConfiguration(decoder); + config->outputFormat = FAAD_FMT_16BIT; + config->downMatrix = 1; // Turn 5.1 channels into 2 + NeAACDecSetConfiguration( decoder, config); + + int32_t initval = 0; + if ((initval = NeAACDecInit(decoder, buf, + FAAD_MIN_STREAMSIZE*MAX_CHANNELS, &samplerate, &channels)) < 0) + { + std::cerr << "Error: could not set up AAC decoder" << std::endl; + if ( buf ) + free( buf ); + buf = NULL; + NeAACDecClose( decoder ); + decoder = NULL; + fclose( fp ); + fp = NULL; + } + return initval; +} + + +bool AAC_ADTS_File::init() +{ + uint32_t initSamplerate = 0; + uint8_t initChannels = 0; + int32_t initval = commonSetup( m_file, m_decoder, m_inBuf, m_inBufSize, initSamplerate, initChannels ); + + if ( initval >= 0 ) + { + m_inBufSize -= initval; + fillBuffer( m_file, m_inBuf, m_inBufSize, initval ); + + // These two only needed for skipping AAC ADIF files + m_adifSamplerate = initSamplerate; + m_adifChannels = initChannels; + + return true; + } + + throw std::runtime_error( "ERROR: Could not initialize AAC file reader!" ); + return false; +} + + +/*QString AAC_ADTS_File::getMbid() +{ + char out[MBID_BUFFER_SIZE]; + int const r = getMP3_MBID(QFile::encodeName(m_fileName), out); + if ( r == 0 ) + return QString::fromLatin1( out ); + return QString(); +}*/ + +void AAC_ADTS_File::getInfo( int& lengthSecs, int& samplerate, int& bitrate, int& nchannels ) +{ + long fileread; + uint32_t initSamplerate; + uint8_t initChannels; + double initLength = 0; + unsigned char *tempBuf = NULL; + size_t tempBufSize; + FILE *fp = NULL; + NeAACDecHandle decoder = NULL; + commonSetup( fp, decoder, tempBuf, tempBufSize, initSamplerate, initChannels ); + + long origpos = ftell( fp ); + fseek( fp, 0, SEEK_END ); + fileread = ftell( fp ); + fseek( fp, origpos, SEEK_SET ); + + if ( (tempBuf[0] == 0xFF) && ((tempBuf[1] & 0xF6) == 0xF0) ) + { + parse( fp, tempBuf, tempBufSize, bitrate, initLength ); + } + else if (memcmp(tempBuf, "ADIF", 4) == 0) + { + int skip_size = (tempBuf[4] & 0x80) ? 9 : 0; + bitrate = ((tempBuf[4 + skip_size] & 0x0F)<<19) | + (tempBuf[5 + skip_size]<<11) | + (tempBuf[6 + skip_size]<<3) | + (tempBuf[7 + skip_size] & 0xE0); + + if ( fileread != 0) + { + initLength = static_cast(fileread) * 8 / bitrate + 0.5; + } + } + + lengthSecs = static_cast(initLength); + nchannels = initChannels; + samplerate = initSamplerate; + + if ( decoder ) + NeAACDecClose( decoder ); + if ( fp ) + fclose( fp ); + if ( tempBuf ) + free( tempBuf ); +} + + +void AAC_ADTS_File::skip( const int mSecs ) +{ + if ( m_header == AAC_ADTS ) + { + // As AAC is VBR we need to check all ADTS headers to enable seeking... + // There is no other solution + unsigned char header[8]; + unsigned int frameCount, frameLength; + double seconds = 0; + + // We need to find the ATDS syncword so rewind to the beginning + // of the unprocessed data. + if ( m_inBufSize > 0 ) + { + fseek ( m_file, -m_inBufSize, SEEK_CUR ); + m_inBufSize = 0; + } + + for( frameCount = 1; seconds * 1000 < mSecs; frameCount++ ) + { + if ( fread( header, 1, ADTS_HEADER_SIZE, m_file ) != ADTS_HEADER_SIZE ) + { + break; + } + if ( !strncmp( (char*)header, "ID3", 3 ) ) + { + // high bit is not used + unsigned char rest[2]; + fread( rest, 1, 2, m_file ); + int tagsize = (header[6] << 21) | (header[7] << 14) | + (rest[0] << 7) | (rest[1] << 0); + + fseek( m_file, tagsize, SEEK_CUR ); + fread( header, 1, ADTS_HEADER_SIZE, m_file ); + } + if ( !((header[0] == 0xFF) && ((header[1] & 0xF6) == 0xF0)) ) + { + std::cerr << "Error: Bad frame header; file may be corrupt!" << std::endl; + break; + } + + int samplerate = adts_sample_rates[ (header[2] & 0x3c) >> 2 ]; + frameLength = ( ( header[3] & 0x3 ) << 11 ) + | ( header[4] << 3 ) + | ( header[5] >> 5 ); + + if ( samplerate > 0 ) + seconds += 1024.0 / samplerate; + else + { + std::cerr << "Error: Bad frame header; file may be corrupt!" << std::endl; + break; + } + + if ( fseek( m_file, frameLength - ADTS_HEADER_SIZE, SEEK_CUR ) == -1 ) + break; + } + m_inBufSize = fread( m_inBuf, 1, FAAD_MIN_STREAMSIZE * MAX_CHANNELS, m_file ); + } + else if ( m_header == AAC_ADIF ) + { + // AAC ADIF is even worse. There's only the one header at the + // beginning of the file. If you want to skip forward, you have to + // decode block by block and check how far along you are. Lovely, eh? + + unsigned long totalSamples = 0; + void *sampleBuffer = NULL; + + do + { + NeAACDecFrameInfo frameInfo; + sampleBuffer = NeAACDecDecode(m_decoder, &frameInfo, m_inBuf, static_cast(m_inBufSize) ); + totalSamples += frameInfo.samples; + if ( frameInfo.bytesconsumed > 0 ) + { + m_inBufSize -= frameInfo.bytesconsumed; + fillBuffer( m_file, m_inBuf, m_inBufSize, frameInfo.bytesconsumed ); + } + if ( totalSamples >= ( mSecs * m_adifSamplerate * m_adifChannels / 1000 ) ) + break; + } while ( sampleBuffer != NULL ); + } +} + + +void AAC_ADTS_File::postDecode(unsigned long bytesConsumed) +{ + m_inBufSize -= bytesConsumed; + fillBuffer( m_file, m_inBuf, m_inBufSize, bytesConsumed ); +} + + +//////////////////////////////////////////////////////////////////////// +// +// AAC in an MP4 wrapper +// +//////////////////////////////////////////////////////////////////////// + + +uint32_t read_callback( void *user_data, void *buffer, uint32_t length ) +{ + return static_cast(fread( buffer, 1, length, static_cast(user_data) )); +} + + +uint32_t seek_callback( void *user_data, uint64_t position ) +{ + return fseek( static_cast(user_data), static_cast(position), SEEK_SET ); +} + +AAC_MP4_File::AAC_MP4_File( const QString& fileName, int headerType ) : AAC_File(fileName, headerType) + , m_mp4AudioTrack( -1 ) + , m_mp4SampleId( 0 ) + , m_mp4File ( NULL ) + , m_mp4cb ( NULL ) +{ +} + +int32_t AAC_MP4_File::readSample() +{ + unsigned int bsize; + int32_t rc = mp4ff_read_sample( m_mp4File, m_mp4AudioTrack, m_mp4SampleId, &m_inBuf, &bsize ); + m_inBufSize = bsize; + // Not necessarily an error. Could just mean end of file. + //if ( rc == 0 ) + // std::cerr << "Reading samples failed." << std::endl; + return rc; +} + + +int32_t AAC_MP4_File::getTrack( const mp4ff_t *f ) +{ + // find AAC track + int32_t numTracks = mp4ff_total_tracks( f ); + + for ( int32_t i = 0; i < numTracks; i++ ) + { + unsigned char *buff = NULL; + unsigned int buff_size = 0; + mp4AudioSpecificConfig mp4ASC; + + mp4ff_get_decoder_config( f, i, &buff, &buff_size ); + + if ( buff ) + { + int8_t rc = NeAACDecAudioSpecificConfig( buff, buff_size, &mp4ASC ); + free( buff ); + + if ( rc < 0 ) + continue; + return i; + } + } + + // can't decode this, probably DRM + return -1; +} + + +bool AAC_MP4_File::commonSetup( NeAACDecHandle& decoder, mp4ff_callback_t*& cb, FILE*& fp, mp4ff_t*& mp4, int32_t& audioTrack ) +{ + fp = fopen(QFile::encodeName(m_fileName), "rb"); + if ( !fp ) + { + throw std::runtime_error( "Error: failed to open AAC file!" ); + return false; + } + + decoder = NeAACDecOpen(); + + // Set configuration + NeAACDecConfigurationPtr config; + config = NeAACDecGetCurrentConfiguration( decoder ); + config->outputFormat = FAAD_FMT_16BIT; + config->downMatrix = 1; // Turn 5.1 channels into 2 + NeAACDecSetConfiguration( decoder, config ); + + // initialise the callback structure + cb = static_cast( malloc( sizeof(mp4ff_callback_t) ) ); + + cb->read = read_callback; + cb->seek = seek_callback; + cb->user_data = fp; + + mp4 = mp4ff_open_read( cb ); + + if ( !mp4 ) + { + // unable to open file + free( cb ); + cb = NULL; + NeAACDecClose( decoder ); + decoder = NULL; + fclose( fp ); + fp = NULL; + throw std::runtime_error( "Error: failed to set up AAC decoder!" ); + return false; + } + + if ( ( audioTrack = getTrack( mp4 )) < 0 ) + { + free( cb ); + cb = NULL; + NeAACDecClose( decoder ); + decoder = NULL; + fclose( fp ); + fp = NULL; + mp4ff_close( mp4 ); + mp4 = NULL; + audioTrack = 0; + throw std::runtime_error( "Error: Unable to find an audio track. Is the file DRM protected?" ); + return false; + } + return true; +} + + +/*QString AAC_MP4_File::getMbid() +{ + int j = mp4ff_meta_get_num_items( m_mp4File ); + if ( j > 0 ) + { + int k; + for ( k = 0; k < j; k++ ) + { + char *tag = NULL, *item = NULL; + if ( mp4ff_meta_get_by_index( m_mp4File, k, &item, &tag ) ) + { + if ( item != NULL && tag != NULL ) + { + QString key(item); + if ( key.toLower() == "musicbrainz track id" ) + { + QString ret(tag); + free( item ); + free( tag ); + return ret; + } + free( item ); + free( tag ); + } + } + } + } + return QString(); +}*/ + + +void AAC_MP4_File::getInfo( int& lengthSecs, int& samplerate, int& bitrate, int& nchannels ) +{ + FILE* fp = NULL; + mp4ff_callback_t *cb = NULL; + NeAACDecHandle decoder = NULL; + mp4ff_t* mp4 = NULL; + int32_t audioTrack; + + bool success = commonSetup( decoder, cb, fp, mp4, audioTrack ); + + if ( success ) + { + // get basic file info + mp4AudioSpecificConfig mp4ASC; + unsigned char* buffer = NULL; + unsigned int buffer_size = 0; + double f = 1024.0; + unsigned int framesize = 1024; + + int32_t samples = mp4ff_num_samples( mp4, audioTrack ); + + if ( buffer ) + { + if ( NeAACDecAudioSpecificConfig(buffer, buffer_size, &mp4ASC) >= 0 ) + { + if ( mp4ASC.frameLengthFlag == 1 ) + framesize = 960; + if ( mp4ASC.sbr_present_flag == 1 ) + framesize *= 2; + if ( mp4ASC.sbr_present_flag == 1 ) + f = f * 2.0; + } + free( buffer ); + } + + samplerate = mp4ff_get_sample_rate( mp4, audioTrack ); + if ( samplerate > 0 ) + lengthSecs = static_cast(samples * f / samplerate + 0.5); + bitrate = mp4ff_get_avg_bitrate( mp4, audioTrack ); + nchannels = mp4ff_get_channel_count( mp4, audioTrack ); + + mp4ff_close( mp4 ); + NeAACDecClose( decoder ); + free( cb ); + fclose( fp ); + } +} + + +bool AAC_MP4_File::init() +{ + FILE* fp = NULL; + + bool success = commonSetup( m_decoder, m_mp4cb, fp, m_mp4File, m_mp4AudioTrack ); + if ( !success ) + return false; + + unsigned char* buffer = NULL; + unsigned int buffer_size = 0; + uint32_t samplerate; + uint8_t channels; + + mp4ff_get_decoder_config( m_mp4File, m_mp4AudioTrack, &buffer, &buffer_size ); + + if( NeAACDecInit2( m_decoder, buffer, buffer_size, &samplerate, &channels) < 0 ) + { + // If some error initializing occured, skip the file + if ( fp ) + fclose( fp ); + throw std::runtime_error( "Error: unable to initialize AAC decoder library!" ); + return false; + } + + if ( buffer ) + free( buffer ); + + return true; +} + + +void AAC_MP4_File::postDecode(unsigned long) +{ + free( m_inBuf ); + m_inBuf = NULL; + m_mp4SampleId++; +} + +void AAC_MP4_File::skip( const int mSecs ) +{ + double dur = 0.0; + int f = 1; + unsigned char *buff = NULL; + unsigned int buff_size = 0; + uint32_t totalSamples = mp4ff_num_samples( m_mp4File, m_mp4AudioTrack ); + mp4AudioSpecificConfig mp4ASC; + + mp4ff_get_decoder_config( m_mp4File, m_mp4AudioTrack, &buff, &buff_size ); + + if ( buff ) + { + int8_t rc = NeAACDecAudioSpecificConfig( buff, buff_size, &mp4ASC ); + free( buff ); + if ( rc >= 0 && mp4ASC.sbr_present_flag == 1 ) + f = 2; + + // I think the f multiplier is needed here. + while ( dur * 1000.0 * f / static_cast(mp4ASC.samplingFrequency) < mSecs && m_mp4SampleId < totalSamples ) + { + dur += mp4ff_get_sample_duration( m_mp4File, m_mp4AudioTrack, m_mp4SampleId ); + m_mp4SampleId++; + } + } + else + std::cerr << "Error: could not skip " << mSecs << " milliseconds" << std::endl; +} + + +AAC_MP4_File::~AAC_MP4_File() +{ + if ( m_mp4File ) + mp4ff_close( m_mp4File ); + if ( m_mp4cb ) + { + free( m_mp4cb ); + } +} + + +//////////////////////////////////////////////////////////////////////// +// +// AacSource +// +//////////////////////////////////////////////////////////////////////// + +AacSource::AacSource() + : m_eof( false ) + , m_aacFile( NULL ) +{} + + +AacSource::~AacSource() +{ + delete m_aacFile; +} + + +int AacSource::checkHeader() +{ + FILE *fp = NULL; + unsigned char header[10]; + + // check for mp4 file + fp = fopen(QFile::encodeName(m_fileName), "rb"); + if ( !fp ) + { + std::cerr << "Error: failed to open " << strerror( errno ) << std::endl; + return AAC_File::AAC_UNKNOWN; + } + + fread( header, 1, 10, fp ); + + // MP4 headers + if ( !memcmp( &header[4], "ftyp", 4 ) ) + { + fclose( fp ); + return AAC_File::AAC_MP4; + } + + // Skip id3 tags + int tagsize = 0; + if ( !memcmp( header, "ID3", 3 ) ) + { + /* high bit is not used */ + tagsize = (header[6] << 21) | (header[7] << 14) | + (header[8] << 7) | (header[9] << 0); + + tagsize += 10; + fseek( fp, tagsize, SEEK_SET ); + fread( header, 1, 10, fp ); + } + + // Check for ADTS OR ADIF headers + if ( (header[0] == 0xFF) && ((header[1] & 0xF6) == 0xF0) ) + { + fclose( fp ); + return AAC_File::AAC_ADTS; + } + else if (memcmp(header, "ADIF", 4) == 0) + { + fclose( fp ); + return AAC_File::AAC_ADIF; + } + + fclose( fp ); + return AAC_File::AAC_UNKNOWN; +} + + +void AacSource::getInfo( int& lengthSecs, int& samplerate, int& bitrate, int& nchannels ) +{ + // get the header plus some other stuff.. + + m_aacFile->getInfo( lengthSecs, samplerate, bitrate, nchannels ); +} + + +void AacSource::init(const QString& fileName) +{ + m_fileName = fileName; + + int headerType = checkHeader(); + if ( headerType != AAC_File::AAC_UNKNOWN ) + { + if ( headerType == AAC_File::AAC_MP4 ) + m_aacFile = new AAC_MP4_File(m_fileName, headerType); + else + m_aacFile = new AAC_ADTS_File( m_fileName, headerType ); + } + + if ( m_aacFile ) + m_aacFile->init(); + else + throw std::runtime_error( "ERROR: No suitable AAC decoder found!" ); +} + + +/*QString AacSource::getMbid() +{ + QString mbid = m_aacFile->getMbid(); + return mbid; +}*/ + + +void AacSource::skip( const int mSecs ) +{ + if ( mSecs < 0 || !m_aacFile->m_decoder ) + return; + + m_aacFile->skip( mSecs ); +} + + +void AacSource::skipSilence(double silenceThreshold /* = 0.0001 */) +{ + if ( !m_aacFile->m_decoder ) + return; + + silenceThreshold *= static_cast( std::numeric_limits::max() ); + + for (;;) + { + if ( m_aacFile->m_header == AAC_File::AAC_MP4 ) + { + if ( !static_cast(m_aacFile)->readSample() ) + break; + } + NeAACDecFrameInfo frameInfo; + + void* sampleBuffer = NeAACDecDecode(m_aacFile->m_decoder, &frameInfo, m_aacFile->m_inBuf, static_cast(m_aacFile->m_inBufSize) ); + + m_aacFile->postDecode( frameInfo.bytesconsumed ); + + if ( frameInfo.error > 0 ) + { + break; + } + else if ( frameInfo.samples > 0 ) + { + double sum = 0; + int16_t *buf = static_cast(sampleBuffer); + switch ( frameInfo.channels ) + { + case 1: + for (size_t j = 0; j < frameInfo.samples; ++j) + sum += abs( buf[j] ); + break; + case 2: + for (size_t j = 0; j < frameInfo.samples; j+=2) + sum += abs( (buf[j] >> 1) + (buf[j+1] >> 1) ); + break; + } + if ( (sum >= silenceThreshold * static_cast(frameInfo.samples/frameInfo.channels) ) ) + break; + } + } +} + + +int AacSource::updateBuffer( signed short *pBuffer, size_t bufferSize ) +{ + size_t nwrit = 0; //number of samples written to the output buffer + + if ( m_aacFile->m_overflowSize > 0 ) + { + size_t samples_to_use = bufferSize < m_aacFile->m_overflowSize ? bufferSize : m_aacFile->m_overflowSize; + memcpy( pBuffer, m_aacFile->m_overflow, samples_to_use * sizeof(signed short) ); + nwrit += samples_to_use; + m_aacFile->m_overflowSize -= samples_to_use; + memmove( (void*)(m_aacFile->m_overflow), (void*)(m_aacFile->m_overflow + samples_to_use*sizeof(signed short)), samples_to_use*sizeof(signed short) ); + } + + if ( !m_aacFile->m_decoder ) + return 0; + + for (;;) + { + signed short* pBufferIt = pBuffer + nwrit; + void* sampleBuffer; + + assert( nwrit <= bufferSize ); + + if ( m_aacFile->m_header == AAC_File::AAC_MP4 ) + { + if ( !static_cast(m_aacFile)->readSample() ) + { + m_eof = true; + return static_cast(nwrit); + } + } + NeAACDecFrameInfo frameInfo; + + sampleBuffer = NeAACDecDecode(m_aacFile->m_decoder, &frameInfo, m_aacFile->m_inBuf, static_cast(m_aacFile->m_inBufSize) ); + size_t samples_to_use = (bufferSize - nwrit) < frameInfo.samples ? bufferSize-nwrit : frameInfo.samples; + + if ( samples_to_use > 0 && sampleBuffer != NULL ) + { + memcpy( pBufferIt, sampleBuffer, samples_to_use * sizeof(signed short) ); + nwrit += samples_to_use; + } + + if ( samples_to_use < frameInfo.samples ) + { + m_aacFile->m_overflow = static_cast(realloc( m_aacFile->m_overflow, (frameInfo.samples - samples_to_use) * sizeof(signed short) ) ); + memcpy( m_aacFile->m_overflow, static_cast(sampleBuffer) + samples_to_use, (frameInfo.samples - samples_to_use) * sizeof(signed short) ); + m_aacFile->m_overflowSize = frameInfo.samples - samples_to_use; + } + + m_aacFile->postDecode( frameInfo.bytesconsumed ); + + if ( sampleBuffer == NULL ) + { + m_eof = true; + break; + } + + if ( frameInfo.error > 0 ) + { + std::cerr << "Error: " << NeAACDecGetErrorMessage(frameInfo.error) << std::endl; + break; + } + + if ( nwrit == bufferSize ) + break; + } + + return static_cast(nwrit); +} diff --git a/thirdparty/liblastfm2/src/fingerprint/contrib/AacSource.h b/thirdparty/liblastfm2/src/fingerprint/contrib/AacSource.h new file mode 100644 index 000000000..0fff6f585 --- /dev/null +++ b/thirdparty/liblastfm2/src/fingerprint/contrib/AacSource.h @@ -0,0 +1,46 @@ +/* + Copyright 2009 Last.fm Ltd. + Copyright 2009 John Stamp + + This file is part of liblastfm. + + liblastfm 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. + + liblastfm 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 liblastfm. If not, see . +*/ +#ifndef __AAC_SOURCE_H__ +#define __AAC_SOURCE_H__ + +#include + +class AacSource : public lastfm::FingerprintableSource +{ +public: + AacSource(); + ~AacSource(); + + virtual void getInfo(int& lengthSecs, int& samplerate, int& bitrate, int& nchannels); + virtual void init(const QString& fileName); + virtual int updateBuffer(signed short* pBuffer, size_t bufferSize); + virtual void skip(const int mSecs); + virtual void skipSilence(double silenceThreshold = 0.0001); + virtual bool eof() const { return m_eof; } + +private: + int checkHeader(); + QString m_fileName; + bool m_eof; + class AAC_File *m_aacFile; +}; + +#endif + diff --git a/thirdparty/liblastfm2/src/fingerprint/contrib/AacSource_p.h b/thirdparty/liblastfm2/src/fingerprint/contrib/AacSource_p.h new file mode 100644 index 000000000..870482c1f --- /dev/null +++ b/thirdparty/liblastfm2/src/fingerprint/contrib/AacSource_p.h @@ -0,0 +1,94 @@ +/* + Copyright 2009 Last.fm Ltd. + Copyright 2009 John Stamp + + This file is part of liblastfm. + + liblastfm 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. + + liblastfm 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 liblastfm. If not, see . +*/ +#include +#include + +class AAC_File +{ +public: + AAC_File(const QString&, int headerType); + virtual ~AAC_File(); + virtual void getInfo( int& lengthSecs, int& samplerate, int& bitrate, int& nchannels ) = 0; + virtual bool init() = 0; + //virtual QString getMbid() = 0; + virtual void skip( const int mSecs ) = 0; + virtual void postDecode(unsigned long) = 0; + + enum HeaderType + { + AAC_UNKNOWN = 0, + AAC_ADIF, + AAC_ADTS, + AAC_MP4 + }; + + QString m_fileName; + unsigned char *m_inBuf; + size_t m_inBufSize; + NeAACDecHandle m_decoder; + unsigned char *m_overflow; + size_t m_overflowSize; + int m_header; +}; + + +class AAC_MP4_File : public AAC_File +{ +public: + AAC_MP4_File(const QString&, int headerType = AAC_MP4 ); + ~AAC_MP4_File(); + virtual void getInfo( int& lengthSecs, int& samplerate, int& bitrate, int& nchannels ); + virtual bool init(); + //virtual QString getMbid(); + virtual void skip( const int mSecs ); + virtual void postDecode(unsigned long); + int32_t readSample(); + +private: + bool commonSetup( NeAACDecHandle& handle, mp4ff_callback_t*& cb, FILE*& fp, mp4ff_t*& mp4, int32_t& audioTrack ); + virtual int32_t getTrack( const mp4ff_t* f ); + int m_mp4AudioTrack; + uint32_t m_mp4SampleId; + mp4ff_t *m_mp4File; + mp4ff_callback_t *m_mp4cb; +}; + + +class AAC_ADTS_File : public AAC_File +{ +public: + AAC_ADTS_File( const QString& fileName, int headerType ); + ~AAC_ADTS_File(); + virtual void getInfo( int& lengthSecs, int& samplerate, int& bitrate, int& nchannels ); + virtual bool init(); + //virtual QString getMbid(); + virtual void skip( const int mSecs ); + virtual void postDecode(unsigned long bytesconsumed ); + +private: + int32_t commonSetup( FILE*& fp, NeAACDecHandle& decoder, unsigned char*& buf, size_t& bufSize, uint32_t& samplerate, uint8_t& channels ); + void parse( FILE*& fp, unsigned char*& buf, size_t& bufSize, int &bitrate, double &length ); + void fillBuffer( FILE*& fp, unsigned char*& buf, size_t& bufSize, const size_t m_bytesConsumed ); + + FILE* m_file; + // These two only needed for skipping AAC ADIF files + uint32_t m_adifSamplerate; + int m_adifChannels; +}; diff --git a/thirdparty/liblastfm2/src/fingerprint/contrib/FlacSource.cpp b/thirdparty/liblastfm2/src/fingerprint/contrib/FlacSource.cpp new file mode 100644 index 000000000..1e1e29900 --- /dev/null +++ b/thirdparty/liblastfm2/src/fingerprint/contrib/FlacSource.cpp @@ -0,0 +1,339 @@ +/* + Copyright 2009 Last.fm Ltd. + Copyright 2009 John Stamp + + This file is part of liblastfm. + + liblastfm 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. + + liblastfm 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 liblastfm. If not, see . +*/ +#include "FlacSource.h" +#include +#include +#include +#include +#include +#include +#include + +#include + + +FLAC__StreamDecoderWriteStatus FlacSource::_write_callback(const FLAC__StreamDecoder *, const FLAC__Frame *frame, const FLAC__int32 * const buffer[], void *client_data) +{ + assert(client_data != NULL); + FlacSource *instance = reinterpret_cast(client_data); + assert(instance != NULL); + return instance->write_callback(frame, buffer); +} + +FLAC__StreamDecoderWriteStatus FlacSource::write_callback(const FLAC__Frame *frame, const FLAC__int32 * const buffer[]) +{ + m_outBufLen = 0; + + if ( m_outBuf ) + { + size_t i; + for(i = 0; i < frame->header.blocksize; i++) + { + switch ( m_channels ) + { + case 1: + m_outBuf[m_outBufLen] = (FLAC__int16)buffer[0][i]; // mono + m_outBufLen++; + break; + case 2: + m_outBuf[m_outBufLen] = (FLAC__int16)buffer[0][i]; // left channel + m_outBuf[m_outBufLen+1] = (FLAC__int16)buffer[1][i]; // right channel + m_outBufLen += 2; + break; + } + } + m_samplePos += frame->header.blocksize; + } + + return FLAC__STREAM_DECODER_WRITE_STATUS_CONTINUE; +} + +// --------------------------------------------------------------------- + +void FlacSource::_metadata_callback(const FLAC__StreamDecoder *, const FLAC__StreamMetadata *metadata, void *client_data) +{ + assert(client_data != NULL); + FlacSource *instance = reinterpret_cast(client_data); + assert(instance != NULL); + instance->metadata_callback(metadata); +} + +void FlacSource::metadata_callback( const FLAC__StreamMetadata *metadata ) +{ + switch ( metadata->type ) + { + case FLAC__METADATA_TYPE_STREAMINFO: + m_channels = metadata->data.stream_info.channels; + m_totalSamples = metadata->data.stream_info.total_samples; + m_samplerate = metadata->data.stream_info.sample_rate; + m_bps = metadata->data.stream_info.bits_per_sample; + m_maxFrameSize = metadata->data.stream_info.max_framesize; + break; + case FLAC__METADATA_TYPE_VORBIS_COMMENT: + m_commentData = FLAC__metadata_object_clone(metadata); + break; + default: + break; + } +} + +// --------------------------------------------------------------------- + +void FlacSource::_error_callback(const FLAC__StreamDecoder *, FLAC__StreamDecoderErrorStatus status, void *client_data) +{ + assert(client_data != NULL); + FlacSource *instance = reinterpret_cast(client_data); + assert(instance != NULL); + instance->error_callback(status); +} + +void FlacSource::error_callback(FLAC__StreamDecoderErrorStatus status) +{ + std::cerr << "Got FLAC error: " << FLAC__StreamDecoderErrorStatusString[status] << std::endl; +} + +// --------------------------------------------------------------------- + +FlacSource::FlacSource() + : m_decoder( 0 ) + , m_fileSize( 0 ) + , m_outBuf( 0 ) + , m_outBufLen( 0 ) + , m_outBufPos( 0 ) + , m_samplePos( 0 ) + , m_maxFrameSize( 0 ) + , m_commentData( 0 ) + , m_bps( 0 ) + , m_channels( 0 ) + , m_samplerate( 0 ) + , m_totalSamples( 0 ) + , m_eof( false ) +{ +} + +// --------------------------------------------------------------------- + +FlacSource::~FlacSource() +{ + if ( m_decoder ) + { + FLAC__stream_decoder_finish( m_decoder ); + FLAC__stream_decoder_delete( m_decoder ); + } + if ( m_commentData ) + FLAC__metadata_object_delete( m_commentData ); + if ( m_outBuf ) + free( m_outBuf ); +} + +// --------------------------------------------------------------------- + +void FlacSource::init(const QString& fileName) +{ + m_fileName = fileName; + + if ( !m_decoder ) + { + FILE *f = fopen(QFile::encodeName(m_fileName), "rb" ); + if ( f ) + { + // Need to check which init call to use; flac doesn't do that for us + unsigned char header[35]; + bool isOgg = false; + fread( header, 1, 35, f ); + if ( memcmp(header, "OggS", 4) == 0 && + memcmp(&header[29], "FLAC", 4) == 0 ) + isOgg = true; + + // getInfo() will need this to calculate bitrate + fseek( f, 0, SEEK_END ); + m_fileSize = ftell(f); + + rewind( f ); + + m_decoder = FLAC__stream_decoder_new(); + FLAC__stream_decoder_set_metadata_respond(m_decoder, FLAC__METADATA_TYPE_STREAMINFO); + FLAC__stream_decoder_set_metadata_respond(m_decoder, FLAC__METADATA_TYPE_VORBIS_COMMENT); + + int init_status; + if ( FLAC_API_SUPPORTS_OGG_FLAC && isOgg ) + init_status = FLAC__stream_decoder_init_ogg_FILE( m_decoder, f, _write_callback, _metadata_callback, _error_callback, this ); + else + init_status = FLAC__stream_decoder_init_FILE( m_decoder, f, _write_callback, _metadata_callback, _error_callback, this ); + + if(init_status != FLAC__STREAM_DECODER_INIT_STATUS_OK) + return; + + FLAC__stream_decoder_process_until_end_of_metadata( m_decoder ); + m_outBuf = static_cast(malloc( sizeof(signed short)*m_maxFrameSize)); + + if ( m_bps != 16 ) + { + FLAC__stream_decoder_finish( m_decoder ); + FLAC__stream_decoder_delete( m_decoder ); + FLAC__metadata_object_delete( m_commentData ); + m_decoder = 0; + m_commentData = 0; + throw std::runtime_error( "ERROR: only 16 bit FLAC files are currently supported!" ); + } + } + else + throw std::runtime_error( "ERROR: cannot load FLAC file!" ); + } +} + +// --------------------------------------------------------------------- + +/*QString FlacSource::getMbid() +{ + if ( m_commentData ) + { + FLAC__StreamMetadata_VorbisComment *vc; + vc = &m_commentData->data.vorbis_comment; + for ( unsigned int i = 0; i < vc->num_comments; ++i ) + { + QByteArray key( (char*)(vc->comments[i].entry), vc->comments[i].length ); + if ( key.left(20).toLower() == "musicbrainz_trackid=" ) + { + QString val = key.mid(20); + return val; + } + } + } + + return QString(); +}*/ + +// --------------------------------------------------------------------- + +void FlacSource::getInfo(int& lengthSecs, int& samplerate, int& bitrate, int& nchannels) +{ + lengthSecs = 0; + samplerate = 0; + bitrate = 0; + nchannels = 0; + + if ( m_decoder ) + { + samplerate = m_samplerate; + nchannels = m_channels; + if ( samplerate > 0 ) + lengthSecs = static_cast( static_cast(m_totalSamples)/m_samplerate + 0.5); + + // Calcuate bitrate + if ( lengthSecs > 0 ) + { + FLAC__Metadata_SimpleIterator *it = FLAC__metadata_simple_iterator_new(); + FLAC__metadata_simple_iterator_init( it, QFile::encodeName(m_fileName), true, true ); + while( !FLAC__metadata_simple_iterator_is_last( it ) ) + { + FLAC__metadata_simple_iterator_next( it ); + } + off_t audioOffset = FLAC__metadata_simple_iterator_get_block_offset( it ) + + FLAC__metadata_simple_iterator_get_block_length( it ); + FLAC__metadata_simple_iterator_delete( it ); + bitrate = static_cast( static_cast(m_fileSize - audioOffset) * 8 / lengthSecs + 0.5 ); + } + } +} + +// --------------------------------------------------------------------- + +void FlacSource::skip( const int mSecs ) +{ + FLAC__uint64 absSample = mSecs * m_samplerate / 1000 + m_samplePos; + if ( !FLAC__stream_decoder_seek_absolute(m_decoder, absSample) ) + FLAC__stream_decoder_reset( m_decoder ); + m_outBufLen = 0; +} + +// --------------------------------------------------------------------- + +void FlacSource::skipSilence(double silenceThreshold /* = 0.0001 */) +{ + silenceThreshold *= static_cast( std::numeric_limits::max() ); + for ( ;; ) + { + double sum = 0; + bool result = FLAC__stream_decoder_process_single( m_decoder ); + // there was a fatal read + if ( !result ) + break; + + switch ( m_channels ) + { + case 1: + for (size_t j = 0; j < m_outBufLen; ++j) + sum += abs( m_outBuf[j] ); + break; + case 2: + for ( size_t j = 0; j < m_outBufLen; j+=2 ) + sum += abs( (m_outBuf[j] >> 1) + + (m_outBuf[j+1] >> 1) ); + break; + } + if ( (sum >= silenceThreshold * static_cast(m_outBufLen) ) ) + break; + } + m_outBufLen = 0; +} + +// --------------------------------------------------------------------- + +int FlacSource::updateBuffer( signed short *pBuffer, size_t bufferSize ) +{ + size_t nwrit = 0; + + for ( ;; ) + { + size_t samples_to_use = std::min (bufferSize - nwrit, m_outBufLen - m_outBufPos); + signed short* pBufferIt = pBuffer + nwrit; + + nwrit += samples_to_use; + assert( nwrit <= bufferSize ); + memcpy( pBufferIt, m_outBuf + m_outBufPos, sizeof(signed short)*samples_to_use ); + + if ( samples_to_use < m_outBufLen - m_outBufPos ) + m_outBufPos = samples_to_use; + else + { + m_outBufPos = 0; + bool result = FLAC__stream_decoder_process_single( m_decoder ); + // there was a fatal read + if ( !result ) + { + std::cerr << "Fatal error decoding FLAC" << std::endl; + return 0; + } + else if ( FLAC__stream_decoder_get_state( m_decoder ) == FLAC__STREAM_DECODER_END_OF_STREAM ) + { + m_eof = true; + break; + } + } + + if ( nwrit == bufferSize ) + return static_cast(nwrit); + } + return static_cast(nwrit); +} + +// ----------------------------------------------------------------------------- + diff --git a/thirdparty/liblastfm2/src/fingerprint/contrib/FlacSource.h b/thirdparty/liblastfm2/src/fingerprint/contrib/FlacSource.h new file mode 100644 index 000000000..24dd97f11 --- /dev/null +++ b/thirdparty/liblastfm2/src/fingerprint/contrib/FlacSource.h @@ -0,0 +1,74 @@ +/* + Copyright 2009 Last.fm Ltd. + Copyright 2009 John Stamp + + This file is part of liblastfm. + + liblastfm 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. + + liblastfm 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 liblastfm. If not, see . +*/ +#ifndef __FLAC_SOURCE_H__ +#define __FLAC_SOURCE_H__ + +#include +#include +#include + + +class FlacSource : public lastfm::FingerprintableSource +{ +public: + FlacSource(); + virtual ~FlacSource(); + + virtual void getInfo(int& lengthSecs, int& samplerate, int& bitrate, int& nchannels); + virtual void init(const QString& fileName); + + // return a chunk of PCM data from the FLAC file + virtual int updateBuffer(signed short* pBuffer, size_t bufferSize); + + virtual void skip(const int mSecs); + virtual void skipSilence(double silenceThreshold = 0.0001); + + //QString getMbid(); + + bool eof() const { return m_eof; } + +private: + static FLAC__StreamDecoderWriteStatus _write_callback(const FLAC__StreamDecoder *decoder, const FLAC__Frame *frame, const FLAC__int32 * const buffer[], void *client_data); + static void _metadata_callback(const FLAC__StreamDecoder *decoder, const FLAC__StreamMetadata *metadata, void *client_data); + static void _error_callback(const ::FLAC__StreamDecoder *decoder, ::FLAC__StreamDecoderErrorStatus status, void *client_data); + + FLAC__StreamDecoderWriteStatus write_callback(const FLAC__Frame *frame, const FLAC__int32 * const buffer[]); + void metadata_callback( const FLAC__StreamMetadata *metadata ); + void error_callback(FLAC__StreamDecoderErrorStatus status); + + FLAC__StreamDecoder *m_decoder; + QString m_fileName; + size_t m_fileSize; + short *m_outBuf; + size_t m_outBufLen; + size_t m_outBufPos; + FLAC__uint64 m_samplePos; + unsigned m_maxFrameSize; + FLAC__StreamMetadata* m_commentData; + unsigned m_bps; + unsigned m_channels; + unsigned m_samplerate; + FLAC__uint64 m_totalSamples; + + bool m_eof; +}; + +#endif + diff --git a/thirdparty/liblastfm2/src/fingerprint/contrib/MadSource.cpp b/thirdparty/liblastfm2/src/fingerprint/contrib/MadSource.cpp new file mode 100644 index 000000000..00e725ae6 --- /dev/null +++ b/thirdparty/liblastfm2/src/fingerprint/contrib/MadSource.cpp @@ -0,0 +1,514 @@ +/* + Copyright 2009 Last.fm Ltd. + Copyright 2009 John Stamp + + This file is part of liblastfm. + + liblastfm 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. + + liblastfm 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 liblastfm. If not, see . +*/ + +#include +#include +#include +#include +#include +#include +#include +#include +#include "MadSource.h" + +#undef max // was definded in mad + +using namespace std; + + +// ----------------------------------------------------------- + +MadSource::MadSource() + : m_pMP3_Buffer ( new unsigned char[m_MP3_BufferSize+MAD_BUFFER_GUARD] ) +{} + +// ----------------------------------------------------------- + +MadSource::~MadSource() +{ + if ( m_inputFile.isOpen() ) + { + m_inputFile.close(); + mad_synth_finish(&m_mad_synth); + mad_frame_finish(&m_mad_frame); + mad_stream_finish(&m_mad_stream); + } + if (m_pMP3_Buffer) delete[] m_pMP3_Buffer; +} + +// --------------------------------------------------------------------- + +inline short f2s(mad_fixed_t f) +{ + /* A fixed point number is formed of the following bit pattern: + * + * SWWWFFFFFFFFFFFFFFFFFFFFFFFFFFFF + * MSB LSB + * S ==> Sign (0 is positive, 1 is negative) + * W ==> Whole part bits + * F ==> Fractional part bits + * + * This pattern contains MAD_F_FRACBITS fractional bits, one + * should alway use this macro when working on the bits of a fixed + * point number. It is not guaranteed to be constant over the + * different platforms supported by libmad. + * + * The signed short value is formed, after clipping, by the least + * significant whole part bit, followed by the 15 most significant + * fractional part bits. Warning: this is a quick and dirty way to + * compute the 16-bit number, madplay includes much better + * algorithms. + */ + + /* Clipping */ + if(f >= MAD_F_ONE) + return(SHRT_MAX); + if(f <= -MAD_F_ONE) + return(-SHRT_MAX); + + /* Conversion. */ + f = f >> (MAD_F_FRACBITS-15); + return (signed short)f; +} + +// --------------------------------------------------------------------- + +string MadSource::MadErrorString(const mad_error& error) +{ + switch(error) + { + /* Generic unrecoverable errors. */ + case MAD_ERROR_BUFLEN: + return("input buffer too small (or EOF)"); + case MAD_ERROR_BUFPTR: + return("invalid (null) buffer pointer"); + case MAD_ERROR_NOMEM: + return("not enough memory"); + + /* Frame header related unrecoverable errors. */ + case MAD_ERROR_LOSTSYNC: + return("lost synchronization"); + case MAD_ERROR_BADLAYER: + return("reserved header layer value"); + case MAD_ERROR_BADBITRATE: + return("forbidden bitrate value"); + case MAD_ERROR_BADSAMPLERATE: + return("reserved sample frequency value"); + case MAD_ERROR_BADEMPHASIS: + return("reserved emphasis value"); + + /* Recoverable errors */ + case MAD_ERROR_BADCRC: + return("CRC check failed"); + case MAD_ERROR_BADBITALLOC: + return("forbidden bit allocation value"); + case MAD_ERROR_BADSCALEFACTOR: + return("bad scalefactor index"); + case MAD_ERROR_BADFRAMELEN: + return("bad frame length"); + case MAD_ERROR_BADBIGVALUES: + return("bad big_values count"); + case MAD_ERROR_BADBLOCKTYPE: + return("reserved block_type"); + case MAD_ERROR_BADSCFSI: + return("bad scalefactor selection info"); + case MAD_ERROR_BADDATAPTR: + return("bad main_data_begin pointer"); + case MAD_ERROR_BADPART3LEN: + return("bad audio data length"); + case MAD_ERROR_BADHUFFTABLE: + return("bad Huffman table select"); + case MAD_ERROR_BADHUFFDATA: + return("Huffman data overrun"); + case MAD_ERROR_BADSTEREO: + return("incompatible block_type for JS"); + + /* Unknown error. This switch may be out of sync with libmad's + * defined error codes. + */ + default: + return("Unknown error code"); + } +} + + +// ----------------------------------------------------------------------------- + +bool MadSource::isRecoverable(const mad_error& error, bool log) +{ + if (MAD_RECOVERABLE (error)) + { + /* Do not print a message if the error is a loss of + * synchronization and this loss is due to the end of + * stream guard bytes. (See the comments marked {3} + * supra for more informations about guard bytes.) + */ + if (error != MAD_ERROR_LOSTSYNC /*|| mad_stream.this_frame != pGuard */ && log) + { + cerr << "Recoverable frame level error: " + << MadErrorString(error) << endl; + } + + return true; + } + else + { + if (error == MAD_ERROR_BUFLEN) + return true; + else + { + stringstream ss; + + ss << "Unrecoverable frame level error: " + << MadErrorString (error) << endl; + throw ss.str(); + } + } + + return false; +} + +// ----------------------------------------------------------- + +void MadSource::init(const QString& fileName) +{ + m_inputFile.setFileName( m_fileName = fileName ); + bool fine = m_inputFile.open( QIODevice::ReadOnly ); + + if ( !fine ) + { + throw std::runtime_error ("Cannot load mp3 file!"); + } + + mad_stream_init(&m_mad_stream); + mad_frame_init (&m_mad_frame); + mad_synth_init (&m_mad_synth); + mad_timer_reset(&m_mad_timer); + + m_pcmpos = m_mad_synth.pcm.length; +} + +// ----------------------------------------------------------------------------- + +/*QString MadSource::getMbid() +{ + char out[MBID_BUFFER_SIZE]; + int const r = getMP3_MBID( QFile::encodeName( m_fileName ), out ); + if (r == 0) + return QString::fromLatin1( out ); + return QString(); +}*/ + +void MadSource::getInfo(int& lengthSecs, int& samplerate, int& bitrate, int& nchannels ) +{ + // get the header plus some other stuff.. + QFile inputFile(m_fileName); + bool fine = inputFile.open( QIODevice::ReadOnly ); + + if ( !fine ) + { + throw std::runtime_error ("ERROR: Cannot load file for getInfo!"); + return; + } + + unsigned char* pMP3_Buffer = new unsigned char[m_MP3_BufferSize+MAD_BUFFER_GUARD]; + + mad_stream madStream; + mad_header madHeader; + mad_timer_t madTimer; + + mad_stream_init(&madStream); + mad_timer_reset(&madTimer); + + double avgSamplerate = 0; + double avgBitrate = 0; + double avgNChannels = 0; + int nFrames = 0; + + while ( fetchData( inputFile, pMP3_Buffer, m_MP3_BufferSize, madStream) ) + { + if ( mad_header_decode(&madHeader, &madStream) != 0 ) + { + if ( isRecoverable(madStream.error) ) + continue; + else + break; + } + + mad_timer_add(&madTimer, madHeader.duration); + + avgSamplerate += madHeader.samplerate; + avgBitrate += madHeader.bitrate; + + if ( madHeader.mode == MAD_MODE_SINGLE_CHANNEL ) + ++avgNChannels; + else + avgNChannels += 2; + + ++nFrames; + } + + inputFile.close(); + mad_stream_finish(&madStream); + mad_header_finish(&madHeader); + delete[] pMP3_Buffer; + + + lengthSecs = static_cast(madTimer.seconds); + samplerate = static_cast( (avgSamplerate/nFrames) + 0.5 ); + bitrate = static_cast( (avgBitrate/nFrames) + 0.5 ); + nchannels = static_cast( (avgNChannels/nFrames) + 0.5 ); +} + +// ----------------------------------------------------------- + + +bool MadSource::fetchData( QFile& mp3File, + unsigned char* pMP3_Buffer, + const int MP3_BufferSize, + mad_stream& madStream ) +{ + unsigned char *pReadStart = NULL; + unsigned char *pGuard = NULL; + + if ( madStream.buffer == NULL || + madStream.error == MAD_ERROR_BUFLEN ) + { + + size_t readSize; + size_t remaining; + + /* {2} libmad may not consume all bytes of the input + * buffer. If the last frame in the buffer is not wholly + * contained by it, then that frame's start is pointed by + * the next_frame member of the Stream structure. This + * common situation occurs when mad_frame_decode() fails, + * sets the stream error code to MAD_ERROR_BUFLEN, and + * sets the next_frame pointer to a non NULL value. (See + * also the comment marked {4} bellow.) + * + * When this occurs, the remaining unused bytes must be + * put back at the beginning of the buffer and taken in + * account before refilling the buffer. This means that + * the input buffer must be large enough to hold a whole + * frame at the highest observable bit-rate (currently 448 + * kb/s). XXX=XXX Is 2016 bytes the size of the largest + * frame? (448000*(1152/32000))/8 + */ + if (madStream.next_frame != NULL) + { + remaining = madStream.bufend - madStream.next_frame; + memmove (pMP3_Buffer, madStream.next_frame, remaining); + + pReadStart = pMP3_Buffer + remaining; + readSize = MP3_BufferSize - remaining; + } + else + { + readSize = MP3_BufferSize; + pReadStart = pMP3_Buffer; + remaining = 0; + } + + readSize = mp3File.read( reinterpret_cast(pReadStart), readSize ); + + // nothing else to read! + if (readSize <= 0) + return false; + + if ( mp3File.atEnd() ) + { + pGuard = pReadStart + readSize; + + memset (pGuard, 0, MAD_BUFFER_GUARD); + readSize += MAD_BUFFER_GUARD; + } + + // Pipe the new buffer content to libmad's stream decoder facility. + mad_stream_buffer( &madStream, pMP3_Buffer, + static_cast(readSize + remaining)); + + madStream.error = MAD_ERROR_NONE; + } + + return true; +} + +// ----------------------------------------------------------------------------- + +void MadSource::skipSilence(double silenceThreshold /* = 0.0001 */) +{ + mad_frame madFrame; + mad_synth madSynth; + + mad_frame_init(&madFrame); + mad_synth_init (&madSynth); + + silenceThreshold *= static_cast( numeric_limits::max() ); + + for (;;) + { + if ( !fetchData( m_inputFile, m_pMP3_Buffer, m_MP3_BufferSize, m_mad_stream) ) + break; + + if ( mad_frame_decode(&madFrame, &m_mad_stream) != 0 ) + { + if ( isRecoverable(m_mad_stream.error) ) + continue; + else + break; + } + + mad_synth_frame (&madSynth, &madFrame); + + double sum = 0; + + switch (madSynth.pcm.channels) + { + case 1: + for (size_t j = 0; j < madSynth.pcm.length; ++j) + sum += abs(f2s(madSynth.pcm.samples[0][j])); + break; + case 2: + for (size_t j = 0; j < madSynth.pcm.length; ++j) + sum += abs(f2s( + (madSynth.pcm.samples[0][j] >> 1) + + (madSynth.pcm.samples[1][j] >> 1))); + break; + } + + if ( (sum >= silenceThreshold * madSynth.pcm.length) ) + break; + } + + mad_frame_finish(&madFrame); +} + +// ----------------------------------------------------------------------------- + +void MadSource::skip(const int mSecs) +{ + if ( mSecs <= 0 ) + return; + + mad_header madHeader; + mad_header_init(&madHeader); + + for (;;) + { + if (!fetchData( m_inputFile, m_pMP3_Buffer, m_MP3_BufferSize, m_mad_stream)) + break; + + if ( mad_header_decode(&madHeader, &m_mad_stream) != 0 ) + { + if ( isRecoverable(m_mad_stream.error) ) + continue; + else + break; + } + + mad_timer_add(&m_mad_timer, madHeader.duration); + + if ( mad_timer_count(m_mad_timer, MAD_UNITS_MILLISECONDS) >= mSecs ) + break; + } + + mad_header_finish(&madHeader); +} + +// ----------------------------------------------------------- + +int MadSource::updateBuffer(signed short* pBuffer, size_t bufferSize) +{ + size_t nwrit = 0; //number of samples written to the output buffer + + for (;;) + { + // get a (valid) frame + // m_pcmpos == 0 could mean two things + // - we have completely decoded a frame, but the output buffer is still + // not full (it would make more sense for pcmpos == pcm.length(), but + // the loop assigns pcmpos = 0 at the end and does it this way! + // - we are starting a stream + if ( m_pcmpos == m_mad_synth.pcm.length ) + { + if ( !fetchData( m_inputFile, m_pMP3_Buffer, m_MP3_BufferSize, m_mad_stream) ) + { + break; // nothing else to read + } + + // decode the frame + if (mad_frame_decode (&m_mad_frame, &m_mad_stream)) + { + if ( isRecoverable(m_mad_stream.error) ) + continue; + else + break; + } // if (mad_frame_decode (&madFrame, &madStream)) + + mad_timer_add (&m_mad_timer, m_mad_frame.header.duration); + mad_synth_frame (&m_mad_synth, &m_mad_frame); + + m_pcmpos = 0; + } + + size_t samples_for_mp3 = m_mad_synth.pcm.length - m_pcmpos; + size_t samples_for_buf = bufferSize - nwrit; + signed short* pBufferIt = pBuffer + nwrit; + size_t i = 0, j = 0; + + switch( m_mad_synth.pcm.channels ) + { + case 1: + { + size_t samples_to_use = min (samples_for_mp3, samples_for_buf); + for (i = 0; i < samples_to_use; ++i ) + pBufferIt[i] = f2s( m_mad_synth.pcm.samples[0][i+m_pcmpos] ); + } + j = i; + break; + + case 2: + for (; i < samples_for_mp3 && j < samples_for_buf ; ++i, j+=2 ) + { + pBufferIt[j] = f2s( m_mad_synth.pcm.samples[0][i+m_pcmpos] ); + pBufferIt[j+1] = f2s( m_mad_synth.pcm.samples[1][i+m_pcmpos] ); + } + break; + + default: + cerr << "wtf kind of mp3 has " << m_mad_synth.pcm.channels << " channels??\n"; + break; + } + + m_pcmpos += i; + nwrit += j; + + assert( nwrit <= bufferSize ); + + if (nwrit == bufferSize) + return static_cast(nwrit); + } + + return static_cast(nwrit); +} + +// ----------------------------------------------------------------------------- + diff --git a/thirdparty/liblastfm2/src/fingerprint/contrib/MadSource.h b/thirdparty/liblastfm2/src/fingerprint/contrib/MadSource.h new file mode 100644 index 000000000..c22cb6d5d --- /dev/null +++ b/thirdparty/liblastfm2/src/fingerprint/contrib/MadSource.h @@ -0,0 +1,69 @@ +/* + Copyright 2009 Last.fm Ltd. + Copyright 2009 John Stamp + + This file is part of liblastfm. + + liblastfm 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. + + liblastfm 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 liblastfm. If not, see . +*/ + +#ifndef __MP3_SOURCE_H__ +#define __MP3_SOURCE_H__ + +#include +#include +#include +#include +#include +#include + + +class MadSource : public lastfm::FingerprintableSource +{ +public: + MadSource(); + ~MadSource(); + + virtual void getInfo(int& lengthSecs, int& samplerate, int& bitrate, int& nchannels); + virtual void init(const QString& fileName); + virtual int updateBuffer(signed short* pBuffer, size_t bufferSize); + virtual void skip(const int mSecs); + virtual void skipSilence(double silenceThreshold = 0.0001); + virtual bool eof() const { return m_inputFile.atEnd(); } + +private: + static bool fetchData( QFile& mp3File, + unsigned char* pMP3_Buffer, + const int MP3_BufferSize, + mad_stream& madStream ); + + static bool isRecoverable(const mad_error& error, bool log = false); + + static std::string MadErrorString(const mad_error& error); + + struct mad_stream m_mad_stream; + struct mad_frame m_mad_frame; + mad_timer_t m_mad_timer; + struct mad_synth m_mad_synth; + + QFile m_inputFile; + + unsigned char* m_pMP3_Buffer; + static const int m_MP3_BufferSize = (5*8192); + QString m_fileName; + + size_t m_pcmpos; +}; + +#endif diff --git a/thirdparty/liblastfm2/src/fingerprint/contrib/VorbisSource.cpp b/thirdparty/liblastfm2/src/fingerprint/contrib/VorbisSource.cpp new file mode 100644 index 000000000..fd4defb17 --- /dev/null +++ b/thirdparty/liblastfm2/src/fingerprint/contrib/VorbisSource.cpp @@ -0,0 +1,204 @@ +/* + Copyright 2009 Last.fm Ltd. + Copyright 2009 John Stamp + + This file is part of liblastfm. + + liblastfm 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. + + liblastfm 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 liblastfm. If not, see . +*/ +#include "VorbisSource.h" +#include +#include +#include +#include +#include +#include +#include + +// These specify the output format +static const int wordSize = 2; // 16 bit output +static const int isSigned = 1; +#if __BIG_ENDIAN__ +static const int isBigEndian = 1; +#else +static const int isBigEndian = 0; +#endif + + +VorbisSource::VorbisSource() + : m_channels( 0 ) + , m_samplerate( 0 ) + , m_eof( false ) +{ + memset( &m_vf, 0, sizeof(m_vf) ); +} + +// --------------------------------------------------------------------- + +VorbisSource::~VorbisSource() +{ + // ov_clear() also closes the file + ov_clear( &m_vf ); +} + +// --------------------------------------------------------------------- + +void VorbisSource::init(const QString& fileName) +{ + m_fileName = fileName; + + if ( m_vf.datasource ) + { + std::cerr << "Warning: file already appears to be open"; + return; + } + + FILE *fp = fopen(QFile::encodeName(m_fileName), "rb" ); + if( !fp ) + throw std::runtime_error( "ERROR: Cannot open ogg file!" ); + + // See the warning about calling ov_open on Windows + if ( ov_test_callbacks( fp, &m_vf, NULL, 0, OV_CALLBACKS_DEFAULT ) < 0 ) + { + fclose( fp ); + throw std::runtime_error( "ERROR: This is not an ogg vorbis file!" ); + } + + ov_test_open( &m_vf ); + + // Don't fingerprint files with more than one logical bitstream + // They most likely contain more than one track + if ( ov_streams( &m_vf ) != 1 ) + throw std::runtime_error( "ERROR: ogg file contains multiple bitstreams" ); + + m_channels = ov_info( &m_vf, 0 )->channels; + m_samplerate = static_cast(ov_info( &m_vf, 0 )->rate); + m_eof = false; +} + +void VorbisSource::getInfo( int& lengthSecs, int& samplerate, int& bitrate, int& nchannels) +{ + // stream info + nchannels = ov_info( &m_vf, -1 )->channels; + samplerate = static_cast(ov_info( &m_vf, -1 )->rate); + lengthSecs = static_cast(ov_time_total( &m_vf, -1 ) + 0.5); + bitrate = static_cast(ov_bitrate( &m_vf, -1 )); +} + +// --------------------------------------------------------------------- + +void VorbisSource::skip( const int mSecs ) +{ + if ( mSecs < 0 ) + return; + + double ts = mSecs / 1000.0 + ov_time_tell( &m_vf ); + ov_time_seek( &m_vf, ts ); +} + +// --------------------------------------------------------------------- + +void VorbisSource::skipSilence(double silenceThreshold /* = 0.0001 */) +{ + silenceThreshold *= static_cast( std::numeric_limits::max() ); + + char sampleBuffer[4096]; + int bs = 0; + for (;;) + { + long charReadBytes = ov_read( &m_vf, sampleBuffer, 4096, isBigEndian, wordSize, isSigned, &bs ); + + // eof + if ( !charReadBytes ) + { + m_eof = true; + break; + } + if ( charReadBytes < 0 ) + { + // a bad bit of data: OV_HOLE || OV_EBADLINK + continue; + } + else if ( charReadBytes > 0 ) + { + double sum = 0; + int16_t *buf = reinterpret_cast(sampleBuffer); + switch ( m_channels ) + { + case 1: + for (long j = 0; j < charReadBytes/wordSize; j++) + sum += abs( buf[j] ); + break; + case 2: + for (long j = 0; j < charReadBytes/wordSize; j+=2) + sum += abs( (buf[j] >> 1) + (buf[j+1] >> 1) ); + break; + } + if ( sum >= silenceThreshold * static_cast(charReadBytes/wordSize/m_channels) ) + break; + } + } +} + +// --------------------------------------------------------------------- + +int VorbisSource::updateBuffer( signed short *pBuffer, size_t bufferSize ) +{ + char buf[ bufferSize * wordSize ]; + int bs = 0; + size_t charwrit = 0; //number of samples written to the output buffer + + for (;;) + { + long charReadBytes = ov_read( &m_vf, buf, static_cast(bufferSize * wordSize - charwrit), + isBigEndian, wordSize, isSigned, &bs ); + if ( !charReadBytes ) + { + m_eof = true; + break; // nothing else to read + } + + // Don't really need this though since we're excluding files that have + // more than one logical bitstream + if ( bs != 0 ) + { + vorbis_info *vi = ov_info( &m_vf, -1 ); + if ( m_channels != vi->channels || m_samplerate != vi->rate ) + { + std::cerr << "Files that change channel parameters or samplerate are currently not supported" << std::endl; + return 0; + } + } + + if( charReadBytes < 0 ) + { + std::cerr << "Warning: corrupt section of data, attempting to continue..." << std::endl; + continue; + } + + char* pBufferIt = reinterpret_cast(pBuffer) + charwrit; + charwrit += charReadBytes; + + assert( charwrit <= bufferSize * wordSize ); + memcpy( pBufferIt, buf, charReadBytes ); + + if (charwrit == bufferSize * wordSize) + return static_cast(charwrit/wordSize); + } + + return static_cast(charwrit/wordSize); +} + +// ----------------------------------------------------------------------------- + diff --git a/thirdparty/liblastfm2/src/fingerprint/contrib/VorbisSource.h b/thirdparty/liblastfm2/src/fingerprint/contrib/VorbisSource.h new file mode 100644 index 000000000..988ce6239 --- /dev/null +++ b/thirdparty/liblastfm2/src/fingerprint/contrib/VorbisSource.h @@ -0,0 +1,47 @@ +/* + Copyright 2009 Last.fm Ltd. + Copyright 2009 John Stamp + + This file is part of liblastfm. + + liblastfm 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. + + liblastfm 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 liblastfm. If not, see . +*/ +#ifndef __VORBIS_SOURCE_H__ +#define __VORBIS_SOURCE_H__ + +#include +#include + + +class VorbisSource : public lastfm::FingerprintableSource +{ +public: + VorbisSource(); + ~VorbisSource(); + virtual void getInfo(int& lengthSecs, int& samplerate, int& bitrate, int& nchannels); + virtual void init(const QString& fileName); + virtual int updateBuffer(signed short* pBuffer, size_t bufferSize); + virtual void skip(const int mSecs); + virtual void skipSilence(double silenceThreshold = 0.0001); + virtual bool eof() const { return m_eof; } + +private: + OggVorbis_File m_vf; + QString m_fileName; + int m_channels; + int m_samplerate; + bool m_eof; +}; + +#endif diff --git a/thirdparty/liblastfm2/src/fingerprint/contrib/lastfm-fingerprint.pro b/thirdparty/liblastfm2/src/fingerprint/contrib/lastfm-fingerprint.pro new file mode 100644 index 000000000..bd615e723 --- /dev/null +++ b/thirdparty/liblastfm2/src/fingerprint/contrib/lastfm-fingerprint.pro @@ -0,0 +1,13 @@ +QT = core xml network +LIBS += -L$$DESTDIR -llastfm -llastfm_fingerprint +LIBS += -lvorbisfile -lFLAC -lfaad -lmp4ff -lmad +SOURCES = AacSource.cpp FlacSource.cpp MadSource.cpp VorbisSource.cpp main.cpp + +mac { + INCLUDEPATH += /opt/local/include + LIBS += -L/opt/local/lib + + DEFINES += MACPORTS_SUCKS + SOURCES -= AacSource.cpp + LIBS -= -lmp4ff +} diff --git a/thirdparty/liblastfm2/src/fingerprint/contrib/main.cpp b/thirdparty/liblastfm2/src/fingerprint/contrib/main.cpp new file mode 100644 index 000000000..3b035f2d8 --- /dev/null +++ b/thirdparty/liblastfm2/src/fingerprint/contrib/main.cpp @@ -0,0 +1,173 @@ +/* + Copyright 2009 Last.fm Ltd. + Copyright 2009 John Stamp + + This file is part of liblastfm. + + liblastfm 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. + + liblastfm 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 liblastfm. If not, see . +*/ + +// ubuntu 9.04: sudo apt-get install libmad0-dev libvorbis-dev libflac-dev libfaac-dev +// macports: sudo port install libmad libvorbis libflac +// Windows: lol + +#include "MadSource.h" +#include "VorbisSource.h" +#include "FlacSource.h" +#include "AacSource.h" +#include +#include +#include +#include +#include +int typeOf(const QString& path); +lastfm::FingerprintableSource* factory(int type); +enum { MP3, OGG, FLAC, AAC, UNKNOWN }; +namespace lastfm { Track taglib(const QString& path); } + + +int main(int argc, char** argv) try +{ + if (argc < 2) { + std::cerr << "usage: " << argv[0] << " path" << std::endl; + return 1; + } + + QCoreApplication app(argc, argv); + QEventLoop loop; + + QString const path = QFile::decodeName(argv[1]); + + lastfm::Track t = lastfm::taglib(path); //see contrib //TODO mbid + lastfm::Fingerprint fp(t); + if (fp.id().isNull()) { + lastfm::FingerprintableSource* src = factory(typeOf(path)); + fp.generate(src); + QNetworkReply* reply = fp.submit(); + loop.connect(reply, SIGNAL(finished()), SLOT(quit())); + fp.decode(reply); + } + + QNetworkReply* reply = fp.id().getSuggestions(); + loop.connect(reply, SIGNAL(finished()), SLOT(quit())); + + std::cout << reply->readAll().data() << std::endl; //returns XML + return 0; +} +catch (std::exception& e) +{ + std::cerr << e.what() << std::endl; +} + +lastfm::FingerprintableSource* factory(int type) +{ + switch (type) { + case MP3: return new MadSource; + case OGG: return new VorbisSource; + case FLAC: return new FlacSource; + #ifndef MACPORTS_SUCKS + case AAC: return new AacSource; + #endif + default: throw std::runtime_error("Cannot handle filetype"); + } +} + +int typeOf(const QString& fileName) +{ + QStringList parts = fileName.split( "." ); + QString extension; + if ( parts.size() > 1 ) + extension = parts.last(); + + // Let's be trusting about extensions + if ( extension.toLower() == "mp3" ) + return MP3; + else if ( extension.toLower() == "ogg" ) + return OGG; + else if ( extension.toLower() == "oga" ) + return FLAC; + else if ( extension.toLower() == "flac" ) + return FLAC; + else if ( extension.toLower() == "aac" ) + return AAC; + else if ( extension.toLower() == "m4a" ) + return AAC; + + // So much for relying on extensions. Let's try file magic instead. + FILE *fp = NULL; + unsigned char header[35]; + + fp = fopen(QFile::encodeName(fileName), "rb"); + if ( !fp ) + { + return UNKNOWN; + } + int fType = UNKNOWN; + fread( header, 1, 35, fp ); + + // Some formats can have ID3 tags (or not), so let's just + // get them out of the way first before we check what we have. + if ( memcmp( header, "ID3", 3) == 0 ) + { + int tagsize = 0; + /* high bit is not used */ + tagsize = (header[6] << 21) | (header[7] << 14) | + (header[8] << 7) | (header[9] << 0); + + tagsize += 10; + fseek( fp, tagsize, SEEK_SET ); + fread( header, 1, 35, fp ); + } + + if ( (header[0] == 0xFF) && ((header[1] & 0xFE) == 0xFA ) ) + { + fType = MP3; + } + else if ( memcmp(header, "OggS", 4) == 0 ) + { + if ( memcmp(&header[29], "vorbis", 6) == 0 ) + { + // ogg vorbis (.ogg) + fType = OGG; + } + else if ( memcmp(&header[29], "FLAC", 4) == 0 ) + { + // ogg flac (.oga) + fType = FLAC; + } + } + else if ( memcmp(header, "fLaC", 4 ) == 0 ) + { + // flac file + fType = FLAC; + } + else if ( (header[0] == 0xFF) && ((header[1] & 0xF6) == 0xF0) ) + { + // aac adts + fType = AAC; + } + else if (memcmp(header, "ADIF", 4) == 0) + { + // aac adif + fType = AAC; + } + else if ( memcmp( &header[4], "ftyp", 4 ) == 0 ) + { + // mp4 header: aac + fType = AAC; + } + + fclose(fp); + return fType; +} diff --git a/thirdparty/liblastfm2/src/fingerprint/fingerprint.pro b/thirdparty/liblastfm2/src/fingerprint/fingerprint.pro new file mode 100644 index 000000000..043ad7bfb --- /dev/null +++ b/thirdparty/liblastfm2/src/fingerprint/fingerprint.pro @@ -0,0 +1,25 @@ +TEMPLATE = lib +TARGET = lastfm_fingerprint +LIBS += -L$$DESTDIR -llastfm +QT = core xml network sql +include( _files.qmake ) +DEFINES += LASTFM_FINGERPRINT_LIB + +INSTALLS = target +target.path = /lib + +mac:CONFIG( app_bundle ) { + LIBS += libfftw3f.a libsamplerate.a -L/opt/local/include + INCLUDEPATH += /opt/local/include:/opt/qt/qt-current/lib/QtSql.framework/Include/ +}else{ + INCLUDEPATH += /opt/qt/qt-current/lib/QtSql.framework/Include/ + CONFIG += link_pkgconfig + PKGCONFIG += samplerate + win32 { + CONFIG += link_pkgconfig + DEFINES += __NO_THREAD_CHECK + QMAKE_LFLAGS_DEBUG += /NODEFAULTLIB:msvcrt.lib /NODEFAULTLIB:libcmt.lib + } + PKGCONFIG += fftw3f + +} diff --git a/thirdparty/liblastfm2/src/fingerprint/fplib/CircularArray.h b/thirdparty/liblastfm2/src/fingerprint/fplib/CircularArray.h new file mode 100644 index 000000000..bfec5a8fd --- /dev/null +++ b/thirdparty/liblastfm2/src/fingerprint/fplib/CircularArray.h @@ -0,0 +1,292 @@ +/* + Copyright 2005-2009 Last.fm Ltd. + + This file is part of liblastfm. + + liblastfm 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. + + liblastfm 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 liblastfm. If not, see . +*/ +#ifndef __CIRCULAR_ARRAY_H +#define __CIRCULAR_ARRAY_H + +#include +#include +#include +#include +#include // for memset +#include // for max + +#ifndef NULL +#define NULL 0 +#endif + +template< typename T > +class CircularArray +{ + +public: + + typedef size_t size_type; + + ///////////////////////////////////////////////////////////// + + // IMPORTANT: The distance must be redefined!! + // See declaration of iterator from stl_iterator_base_types.h: + // template + // struct iterator { ... + + // ---------- Forward declarations + + class iterator : + public std::iterator + { + // it should be by default because is an inner class, but I put it just to be sure.. + friend class CircularArray; + + private: + iterator( size_type idx, T* pData, size_type size ) : _idx(idx), _pData(pData), _size(size) {} + + public: + + //typedef random_access_iterator_tag iterator_category; + + iterator() : _idx(0), _pData(NULL) {} + + iterator& operator++() + { // preincrement + _idx = (_idx + 1) % _size; + return (*this); + } + + iterator operator++(int) + { // postincrement + iterator _Tmp = *this; + _idx = (_idx + 1) % _size; + return (_Tmp); + } + + void operator+=(size_type offs) + { + this->_idx = (_idx + offs) % _size; + } + + iterator operator+(size_type offs) const + { + size_type newIdx = (_idx + offs) % _size; + iterator _Tmp(newIdx, _pData, _size); + return _Tmp; + } + + // return the distance between this iterator and it + size_t operator-(const iterator& it) const + { + if ( this->_idx > it._idx ) + return this->_idx - it._idx; + else + return this->_idx + (_size - it._idx); + } + + iterator operator-(size_type offs) const + { + size_type newIdx; + + if ( offs <= _idx ) + newIdx = _idx - offs; + else + newIdx = _size - ((_idx - offs) % _size); // note: should be ok, but to be checked better + + iterator _Tmp(newIdx, _pData, _size); + return _Tmp; + } + + iterator& operator--() + { // predecrement + if (_idx == 0) + _idx = _size - 1; + else + --_idx; + return (*this); + } + + iterator operator--(int) + { // postdecrement + iterator _Tmp = *this; + if (_idx == 0) + _idx = _size - 1; + else + --_idx; + return (_Tmp); + } + + T& operator*() const + { // return designated object + return _pData[_idx]; + } + + T* operator->() const + { // return pointer to class object + return &_pData[_idx]; + } + + /* T& operator=(const T& right) + { // assign reference right to _val + return ( this->_idx = right._idx ); + }*/ + + bool operator==(const iterator& right) const + { // test for iterator equality + return ( this->_idx == right._idx ); + } + + bool operator!=(const iterator& right) const + { // test for iterator inequality + return ( this->_idx != right._idx ); + } + + protected: + size_type _idx; + T* _pData; + size_type _size; + }; + + ///////////////////////////////////////////////////////////// + + + CircularArray() + : _headIdx(0), _pData(NULL), _size(0) + { } + + CircularArray( size_type size ) + : _headIdx(0), _pData(NULL) + { + this->resize(size); + } + + CircularArray( size_type size, const T& init ) + : _headIdx(0), _pData(NULL) + { + this->resize(size, init); + } + + ~CircularArray() + { + this->clear(); + } + + // remember: it is not working (yet!) with negative numbers! + T& operator[](size_type offset) + { + return _pData[ (_headIdx + offset) % _size ]; + } + + void resize( size_type size ) + { + _headIdx = 0; + if ( size == _size ) + return; + + this->clear(); + _pData = new T[size]; + _size = size; + } + + void resize( size_type size, const T& init ) + { + this->resize(size, false); + this->fill(init); + } + + void fill( const T& val ) + { + for (size_type i=0; i<_size; ++i) + _pData[i] = val; + } + + void zero_fill() + { + memset( _pData, 0, _size * sizeof(T) ); + } + + bool empty() const + { + return ( _pData == NULL ); + } + + void clear() + { + if (_pData) + delete [] _pData; + _pData = NULL; + _headIdx = 0; + _size = 0; + } + + iterator head() const + { + if (_pData == NULL) + std::cerr << "WARNING: iterator in CircularArray points to an empty CircularArray" << std::endl; + return iterator(_headIdx, _pData, _size); + } + + void shift_head( int offset ) + { + if ( offset < 0) + { + int mod = (-offset) % (int)_size; + mod -= (int)_headIdx; + _headIdx = _size - mod; + } + else + _headIdx = (_headIdx + offset) % _size; + } + + size_type size() const + { + return _size; + } + + //// to be changed to an input forward iterator + //template + //void get_data( TIterator toFillIt, size_type size = 0 ) + //{ + // if ( size == 0 ) + // size = _size; + // iterator it = head(); + // + // for (size_type i = 0; i < size; ++i) + // *(toFillIt++) = *(it++); + //} + + // IMPORTANT! Destination buffer MUST be the same size! + void copy_buffer( T* pDest ) + { + memcpy( pDest, _pData, sizeof(T)*_size ); + } + + // returns the buffer + T* get_buffer() const + { + return _pData; + } + + +private: + + size_type _headIdx; // index + T* _pData; // array of data + size_type _size; // size of data + +}; + +#endif // __CIRCULAR_ARRAY_H diff --git a/thirdparty/liblastfm2/src/fingerprint/fplib/Filter.cpp b/thirdparty/liblastfm2/src/fingerprint/fplib/Filter.cpp new file mode 100644 index 000000000..eed4ea3a3 --- /dev/null +++ b/thirdparty/liblastfm2/src/fingerprint/fplib/Filter.cpp @@ -0,0 +1,128 @@ +/* + Copyright 2005-2009 Last.fm Ltd. + + This file is part of liblastfm. + + liblastfm 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. + + liblastfm 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 liblastfm. If not, see . +*/ +#include +#include // for max +#include + +#include "Filter.h" +#include "fp_helper_fun.h" + +using namespace std; + +namespace fingerprint +{ + +Filter::Filter(unsigned int id, float threshold, float weight) +: id(id), threshold(threshold), weight(weight) +{ + float time_rate = 1.5; + + unsigned int t = 1; + vector time_lengths; + + while (t < KEYWIDTH) + { + time_lengths.push_back(t); + t = max( static_cast( round__(time_rate*t) ) + + static_cast( round__(time_rate*t) % 2), + t+1 ); + } + + unsigned int filter_count = 0; + + for (wt = 1; wt <= time_lengths.size(); wt++) + { + for (wb = 1; wb <= NBANDS; wb++) + { + for (first_band = 1; first_band <= NBANDS - wb + 1; + first_band++) + { + unsigned int time = time_lengths[wt-1]; + filter_count++; + + if (filter_count == id) + { + wt = time_lengths[wt-1]; + filter_type = 1; + return; + } + + if (time > 1) + { + filter_count++; + if (filter_count == id) + { + wt = time_lengths[wt-1]; + filter_type = 2; + return; + } + } + + if (wb > 1) + { + filter_count++; + if (filter_count == id) + { + wt = time_lengths[wt-1]; + filter_type = 3; + return; + } + } + + if (time > 1 && wb > 1) + { + filter_count++; + if (filter_count == id) + { + wt = time_lengths[wt-1]; + filter_type = 4; + return; + } + } + + if (time > 3) + { + filter_count++; + if (filter_count == id) + { + wt = time_lengths[wt-1]; + filter_type = 5; + return; + } + } + + if (wb > 3) + { + filter_count++; + if (filter_count == id) + { + wt = time_lengths[wt-1]; + filter_type = 6; + return; + } + } + + } // for first_band + } // for wb + } // for wt +} + +} // end of namespace fingerprint + +// ----------------------------------------------------------------------------- diff --git a/thirdparty/liblastfm2/src/fingerprint/fplib/Filter.h b/thirdparty/liblastfm2/src/fingerprint/fplib/Filter.h new file mode 100644 index 000000000..04185eec2 --- /dev/null +++ b/thirdparty/liblastfm2/src/fingerprint/fplib/Filter.h @@ -0,0 +1,47 @@ +/* + Copyright 2005-2009 Last.fm Ltd. + + This file is part of liblastfm. + + liblastfm 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. + + liblastfm 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 liblastfm. If not, see . +*/ +#ifndef __FILTER_H +#define __FILTER_H + +namespace fingerprint +{ + +struct Filter +{ + /// Constructs a new filter with id. + Filter(unsigned int id, float threshold, float weight); + + unsigned int id; //< filter id + unsigned int wt; //< time width + unsigned int first_band; //< first band + unsigned int wb; //< band width + unsigned int filter_type; //< filter type + + float threshold; //< threshold for filter + float weight; //< filter weight + + // number of frames in time + static const unsigned int KEYWIDTH = 100; + // number of bands to divide the signal (log step) + static const unsigned int NBANDS = 33; +}; + +} + +#endif // __FILTER_H diff --git a/thirdparty/liblastfm2/src/fingerprint/fplib/FingerprintExtractor.cpp b/thirdparty/liblastfm2/src/fingerprint/fplib/FingerprintExtractor.cpp new file mode 100644 index 000000000..305aad7b5 --- /dev/null +++ b/thirdparty/liblastfm2/src/fingerprint/fplib/FingerprintExtractor.cpp @@ -0,0 +1,786 @@ +/* + Copyright 2005-2009 Last.fm Ltd. + + This file is part of liblastfm. + + liblastfm 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. + + liblastfm 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 liblastfm. If not, see . +*/ +#include +#include +#include +#include +#include +#include +#include +#include + +#include // libsamplerate + +#include "FingerprintExtractor.h" +#include "fp_helper_fun.h" // for GroupData +#include "Filter.h" +#include "FloatingAverage.h" +#include "OptFFT.h" + +////////////////////////////////////////////////////////////////////////// + +namespace fingerprint +{ + +using namespace std; +static const int NUM_FRAMES_CLIENT = 32; // ~= 10 secs. + +enum eProcessType +{ + PT_UNKNOWN, + PT_FOR_QUERY, + PT_FOR_FULLSUBMIT +}; + +////////////////////////////////////////////////////////////////////////// + +class PimplData +{ + +public: + + PimplData() + : m_pDownsampledPCM(NULL), m_pDownsampledCurrIt(NULL), + m_normalizedWindowMs(static_cast(NORMALIZATION_SKIP_SECS * 1000 * 2)), + m_compensateBufferSize(FRAMESIZE-OVERLAPSAMPLES + Filter::KEYWIDTH * OVERLAPSAMPLES), + m_downsampledProcessSize(NUM_FRAMES_CLIENT*FRAMESIZE), + // notice that the buffer has extra space on either side for the normalization window + m_fullDownsampledBufferSize( m_downsampledProcessSize + // the actual processed part + m_compensateBufferSize + // a compensation buffer for the fft + ((m_normalizedWindowMs * DFREQ / 1000) / 2) ), // a compensation buffer for the normalization + m_normWindow(m_normalizedWindowMs * DFREQ / 1000), + m_pFFT(NULL), m_pDownsampleState(NULL), m_processType(PT_UNKNOWN) + { + m_pFFT = new OptFFT(m_downsampledProcessSize + m_compensateBufferSize); + m_pDownsampledPCM = new float[m_fullDownsampledBufferSize]; + + // the end of ||-------m_bufferSize-------|-cb-|---norm/2---|| + // ^-- pEndDownsampledBuf + m_pEndDownsampledBuf = m_pDownsampledPCM + m_fullDownsampledBufferSize; + + // loading filters + size_t numFilters = sizeof(rFilters) / sizeof(RawFilter) ; + for (size_t i = 0; i < numFilters; ++i) + m_filters.push_back( Filter( rFilters[i].ftid, rFilters[i].thresh, rFilters[i].weight ) ); + + } + + ~PimplData() + { + if ( m_pFFT ) + delete m_pFFT; + m_pFFT = NULL; + if ( m_pDownsampledPCM ) + delete [] m_pDownsampledPCM; + m_pDownsampledPCM = NULL; + + if ( m_pDownsampleState ) + src_delete(m_pDownsampleState) ; + + } + + float* m_pDownsampledPCM; + float* m_pDownsampledCurrIt; + + const unsigned int m_normalizedWindowMs; + const size_t m_compensateBufferSize; + const size_t m_downsampledProcessSize; + const size_t m_fullDownsampledBufferSize; + + FloatingAverage m_normWindow; + OptFFT* m_pFFT; + + ////////////////////////////////////////////////////////////////////////// + + // libsamplerate + SRC_STATE* m_pDownsampleState; + SRC_DATA m_downsampleData; + + vector m_floatInData; + + ////////////////////////////////////////////////////////////////////////// + + + bool m_groupsReady; + bool m_preBufferPassed; + + eProcessType m_processType; + + size_t m_toSkipSize; + size_t m_toSkipMs; + + size_t m_skippedSoFar; + bool m_skipPassed; + + float* m_pEndDownsampledBuf; + + int m_freq; + int m_nchannels; + + unsigned int m_lengthMs; + int m_minUniqueKeys; + unsigned int m_uniqueKeyWindowMs; + + unsigned int m_toProcessKeys; + unsigned int m_totalWindowKeys; + + vector m_filters; + + deque m_groupWindow; + vector m_groups; + unsigned int m_processedKeys; + + vector m_partialBits; // here just to avoid reallocation + +#if __BIG_ENDIAN__ + +#define reorderbits(X) ((((unsigned int)(X) & 0xff000000) >> 24) | \ + (((unsigned int)(X) & 0x00ff0000) >> 8) | \ + (((unsigned int)(X) & 0x0000ff00) << 8) | \ + (((unsigned int)(X) & 0x000000ff) << 24)) + + vector m_bigEndianGroups; +#endif +}; + +////////////////////////////////////////////////////////////////////////// + +void initCustom( PimplData& pd, + int freq, int nchannels, + unsigned int lengthMs, unsigned int skipMs, + int minUniqueKeys, unsigned int uniqueKeyWindowMs, int duration ); + +inline float getRMS( const FloatingAverage& signal ); +unsigned int processKeys( deque& groups, size_t size, PimplData& pd ); +void integralImage( float** ppFrames, unsigned int nFrames ); +void computeBits( vector& bits, + const vector& f, + float ** frames, unsigned int nframes ); + + +void src_short_to_float_and_mono_array(const short *in, float *out, int srclen, int nchannels); + +////////////////////////////////////////////////////////////////////////// + +// ----------------------------------------------------------------------------- + +FingerprintExtractor::FingerprintExtractor() +: m_pPimplData(NULL) +{ + m_pPimplData = new PimplData(); +} + +// ----------------------------------------------------------------------------- + +FingerprintExtractor::~FingerprintExtractor() +{ + if ( m_pPimplData ) + delete m_pPimplData; +} + +// ----------------------------------------------------------------------------- + +size_t FingerprintExtractor::getToSkipMs() +{ return m_pPimplData->m_toSkipMs; } + +// ----------------------------------------------------------------------------- + +size_t FingerprintExtractor::getMinimumDurationMs() +{ + return static_cast( (QUERY_SIZE_SECS + NORMALIZATION_SKIP_SECS * 2 + GUARD_SIZE_SECS) * 1000 ); +} + +// ----------------------------------------------------------------------------- + +size_t FingerprintExtractor::getVersion() +{ return FINGERPRINT_LIB_VERSION; } + +// ----------------------------------------------------------------------------- + +void FingerprintExtractor::initForQuery(int freq, int nchannels, int duration ) +{ + m_pPimplData->m_skipPassed = false; + m_pPimplData->m_processType = PT_FOR_QUERY; + + if ( !m_pPimplData ) + throw std::runtime_error("Not enough RAM to allocate the fingerprinter!"); + + initCustom( *m_pPimplData, + freq, nchannels, + static_cast(QUERY_SIZE_SECS * 1000), + static_cast(QUERY_START_SECS * 1000), + MIN_UNIQUE_KEYS, + static_cast(UPDATE_SIZE_SECS * 1000), duration ); +} + +// ----------------------------------------------------------------------------- + +void FingerprintExtractor::initForFullSubmit(int freq, int nchannels ) +{ + m_pPimplData->m_skipPassed = true; + m_pPimplData->m_processType = PT_FOR_FULLSUBMIT; + + if ( !m_pPimplData ) + throw std::runtime_error("Not enough RAM to allocate the fingerprinter!"); + + initCustom( *m_pPimplData, + freq, nchannels, + numeric_limits::max(), + 0, MIN_UNIQUE_KEYS, 0, -1 ); +} + +// ----------------------------------------------------------------------------- + +void initCustom( PimplData& pd, + int freq, int nchannels, + unsigned int lengthMs, + unsigned int skipMs, + int minUniqueKeys, + unsigned int uniqueKeyWindowMs, int duration ) +{ + ////////////////////////////////////////////////////////////////////////// + pd.m_freq = freq; + pd.m_nchannels = nchannels; + pd.m_lengthMs = lengthMs; + pd.m_minUniqueKeys = minUniqueKeys; + pd.m_uniqueKeyWindowMs = uniqueKeyWindowMs; + ////////////////////////////////////////////////////////////////////////// + + // *********************************************************************** + if ( pd.m_pDownsampleState ) + pd.m_pDownsampleState = src_delete(pd.m_pDownsampleState) ; + pd.m_pDownsampleState = src_new (SRC_SINC_FASTEST, 1, NULL) ; + pd.m_downsampleData.src_ratio = FDFREQ / freq; + // *********************************************************************** + + ////////////////////////////////////////////////////////////////////////// + if ( pd.m_processType == PT_FOR_FULLSUBMIT ) + skipMs = 0; // make sure + else if ( duration > 0 ) + { + // skip + size + right normalization window + FFT guard + // + int stdDurationMs = static_cast((QUERY_START_SECS + QUERY_SIZE_SECS + NORMALIZATION_SKIP_SECS + GUARD_SIZE_SECS) * 1000); + int actualDurationMs = duration * 1000; + // compute the actual skipMs depending on the duration + if ( actualDurationMs < stdDurationMs ) + skipMs -= max( stdDurationMs - actualDurationMs, 0 ); + } + + pd.m_toSkipMs = max( static_cast(skipMs) - static_cast((pd.m_normalizedWindowMs/2)), 0 ); + pd.m_toSkipSize = static_cast( freq * nchannels * + (pd.m_toSkipMs / 1000.0) ); // half the norm window in secs; + + //if ( pd.m_processType == PT_FOR_QUERY && skipMs > pd.m_normalizedWindowMs/2 ) + //{ + // pd.m_toSkipMs = skipMs - (pd.m_normalizedWindowMs/2); + // pd.m_toSkipSize = static_cast( freq * nchannels * + // (pd.m_toSkipMs / 1000.0) ); // half the norm window in secs + //} + //else + //{ + // pd.m_toSkipMs = 0; + // pd.m_toSkipSize = 0; // half of the normalization window will be skipped in ANY case + //} + + pd.m_skippedSoFar = 0; + pd.m_groupsReady = false; + pd.m_preBufferPassed = false; + + // prepare the position for pre-buffering + pd.m_pDownsampledCurrIt = pd.m_pDownsampledPCM + (pd.m_downsampledProcessSize - (pd.m_normWindow.size() / 2) ); + + pd.m_toProcessKeys = fingerprint::getTotalKeys(pd.m_lengthMs);// (m_lengthMs * DFREQ) / (1000 * OVERLAPSAMPLES) + 1; + pd.m_totalWindowKeys = fingerprint::getTotalKeys(pd.m_uniqueKeyWindowMs); //(m_uniqueKeyWindowMs * DFREQ) / (1000 * OVERLAPSAMPLES) + 1; + + if (pd.m_toProcessKeys == 1) + pd.m_toProcessKeys = 0; + if (pd.m_totalWindowKeys == 1) + pd.m_totalWindowKeys = 0; + + pd.m_processedKeys = 0; + + pd.m_groupWindow.clear(); + pd.m_processedKeys = 0; +} + +// ----------------------------------------------------------------------------- + + +// * cb = compensate buffer size +// * norm = floating normalization window size +// +// PREBUFFER: +// (-------m_bufferSize-------) +// || EMPTY |---norm/2---|-cb-|---norm/2---|| +// 1. {--------read frames-----------} +// 2. {--read normalize window--} +// 3. {----} normalize +// +// 1. read [norm + cb] frames to m_bufferSize - norm/2 +// 2. read [m_buffersize - norm/2...m_buffersize + norm/2] into normalize window +// 3. normalize [m_bufferSize..m_bufferSize+cb] +// +// PROCESS: +// +// ||-------m_bufferSize-------|-cb-|---norm/2---|| +// 1. <--------------------------{------copy-------} +// 2. {--------read frames-------} +// 3. {---------normalize--------} +// 4. {------fft/process/whatevs------} +// +// 1. copy [m_bufferSize..m_bufferSize + cb + norm/2] to beginning +// 2. read m_bufferSize frames to cb + norm/2 +// 3. normalize [cb..m_bufferSize+cb] +// 4. fft/process/whatevs [0...m_bufferSize+cb] +// +// repeat until enough blocks processed and enough groups! +// +bool FingerprintExtractor::process( const short* pPCM, size_t num_samples, bool end_of_stream ) +{ + if ( num_samples == 0 ) + return false; + + // easier read + PimplData& pd = *m_pPimplData; + + if ( pd.m_processType == PT_UNKNOWN ) + throw std::runtime_error("Please call initForQuery() or initForFullSubmit() before process()!"); + + const short* pSourcePCMIt = pPCM; + const short* pSourcePCMIt_end = pPCM + num_samples; + + if ( !pd.m_skipPassed ) + { + // needs to skip data? (reminder: the query needs to skip QUERY_START_SECS (- half of the normalization window) + if ( pd.m_skippedSoFar + num_samples > pd.m_toSkipSize ) + { + pSourcePCMIt = pPCM + (pd.m_toSkipSize - pd.m_skippedSoFar); + pd.m_skipPassed = true; + } + else + { + // need more data + pd.m_skippedSoFar += num_samples; + return false; + } + } + + pair readData(0,0); + pd.m_downsampleData.end_of_input = end_of_stream ? 1 : 0; + + ////////////////////////////////////////////////////////////////////////// + // PREBUFFER: + if ( !pd.m_preBufferPassed ) + { + // 1. downsample [norm + cb] frames to m_bufferSize - norm/2 + pd.m_floatInData.resize( (pSourcePCMIt_end - pSourcePCMIt) / pd.m_nchannels); + src_short_to_float_and_mono_array( pSourcePCMIt, + &(pd.m_floatInData[0]), static_cast(pSourcePCMIt_end - pSourcePCMIt), + pd.m_nchannels); + + pd.m_downsampleData.data_in = &(pd.m_floatInData[0]); + pd.m_downsampleData.input_frames = static_cast(pd.m_floatInData.size()); + + pd.m_downsampleData.data_out = pd.m_pDownsampledCurrIt; + pd.m_downsampleData.output_frames = static_cast(pd.m_pEndDownsampledBuf - pd.m_pDownsampledCurrIt); + + int err = src_process(pd.m_pDownsampleState, &(pd.m_downsampleData)); + if ( err ) + throw std::runtime_error( src_strerror(err) ); + + pd.m_pDownsampledCurrIt += pd.m_downsampleData.output_frames_gen; + + if ( pd.m_pDownsampledCurrIt != pd.m_pEndDownsampledBuf ) + return false; // NEED MORE DATA + + pSourcePCMIt += pd.m_downsampleData.input_frames_used * pd.m_nchannels; + + size_t pos = pd.m_downsampledProcessSize; + size_t window_pos = pd.m_downsampledProcessSize - pd.m_normWindow.size() / 2; + const size_t end_window_pos = window_pos + pd.m_normWindow.size(); + + // 2. read [m_buffersize - norm/2...m_buffersize + norm/2] into normalize window + for (; window_pos < end_window_pos ; ++window_pos) + pd.m_normWindow.add(pd.m_pDownsampledPCM[window_pos] * pd.m_pDownsampledPCM[window_pos]); + + // 3. normalize [m_bufferSize..m_bufferSize+cb] + for (; pos < pd.m_downsampledProcessSize + pd.m_compensateBufferSize; ++pos, ++window_pos) + { + pd.m_pDownsampledPCM[pos] /= getRMS(pd.m_normWindow); + pd.m_normWindow.add(pd.m_pDownsampledPCM[window_pos] * pd.m_pDownsampledPCM[window_pos]); + } + + pd.m_preBufferPassed = true; + } + + ////////////////////////////////////////////////////////////////////////// + // PROCESS: + + bool found_enough_unique_keys = false; + while (pd.m_toProcessKeys == 0 || pd.m_processedKeys < pd.m_toProcessKeys || !found_enough_unique_keys) + { + + // 1. copy [m_bufferSize..m_bufferSize + cb + norm/2] to beginning + if ( pd.m_pDownsampledCurrIt == pd.m_pEndDownsampledBuf ) + { + memcpy( pd.m_pDownsampledPCM, pd.m_pDownsampledPCM + pd.m_downsampledProcessSize, + (pd.m_compensateBufferSize + (pd.m_normWindow.size() / 2)) * sizeof(float)); + pd.m_pDownsampledCurrIt = pd.m_pDownsampledPCM + (pd.m_compensateBufferSize + (pd.m_normWindow.size() / 2)); + } + + // 2. read m_bufferSize frames to cb + norm/2 + pd.m_floatInData.resize( (pSourcePCMIt_end - pSourcePCMIt) / pd.m_nchannels); + + if ( pd.m_floatInData.empty() ) + return false; + + src_short_to_float_and_mono_array( pSourcePCMIt, + &(pd.m_floatInData[0]), static_cast(pSourcePCMIt_end - pSourcePCMIt), + pd.m_nchannels); + + pd.m_downsampleData.data_in = &(pd.m_floatInData[0]); + pd.m_downsampleData.input_frames = static_cast(pd.m_floatInData.size()); + + pd.m_downsampleData.data_out = pd.m_pDownsampledCurrIt; + pd.m_downsampleData.output_frames = static_cast(pd.m_pEndDownsampledBuf - pd.m_pDownsampledCurrIt); + + int err = src_process(pd.m_pDownsampleState, &(pd.m_downsampleData)); + if ( err ) + throw std::runtime_error( src_strerror(err) ); + + pd.m_pDownsampledCurrIt += pd.m_downsampleData.output_frames_gen; + + if ( pd.m_pDownsampledCurrIt != pd.m_pEndDownsampledBuf && !end_of_stream ) + return false; // NEED MORE DATA + + //pSourcePCMIt += readData.second; + pSourcePCMIt += pd.m_downsampleData.input_frames_used * pd.m_nchannels; + + // ******************************************************************** + + // 3. normalize [cb..m_bufferSize+cb] + size_t pos = static_cast(pd.m_compensateBufferSize); + size_t window_pos = static_cast(pd.m_compensateBufferSize + (pd.m_normWindow.size() / 2)); + + for(; pos < pd.m_downsampledProcessSize + pd.m_compensateBufferSize /* m_fullDownsampledBufferSize*/; ++pos, ++window_pos) + { + pd.m_pDownsampledPCM[pos] /= getRMS(pd.m_normWindow); + pd.m_normWindow.add(pd.m_pDownsampledPCM[window_pos] * pd.m_pDownsampledPCM[window_pos]); + } + + // 4. fft/process/whatevs [0...m_bufferSize+cb] + pd.m_processedKeys += processKeys(pd.m_groupWindow, pos, pd); + + // we have too many keys, now we have to chop either one end or the other + if (pd.m_toProcessKeys != 0 && pd.m_processedKeys > pd.m_toProcessKeys) + { + // set up window begin and end + deque::iterator itBeg = pd.m_groupWindow.begin(), itEnd = pd.m_groupWindow.end(); + unsigned int offset_left, offset_right; + + found_enough_unique_keys = + fingerprint::findSignificantGroups( itBeg, itEnd, offset_left, offset_right, pd.m_toProcessKeys, + pd.m_totalWindowKeys, pd.m_minUniqueKeys); + + // if we're happy with this set, snip the beginning and end of the grouped keys + if (found_enough_unique_keys) + { + itBeg->count -= offset_left; + if (offset_right > 0 && itEnd != pd.m_groupWindow.end()) + { + itEnd->count = offset_right; + ++itEnd; + } + } + + // chop the deque + copy(itBeg, itEnd, pd.m_groupWindow.begin()); + pd.m_groupWindow.resize(itEnd - itBeg); + + // recalc keys + pd.m_processedKeys = 0; + for (deque::const_iterator it = pd.m_groupWindow.begin(); it != pd.m_groupWindow.end(); ++it) + pd.m_processedKeys += it->count; + } + + if ( end_of_stream ) + break; + + } // while (totalKeys == 0 || keys < totalKeys || !found_enough_unique_keys) + + + if (pd.m_toProcessKeys != 0 && pd.m_processedKeys < pd.m_toProcessKeys) + throw std::runtime_error("Couldn't deliver the requested number of keys (it's the file too short?)"); + + if ((pd.m_toProcessKeys != 0 && !found_enough_unique_keys) || + (pd.m_toProcessKeys == 0 && !enoughUniqueGoodGroups(pd.m_groupWindow.begin(), pd.m_groupWindow.end(), pd.m_minUniqueKeys))) + { + throw std::runtime_error("Not enough unique keys (it's the file too short?)"); + } + + // copy to a vector so that they can be returned as contiguous data + pd.m_groups.resize(pd.m_groupWindow.size()); + copy(pd.m_groupWindow.begin(), pd.m_groupWindow.end(), pd.m_groups.begin()); + + pd.m_groupsReady = true; + pd.m_processType = PT_UNKNOWN; + return true; +} + +// ----------------------------------------------------------------------------- + +pair FingerprintExtractor::getFingerprint() +{ + // easier read + PimplData& pd = *m_pPimplData; + + if ( pd.m_groupsReady ) + { +#if __BIG_ENDIAN__ + pd.m_bigEndianGroups.resize(pd.m_groups.size()); + for ( size_t i = 0; i < pd.m_groups.size(); ++i ) + { + pd.m_bigEndianGroups[i].key = reorderbits(pd.m_groups[i].key); + pd.m_bigEndianGroups[i].count = reorderbits(pd.m_groups[i].count); + } + + return make_pair(reinterpret_cast(&pd.m_bigEndianGroups[0]), pd.m_bigEndianGroups.size() * sizeof(GroupData) ); + +#else + return make_pair(reinterpret_cast(&pd.m_groups[0]), pd.m_groups.size() * sizeof(GroupData) ); +#endif + } + else + return make_pair(reinterpret_cast(0), 0); // here's where null_ptr would become useful! +} + +// ----------------------------------------------------------------------------- +// ----------------------------------------------------------------------------- +// ----------------------------------------------------------------------------- + +float getRMS(const FloatingAverage& signal) +{ + // we don't want to normalize by the real rms, because excessive clipping will occur + float rms = sqrtf(static_cast(signal.getAverage())) * 10.0F; + + if (rms < 0.1F) + rms = 0.1F; + else if (rms > 3.0F) + rms = 3.0F; + + return rms; +} + +// ----------------------------------------------------------------------------- + +unsigned int processKeys( deque& groups, size_t size, PimplData& pd ) +{ + size_t read_size = min(size, pd.m_downsampledProcessSize + pd.m_compensateBufferSize); + + unsigned int numFrames = pd.m_pFFT->process(pd.m_pDownsampledPCM, read_size); + + if ( numFrames <= Filter::KEYWIDTH ) + return 0; // skip it when the number of frames is too small + + float** ppFrames = pd.m_pFFT->getFrames(); + + integralImage(ppFrames, numFrames); + computeBits(pd.m_partialBits, pd.m_filters, ppFrames, numFrames); + fingerprint::keys2GroupData(pd.m_partialBits, groups, false); + + return static_cast(pd.m_partialBits.size()); + +} + +// ----------------------------------------------------------------------------- + +void integralImage(float** ppFrames, unsigned int nFrames) +{ + for (unsigned int y = 1; y < nFrames; y++) + { + ppFrames[y][0] += ppFrames[y-1][0]; + } + + for (unsigned int x = 1; x < Filter::NBANDS; x++) + { + ppFrames[0][x] += ppFrames[0][x-1]; + } + + for (unsigned int y = 1; y < nFrames; y++) + { + for (unsigned int x = 1; x < Filter::NBANDS; x++) + { + ppFrames[y][x] += static_cast( static_cast(ppFrames[y-1][x]) + + static_cast(ppFrames[y][x-1]) - + static_cast(ppFrames[y-1][x-1]) ); + } + } +} + +// --------------------------------------------------------------------- +// +/// Convert bands to bits, using the supplied filters +void computeBits( vector& bits, + const vector& f, + float ** frames, unsigned int nframes ) +{ + unsigned int first_time = Filter::KEYWIDTH / 2 + 1; + unsigned int last_time = nframes - Filter::KEYWIDTH / 2; + + unsigned int numBits = last_time - first_time + 1; + bits.resize(numBits); + + const unsigned int fSize = static_cast(f.size()); + std::bitset<32> bt; + double X = 0; + + for (unsigned int t2 = first_time; t2 <= last_time; ++t2) + { + + for (unsigned int i = 0; i < fSize; ++i) + { + // we subtract 1 from t1 and b1 because we use integral images + + unsigned int t1 = (unsigned int) ((float) t2 - f[i].wt / 2.0 - 1); + unsigned int t3 = (unsigned int) ((float) t2 + f[i].wt / 2.0 - 1); + unsigned int b1 = f[i].first_band; + unsigned int b2 = (unsigned int) round__((float) b1 + f[i].wb / 2.0) - 1; + unsigned int b3 = b1 + f[i].wb - 1; + --b1; + + unsigned int t_1q = (t1 + t2) / 2; // one quarter time + unsigned int t_3q = t_1q + (t3 - t1 + 1) / 2; // three quarter time + unsigned int b_1q = (b1 + b2) / 2; // one quarter band + unsigned int b_3q = b_1q + (b3 - b1) / 2; // three quarter band + + X = 0; + + // we should check from t1 > 0, but in practice, this doesn't happen + // we subtract 1 from everything because this came from matlab where indices start from 1 + switch (f[i].filter_type) { + case 1: { // total energy + if (b1 > 0) + X = static_cast(frames[t3-1][b3-1]) - static_cast(frames[t3-1][b1-1]) + - static_cast(frames[t1-1][b3-1]) + static_cast(frames[t1-1][b1-1]); + else + X = static_cast(frames[t3-1][b3-1]) - static_cast(frames[t1-1][b3-1]); + break; + } + case 2: { // energy difference over time + if (b1 > 0) + X = static_cast(frames[t1-1][b1-1]) - 2*static_cast(frames[t2-2][b1-1]) + + static_cast(frames[t3-1][b1-1]) - static_cast(frames[t1-1][b3-1]) + + 2*static_cast(frames[t2-2][b3-1]) - static_cast(frames[t3-1][b3-1]); + else + X = - static_cast(frames[t1-1][b3-1]) + 2*static_cast(frames[t2-2][b3-1]) + - static_cast(frames[t3-1][b3-1]); + break; + + } + case 3: { // energy difference over bands + if (b1 > 0) + X = static_cast(frames[t1-1][b1-1]) - static_cast(frames[t3-1][b1-1]) + - 2*static_cast(frames[t1-1][b2-1]) + 2*static_cast(frames[t3-1][b2-1]) + + static_cast(frames[t1-1][b3-1]) - static_cast(frames[t3-1][b3-1]); + else + X = - 2*static_cast(frames[t1-1][b2-1]) + 2*static_cast(frames[t3-1][b2-1]) + + static_cast(frames[t1-1][b3-1]) - static_cast(frames[t3-1][b3-1]); + break; + } + case 4: { + // energy difference over time and bands + if (b1 > 0) + X = static_cast(frames[t1-1][b1-1]) - 2*static_cast(frames[t2-2][b1-1]) + + static_cast(frames[t3-1][b1-1]) - 2*static_cast(frames[t1-1][b2-1]) + + 4*static_cast(frames[t2-2][b2-1]) - 2*static_cast(frames[t3-1][b2-1]) + + static_cast(frames[t1-1][b3-1]) - 2*static_cast(frames[t2-2][b3-1]) + + static_cast(frames[t3-1][b3-1]); + else + X = - 2*static_cast(frames[t1-1][b2-1]) + 4*static_cast(frames[t2-2][b2-1]) + - 2*static_cast(frames[t3-1][b2-1]) + static_cast(frames[t1-1][b3-1]) + - 2*static_cast(frames[t2-2][b3-1]) + static_cast(frames[t3-1][b3-1]); + break; + } + case 5: { // time peak + if (b1 > 0) + X = - static_cast(frames[t1-1][b1-1]) + 2*static_cast(frames[t_1q-1][b1-1]) + - 2*static_cast(frames[t_3q-1][b1-1]) + static_cast(frames[t3-1][b1-1]) + + static_cast(frames[t1-1][b3-1]) - 2*static_cast(frames[t_1q-1][b3-1]) + + 2*static_cast(frames[t_3q-1][b3-1]) - static_cast(frames[t3-1][b3-1]); + else + X = static_cast(frames[t1-1][b3-1]) - 2*static_cast(frames[t_1q-1][b3-1]) + + 2*static_cast(frames[t_3q-1][b3-1]) - static_cast(frames[t3-1][b3-1]); + + break; + } + case 6: { // band beak + if (b1 > 0) + X = - static_cast(frames[t1-1][b1-1]) + static_cast(frames[t3-1][b1-1]) + + 2*static_cast(frames[t1-1][b_1q-1]) - 2*static_cast(frames[t3-1][b_1q-1]) + - 2*static_cast(frames[t1-1][b_3q-1]) + 2*static_cast(frames[t3-1][b_3q-1]) + + static_cast(frames[t1-1][b3-1]) - static_cast(frames[t3-1][b3-1]); + else + X = + 2*static_cast(frames[t1-1][b_1q-1]) - 2*static_cast(frames[t3-1][b_1q-1]) + - 2*static_cast(frames[t1-1][b_3q-1]) + 2*static_cast(frames[t3-1][b_3q-1]) + + static_cast(frames[t1-1][b3-1]) - static_cast(frames[t3-1][b3-1]); + + break; + } + } + + bt[i] = X > f[i].threshold; + } + + bits[t2 - first_time] = bt.to_ulong(); + } +} + +// ----------------------------------------------------------------------------- + +void src_short_to_float_and_mono_array( const short *in, float *out, int srclen, int nchannels ) +{ + switch ( nchannels ) + { + case 1: + src_short_to_float_array(in, out, srclen); + break; + case 2: + { + // this can be optimized + int j = 0; + const double div = numeric_limits::max() * nchannels; + for ( int i = 0; i < srclen; i += 2, ++j ) + { + out[j] = static_cast( static_cast(static_cast(in[i]) + static_cast(in[i+1])) / div ); + } + } + break; + + default: + throw( std::runtime_error("Unsupported number of channels!") ); + } + +} + +// ----------------------------------------------------------------------------- + +} // end of namespace + +// ----------------------------------------------------------------------------- diff --git a/thirdparty/liblastfm2/src/fingerprint/fplib/FingerprintExtractor.h b/thirdparty/liblastfm2/src/fingerprint/fplib/FingerprintExtractor.h new file mode 100644 index 000000000..fac9b5887 --- /dev/null +++ b/thirdparty/liblastfm2/src/fingerprint/fplib/FingerprintExtractor.h @@ -0,0 +1,77 @@ +/* + Copyright 2005-2009 Last.fm Ltd. + + This file is part of liblastfm. + + liblastfm 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. + + liblastfm 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 liblastfm. If not, see . +*/ +#ifndef __FINGERPRINT_EXTRACTOR_H +#define __FINGERPRINT_EXTRACTOR_H + +#include // for pair +#include // for size_t + +namespace fingerprint { + +// ----------------------------------------------------------------------------- + +class PimplData; + +class FingerprintExtractor +{ +public: + + FingerprintExtractor(); // ctor + ~FingerprintExtractor(); // dtor + + // duration (in seconds!) is optional, but if you want to submit tracks <34 secs + // it must be provided. + void initForQuery(int freq, int nchannels, int duration = -1); + void initForFullSubmit(int freq, int nchannels); + + // return false if it needs more data, otherwise true + // IMPORTANT: num_samples specify the size of the *short* array pPCM, that is + // the number of samples that are in the buffer. This includes + // the stereo samples, i.e. + // [L][R][L][R][L][R][L][R] would be num_samples=8 + bool process(const short* pPCM, size_t num_samples, bool end_of_stream = false); + + // returns pair if the data is not ready + std::pair getFingerprint(); + + ////////////////////////////////////////////////////////////////////////// + + // The FingerprintExtractor assumes that the file start from the beginning + // but since the first SkipMs are ignored, it's possible to feed it with NULL. + // In order to know how much must be skipped (in milliseconds) call this function. + // Remark: this is only for "advanced" users! + size_t getToSkipMs(); + + // Return the minimum duration of the file (in ms) + // Any file with a length smaller than this value will be discarded + static size_t getMinimumDurationMs(); + + // return the version of the fingerprint + static size_t getVersion(); + +private: + + PimplData* m_pPimplData; +}; + +// ----------------------------------------------------------------------------- + +} // end of namespace fingerprint + +#endif // __FINGERPRINT_EXTRACTOR_H diff --git a/thirdparty/liblastfm2/src/fingerprint/fplib/FloatingAverage.h b/thirdparty/liblastfm2/src/fingerprint/fplib/FloatingAverage.h new file mode 100644 index 000000000..1be665bd0 --- /dev/null +++ b/thirdparty/liblastfm2/src/fingerprint/fplib/FloatingAverage.h @@ -0,0 +1,106 @@ +/* + Copyright 2005-2009 Last.fm Ltd. + + This file is part of liblastfm. + + liblastfm 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. + + liblastfm 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 liblastfm. If not, see . +*/ +#ifndef __FLOAT_AVERAGE_H__ +#define __FLOAT_AVERAGE_H__ + +//#include +#include +#include "CircularArray.h" + +template +class FloatingAverage +{ +public: + FloatingAverage(size_t size) + { + m_values.resize(size); + m_valIt = m_values.head(); + m_sum = 0; + m_bufferFilled = false; + } + + void purge() + { + m_sum = 0; + const T* pCircularBuffer = m_values.get_buffer(); + const int size = m_values.size(); + + for ( int i = 0; i < size; ++i ) + m_sum += pCircularBuffer[i]; + } + + void add(const T& value) + { + m_sum += value; + + if ( m_bufferFilled ) + { + m_sum -= *m_valIt; + *m_valIt = value; + ++m_valIt; + } + else + { + *m_valIt = value; + ++m_valIt; + if ( m_valIt == m_values.head() ) + m_bufferFilled = true; + } + } + + T getAverage() const + { + if ( !m_bufferFilled ) + return m_sum / (m_valIt - m_values.head()); + else + return m_sum / m_values.size(); + } + + T getError() const + { + T real_sum = 0; + const T* pCircularBuffer = m_values.get_buffer(); + for ( int i = 0; i < size; ++i ) + real_sum += pCircularBuffer[i]; + return abs(real_sum - m_sum) / this->size(); + } + + size_t size() const + { + return m_values.size(); + } + + void clear() + { + m_bufferFilled = false; + m_values.zero_fill(); + m_valIt = m_values.head(); + m_sum = 0; + } + +private: + //std::deque m_values; + CircularArray m_values; + typename CircularArray::iterator m_valIt; + + bool m_bufferFilled; + T m_sum; +}; + +#endif // __FLOAT_AVERAGE_H__ diff --git a/thirdparty/liblastfm2/src/fingerprint/fplib/OptFFT.cpp b/thirdparty/liblastfm2/src/fingerprint/fplib/OptFFT.cpp new file mode 100644 index 000000000..3728c974c --- /dev/null +++ b/thirdparty/liblastfm2/src/fingerprint/fplib/OptFFT.cpp @@ -0,0 +1,411 @@ +/* + Copyright 2005-2009 Last.fm Ltd. + + This file is part of liblastfm. + + liblastfm 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. + + liblastfm 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 liblastfm. If not, see . +*/ +#include "OptFFT.h" +#include "fp_helper_fun.h" +#include "Filter.h" // for NBANDS + +#include +#include +#include +#include +#include +#include +#include +#include + +using namespace std; +// ---------------------------------------------------------------------- + +namespace fingerprint +{ + + static const float hann[] = { + 0.000000f,0.000002f,0.000009f,0.000021f,0.000038f,0.000059f,0.000085f,0.000115f,0.000151f,0.000191f,0.000236f, + 0.000285f,0.000339f,0.000398f,0.000462f,0.000530f,0.000603f,0.000681f,0.000763f,0.000850f,0.000942f,0.001038f, + 0.001140f,0.001245f,0.001356f,0.001471f,0.001591f,0.001716f,0.001845f,0.001980f,0.002118f,0.002262f,0.002410f, + 0.002563f,0.002720f,0.002883f,0.003049f,0.003221f,0.003397f,0.003578f,0.003764f,0.003954f,0.004149f,0.004349f, + 0.004553f,0.004762f,0.004976f,0.005194f,0.005417f,0.005645f,0.005877f,0.006114f,0.006355f,0.006602f,0.006853f, + 0.007108f,0.007368f,0.007633f,0.007903f,0.008177f,0.008455f,0.008739f,0.009027f,0.009319f,0.009617f,0.009919f, + 0.010225f,0.010536f,0.010852f,0.011172f,0.011497f,0.011827f,0.012161f,0.012499f,0.012843f,0.013191f,0.013543f, + 0.013900f,0.014262f,0.014628f,0.014999f,0.015374f,0.015754f,0.016139f,0.016528f,0.016921f,0.017320f,0.017722f, + 0.018130f,0.018541f,0.018958f,0.019379f,0.019804f,0.020234f,0.020668f,0.021107f,0.021551f,0.021999f,0.022451f, + 0.022908f,0.023370f,0.023836f,0.024306f,0.024781f,0.025260f,0.025744f,0.026233f,0.026725f,0.027223f,0.027724f, + 0.028231f,0.028741f,0.029256f,0.029776f,0.030300f,0.030828f,0.031361f,0.031898f,0.032440f,0.032986f,0.033536f, + 0.034091f,0.034650f,0.035214f,0.035781f,0.036354f,0.036930f,0.037512f,0.038097f,0.038687f,0.039281f,0.039879f, + 0.040482f,0.041089f,0.041701f,0.042316f,0.042936f,0.043561f,0.044189f,0.044822f,0.045460f,0.046101f,0.046747f, + 0.047397f,0.048052f,0.048710f,0.049373f,0.050040f,0.050711f,0.051387f,0.052067f,0.052751f,0.053439f,0.054132f, + 0.054828f,0.055529f,0.056234f,0.056943f,0.057657f,0.058374f,0.059096f,0.059822f,0.060552f,0.061286f,0.062024f, + 0.062767f,0.063513f,0.064264f,0.065019f,0.065777f,0.066540f,0.067307f,0.068078f,0.068854f,0.069633f,0.070416f, + 0.071204f,0.071995f,0.072790f,0.073590f,0.074393f,0.075201f,0.076012f,0.076828f,0.077647f,0.078470f,0.079298f, + 0.080129f,0.080964f,0.081804f,0.082647f,0.083494f,0.084345f,0.085200f,0.086059f,0.086922f,0.087788f,0.088659f, + 0.089533f,0.090412f,0.091294f,0.092180f,0.093070f,0.093963f,0.094861f,0.095762f,0.096667f,0.097576f,0.098489f, + 0.099406f,0.100326f,0.101250f,0.102178f,0.103109f,0.104045f,0.104984f,0.105926f,0.106873f,0.107823f,0.108777f, + 0.109734f,0.110696f,0.111661f,0.112629f,0.113601f,0.114577f,0.115557f,0.116540f,0.117526f,0.118517f,0.119511f, + 0.120508f,0.121509f,0.122514f,0.123522f,0.124534f,0.125549f,0.126568f,0.127590f,0.128616f,0.129645f,0.130678f, + 0.131714f,0.132754f,0.133797f,0.134844f,0.135894f,0.136948f,0.138005f,0.139065f,0.140129f,0.141196f,0.142266f, + 0.143340f,0.144418f,0.145498f,0.146582f,0.147670f,0.148760f,0.149854f,0.150951f,0.152052f,0.153156f,0.154263f, + 0.155373f,0.156487f,0.157603f,0.158723f,0.159847f,0.160973f,0.162103f,0.163236f,0.164372f,0.165511f,0.166653f, + 0.167799f,0.168947f,0.170099f,0.171254f,0.172411f,0.173572f,0.174737f,0.175904f,0.177074f,0.178247f,0.179423f, + 0.180603f,0.181785f,0.182970f,0.184158f,0.185350f,0.186544f,0.187741f,0.188941f,0.190144f,0.191350f,0.192559f, + 0.193771f,0.194986f,0.196203f,0.197423f,0.198647f,0.199873f,0.201102f,0.202333f,0.203568f,0.204805f,0.206045f, + 0.207288f,0.208534f,0.209782f,0.211033f,0.212287f,0.213544f,0.214803f,0.216065f,0.217329f,0.218597f,0.219867f, + 0.221139f,0.222414f,0.223692f,0.224972f,0.226255f,0.227541f,0.228829f,0.230120f,0.231413f,0.232709f,0.234007f, + 0.235308f,0.236611f,0.237917f,0.239225f,0.240536f,0.241849f,0.243165f,0.244483f,0.245803f,0.247126f,0.248451f, + 0.249779f,0.251108f,0.252441f,0.253775f,0.255112f,0.256451f,0.257793f,0.259137f,0.260483f,0.261831f,0.263182f, + 0.264534f,0.265889f,0.267247f,0.268606f,0.269967f,0.271331f,0.272697f,0.274065f,0.275435f,0.276808f,0.278182f, + 0.279558f,0.280937f,0.282318f,0.283700f,0.285085f,0.286472f,0.287861f,0.289251f,0.290644f,0.292039f,0.293435f, + 0.294834f,0.296235f,0.297637f,0.299041f,0.300448f,0.301856f,0.303266f,0.304678f,0.306091f,0.307507f,0.308924f, + 0.310343f,0.311764f,0.313187f,0.314611f,0.316038f,0.317466f,0.318895f,0.320327f,0.321760f,0.323194f,0.324631f, + 0.326069f,0.327509f,0.328950f,0.330393f,0.331837f,0.333283f,0.334731f,0.336180f,0.337631f,0.339083f,0.340537f, + 0.341993f,0.343449f,0.344908f,0.346368f,0.347829f,0.349291f,0.350755f,0.352221f,0.353688f,0.355156f,0.356626f, + 0.358097f,0.359569f,0.361042f,0.362517f,0.363994f,0.365471f,0.366950f,0.368430f,0.369911f,0.371394f,0.372877f, + 0.374362f,0.375848f,0.377336f,0.378824f,0.380314f,0.381804f,0.383296f,0.384789f,0.386283f,0.387778f,0.389274f, + 0.390771f,0.392269f,0.393768f,0.395269f,0.396770f,0.398272f,0.399775f,0.401279f,0.402784f,0.404290f,0.405797f, + 0.407305f,0.408813f,0.410322f,0.411833f,0.413344f,0.414856f,0.416368f,0.417882f,0.419396f,0.420911f,0.422427f, + 0.423944f,0.425461f,0.426979f,0.428497f,0.430017f,0.431537f,0.433057f,0.434578f,0.436100f,0.437623f,0.439146f, + 0.440669f,0.442193f,0.443718f,0.445243f,0.446769f,0.448295f,0.449822f,0.451349f,0.452877f,0.454405f,0.455934f, + 0.457463f,0.458992f,0.460522f,0.462052f,0.463582f,0.465113f,0.466644f,0.468176f,0.469708f,0.471240f,0.472772f, + 0.474305f,0.475837f,0.477370f,0.478904f,0.480437f,0.481971f,0.483505f,0.485039f,0.486573f,0.488107f,0.489641f, + 0.491176f,0.492710f,0.494245f,0.495780f,0.497314f,0.498849f,0.500384f,0.501918f,0.503453f,0.504988f,0.506522f, + 0.508057f,0.509591f,0.511126f,0.512660f,0.514194f,0.515728f,0.517262f,0.518796f,0.520330f,0.521863f,0.523396f, + 0.524929f,0.526462f,0.527994f,0.529526f,0.531058f,0.532590f,0.534121f,0.535652f,0.537183f,0.538713f,0.540243f, + 0.541773f,0.543302f,0.544831f,0.546359f,0.547887f,0.549414f,0.550941f,0.552468f,0.553994f,0.555519f,0.557044f, + 0.558569f,0.560093f,0.561616f,0.563139f,0.564661f,0.566182f,0.567703f,0.569223f,0.570743f,0.572262f,0.573780f, + 0.575298f,0.576815f,0.578331f,0.579846f,0.581361f,0.582875f,0.584388f,0.585900f,0.587412f,0.588922f,0.590432f, + 0.591941f,0.593449f,0.594957f,0.596463f,0.597968f,0.599473f,0.600977f,0.602479f,0.603981f,0.605482f,0.606981f, + 0.608480f,0.609978f,0.611474f,0.612970f,0.614464f,0.615958f,0.617450f,0.618941f,0.620431f,0.621920f,0.623408f, + 0.624895f,0.626380f,0.627865f,0.629348f,0.630830f,0.632310f,0.633790f,0.635268f,0.636745f,0.638220f,0.639695f, + 0.641167f,0.642639f,0.644109f,0.645578f,0.647046f,0.648512f,0.649977f,0.651440f,0.652902f,0.654363f,0.655822f, + 0.657279f,0.658735f,0.660190f,0.661643f,0.663094f,0.664544f,0.665993f,0.667440f,0.668885f,0.670329f,0.671771f, + 0.673212f,0.674650f,0.676088f,0.677523f,0.678957f,0.680389f,0.681820f,0.683249f,0.684676f,0.686101f,0.687525f, + 0.688946f,0.690366f,0.691785f,0.693201f,0.694616f,0.696029f,0.697439f,0.698849f,0.700256f,0.701661f,0.703065f, + 0.704466f,0.705866f,0.707263f,0.708659f,0.710053f,0.711444f,0.712834f,0.714222f,0.715608f,0.716991f,0.718373f, + 0.719752f,0.721130f,0.722505f,0.723879f,0.725250f,0.726619f,0.727986f,0.729351f,0.730714f,0.732074f,0.733432f, + 0.734788f,0.736142f,0.737494f,0.738843f,0.740191f,0.741536f,0.742878f,0.744219f,0.745557f,0.746892f,0.748226f, + 0.749557f,0.750886f,0.752212f,0.753536f,0.754857f,0.756177f,0.757493f,0.758808f,0.760120f,0.761429f,0.762736f, + 0.764041f,0.765343f,0.766642f,0.767939f,0.769234f,0.770526f,0.771815f,0.773102f,0.774386f,0.775668f,0.776947f, + 0.778224f,0.779497f,0.780769f,0.782037f,0.783303f,0.784567f,0.785827f,0.787085f,0.788340f,0.789593f,0.790842f, + 0.792089f,0.793334f,0.794575f,0.795814f,0.797050f,0.798283f,0.799513f,0.800741f,0.801965f,0.803187f,0.804406f, + 0.805622f,0.806835f,0.808046f,0.809253f,0.810458f,0.811659f,0.812858f,0.814054f,0.815246f,0.816436f,0.817623f, + 0.818807f,0.819987f,0.821165f,0.822340f,0.823512f,0.824680f,0.825846f,0.827008f,0.828168f,0.829324f,0.830477f, + 0.831628f,0.832775f,0.833918f,0.835059f,0.836197f,0.837331f,0.838462f,0.839591f,0.840715f,0.841837f,0.842955f, + 0.844071f,0.845183f,0.846291f,0.847397f,0.848499f,0.849598f,0.850693f,0.851786f,0.852874f,0.853960f,0.855042f, + 0.856121f,0.857197f,0.858269f,0.859338f,0.860404f,0.861466f,0.862524f,0.863580f,0.864631f,0.865680f,0.866725f, + 0.867766f,0.868804f,0.869839f,0.870870f,0.871897f,0.872922f,0.873942f,0.874959f,0.875973f,0.876983f,0.877989f, + 0.878992f,0.879991f,0.880987f,0.881979f,0.882967f,0.883952f,0.884934f,0.885911f,0.886885f,0.887856f,0.888822f, + 0.889785f,0.890745f,0.891701f,0.892653f,0.893601f,0.894545f,0.895486f,0.896423f,0.897357f,0.898287f,0.899213f, + 0.900135f,0.901053f,0.901968f,0.902879f,0.903786f,0.904689f,0.905588f,0.906484f,0.907376f,0.908264f,0.909148f, + 0.910028f,0.910904f,0.911777f,0.912645f,0.913510f,0.914371f,0.915228f,0.916081f,0.916930f,0.917775f,0.918616f, + 0.919454f,0.920287f,0.921116f,0.921942f,0.922763f,0.923581f,0.924394f,0.925204f,0.926009f,0.926810f,0.927608f, + 0.928401f,0.929191f,0.929976f,0.930757f,0.931534f,0.932308f,0.933077f,0.933842f,0.934603f,0.935359f,0.936112f, + 0.936861f,0.937605f,0.938345f,0.939082f,0.939814f,0.940542f,0.941265f,0.941985f,0.942701f,0.943412f,0.944119f, + 0.944822f,0.945521f,0.946215f,0.946906f,0.947592f,0.948274f,0.948951f,0.949625f,0.950294f,0.950959f,0.951620f, + 0.952276f,0.952928f,0.953576f,0.954220f,0.954859f,0.955495f,0.956125f,0.956752f,0.957374f,0.957992f,0.958606f, + 0.959215f,0.959820f,0.960420f,0.961017f,0.961609f,0.962196f,0.962780f,0.963358f,0.963933f,0.964503f,0.965069f, + 0.965630f,0.966187f,0.966740f,0.967288f,0.967832f,0.968371f,0.968906f,0.969437f,0.969963f,0.970485f,0.971002f, + 0.971515f,0.972023f,0.972527f,0.973027f,0.973522f,0.974012f,0.974498f,0.974980f,0.975457f,0.975930f,0.976398f, + 0.976862f,0.977321f,0.977776f,0.978226f,0.978672f,0.979113f,0.979549f,0.979982f,0.980409f,0.980832f,0.981251f, + 0.981665f,0.982075f,0.982480f,0.982880f,0.983276f,0.983667f,0.984054f,0.984436f,0.984814f,0.985187f,0.985556f, + 0.985919f,0.986279f,0.986634f,0.986984f,0.987329f,0.987670f,0.988007f,0.988339f,0.988666f,0.988989f,0.989307f, + 0.989620f,0.989929f,0.990233f,0.990532f,0.990827f,0.991118f,0.991403f,0.991684f,0.991961f,0.992233f,0.992500f, + 0.992762f,0.993020f,0.993273f,0.993522f,0.993766f,0.994005f,0.994240f,0.994470f,0.994695f,0.994916f,0.995132f, + 0.995343f,0.995550f,0.995752f,0.995949f,0.996142f,0.996329f,0.996513f,0.996691f,0.996865f,0.997035f,0.997199f, + 0.997359f,0.997514f,0.997665f,0.997810f,0.997952f,0.998088f,0.998220f,0.998347f,0.998469f,0.998587f,0.998700f, + 0.998808f,0.998912f,0.999010f,0.999105f,0.999194f,0.999279f,0.999359f,0.999434f,0.999505f,0.999571f,0.999632f, + 0.999689f,0.999740f,0.999787f,0.999830f,0.999868f,0.999900f,0.999929f,0.999952f,0.999971f,0.999985f,0.999995f, + 0.999999f,0.999999f,0.999995f,0.999985f,0.999971f,0.999952f,0.999929f,0.999900f,0.999868f,0.999830f,0.999787f, + 0.999740f,0.999689f,0.999632f,0.999571f,0.999505f,0.999434f,0.999359f,0.999279f,0.999194f,0.999105f,0.999010f, + 0.998912f,0.998808f,0.998700f,0.998587f,0.998469f,0.998347f,0.998220f,0.998088f,0.997952f,0.997810f,0.997665f, + 0.997514f,0.997359f,0.997199f,0.997035f,0.996865f,0.996691f,0.996513f,0.996329f,0.996142f,0.995949f,0.995752f, + 0.995550f,0.995343f,0.995132f,0.994916f,0.994695f,0.994470f,0.994240f,0.994005f,0.993766f,0.993522f,0.993273f, + 0.993020f,0.992762f,0.992500f,0.992233f,0.991961f,0.991684f,0.991403f,0.991118f,0.990827f,0.990532f,0.990233f, + 0.989929f,0.989620f,0.989307f,0.988989f,0.988666f,0.988339f,0.988007f,0.987670f,0.987329f,0.986984f,0.986634f, + 0.986279f,0.985919f,0.985556f,0.985187f,0.984814f,0.984436f,0.984054f,0.983667f,0.983276f,0.982880f,0.982480f, + 0.982075f,0.981665f,0.981251f,0.980832f,0.980409f,0.979982f,0.979549f,0.979113f,0.978672f,0.978226f,0.977776f, + 0.977321f,0.976862f,0.976398f,0.975930f,0.975457f,0.974980f,0.974498f,0.974012f,0.973522f,0.973027f,0.972527f, + 0.972023f,0.971515f,0.971002f,0.970485f,0.969963f,0.969437f,0.968906f,0.968371f,0.967832f,0.967288f,0.966740f, + 0.966187f,0.965630f,0.965069f,0.964503f,0.963933f,0.963358f,0.962780f,0.962196f,0.961609f,0.961017f,0.960420f, + 0.959820f,0.959215f,0.958606f,0.957992f,0.957374f,0.956752f,0.956125f,0.955495f,0.954859f,0.954220f,0.953576f, + 0.952928f,0.952276f,0.951620f,0.950959f,0.950294f,0.949625f,0.948951f,0.948274f,0.947592f,0.946906f,0.946215f, + 0.945521f,0.944822f,0.944119f,0.943412f,0.942701f,0.941985f,0.941265f,0.940542f,0.939814f,0.939082f,0.938345f, + 0.937605f,0.936861f,0.936112f,0.935359f,0.934603f,0.933842f,0.933077f,0.932308f,0.931534f,0.930757f,0.929976f, + 0.929191f,0.928401f,0.927608f,0.926810f,0.926009f,0.925204f,0.924394f,0.923581f,0.922763f,0.921942f,0.921116f, + 0.920287f,0.919454f,0.918616f,0.917775f,0.916930f,0.916081f,0.915228f,0.914371f,0.913510f,0.912645f,0.911777f, + 0.910904f,0.910028f,0.909148f,0.908264f,0.907376f,0.906484f,0.905588f,0.904689f,0.903786f,0.902879f,0.901968f, + 0.901053f,0.900135f,0.899213f,0.898287f,0.897357f,0.896423f,0.895486f,0.894545f,0.893601f,0.892653f,0.891701f, + 0.890745f,0.889785f,0.888822f,0.887856f,0.886885f,0.885911f,0.884934f,0.883952f,0.882967f,0.881979f,0.880987f, + 0.879991f,0.878992f,0.877989f,0.876983f,0.875973f,0.874959f,0.873942f,0.872922f,0.871897f,0.870870f,0.869839f, + 0.868804f,0.867766f,0.866725f,0.865680f,0.864631f,0.863580f,0.862524f,0.861466f,0.860404f,0.859338f,0.858269f, + 0.857197f,0.856121f,0.855042f,0.853960f,0.852874f,0.851786f,0.850693f,0.849598f,0.848499f,0.847397f,0.846291f, + 0.845183f,0.844071f,0.842955f,0.841837f,0.840715f,0.839591f,0.838462f,0.837331f,0.836197f,0.835059f,0.833918f, + 0.832775f,0.831628f,0.830477f,0.829324f,0.828168f,0.827008f,0.825846f,0.824680f,0.823512f,0.822340f,0.821165f, + 0.819987f,0.818807f,0.817623f,0.816436f,0.815246f,0.814054f,0.812858f,0.811659f,0.810458f,0.809253f,0.808046f, + 0.806835f,0.805622f,0.804406f,0.803187f,0.801965f,0.800741f,0.799513f,0.798283f,0.797050f,0.795814f,0.794575f, + 0.793334f,0.792089f,0.790842f,0.789593f,0.788340f,0.787085f,0.785827f,0.784567f,0.783303f,0.782037f,0.780769f, + 0.779497f,0.778224f,0.776947f,0.775668f,0.774386f,0.773102f,0.771815f,0.770526f,0.769234f,0.767939f,0.766642f, + 0.765343f,0.764041f,0.762736f,0.761429f,0.760120f,0.758808f,0.757493f,0.756177f,0.754857f,0.753536f,0.752212f, + 0.750886f,0.749557f,0.748226f,0.746892f,0.745557f,0.744219f,0.742878f,0.741536f,0.740191f,0.738843f,0.737494f, + 0.736142f,0.734788f,0.733432f,0.732074f,0.730714f,0.729351f,0.727986f,0.726619f,0.725250f,0.723879f,0.722505f, + 0.721130f,0.719752f,0.718373f,0.716991f,0.715608f,0.714222f,0.712834f,0.711444f,0.710053f,0.708659f,0.707263f, + 0.705866f,0.704466f,0.703065f,0.701661f,0.700256f,0.698849f,0.697439f,0.696029f,0.694616f,0.693201f,0.691785f, + 0.690366f,0.688946f,0.687525f,0.686101f,0.684676f,0.683249f,0.681820f,0.680389f,0.678957f,0.677523f,0.676088f, + 0.674650f,0.673212f,0.671771f,0.670329f,0.668885f,0.667440f,0.665993f,0.664544f,0.663094f,0.661643f,0.660190f, + 0.658735f,0.657279f,0.655822f,0.654363f,0.652902f,0.651440f,0.649977f,0.648512f,0.647046f,0.645578f,0.644109f, + 0.642639f,0.641167f,0.639695f,0.638220f,0.636745f,0.635268f,0.633790f,0.632310f,0.630830f,0.629348f,0.627865f, + 0.626380f,0.624895f,0.623408f,0.621920f,0.620431f,0.618941f,0.617450f,0.615958f,0.614464f,0.612970f,0.611474f, + 0.609978f,0.608480f,0.606981f,0.605482f,0.603981f,0.602479f,0.600977f,0.599473f,0.597968f,0.596463f,0.594957f, + 0.593449f,0.591941f,0.590432f,0.588922f,0.587412f,0.585900f,0.584388f,0.582875f,0.581361f,0.579846f,0.578331f, + 0.576815f,0.575298f,0.573780f,0.572262f,0.570743f,0.569223f,0.567703f,0.566182f,0.564661f,0.563139f,0.561616f, + 0.560093f,0.558569f,0.557044f,0.555519f,0.553994f,0.552468f,0.550941f,0.549414f,0.547887f,0.546359f,0.544831f, + 0.543302f,0.541773f,0.540243f,0.538713f,0.537183f,0.535652f,0.534121f,0.532590f,0.531058f,0.529526f,0.527994f, + 0.526462f,0.524929f,0.523396f,0.521863f,0.520330f,0.518796f,0.517262f,0.515728f,0.514194f,0.512660f,0.511126f, + 0.509591f,0.508057f,0.506522f,0.504988f,0.503453f,0.501918f,0.500384f,0.498849f,0.497314f,0.495780f,0.494245f, + 0.492710f,0.491176f,0.489641f,0.488107f,0.486573f,0.485039f,0.483505f,0.481971f,0.480437f,0.478904f,0.477370f, + 0.475837f,0.474305f,0.472772f,0.471240f,0.469708f,0.468176f,0.466644f,0.465113f,0.463582f,0.462052f,0.460522f, + 0.458992f,0.457463f,0.455934f,0.454405f,0.452877f,0.451349f,0.449822f,0.448295f,0.446769f,0.445243f,0.443718f, + 0.442193f,0.440669f,0.439146f,0.437623f,0.436100f,0.434578f,0.433057f,0.431537f,0.430017f,0.428497f,0.426979f, + 0.425461f,0.423944f,0.422427f,0.420911f,0.419396f,0.417882f,0.416368f,0.414856f,0.413344f,0.411833f,0.410322f, + 0.408813f,0.407305f,0.405797f,0.404290f,0.402784f,0.401279f,0.399775f,0.398272f,0.396770f,0.395269f,0.393768f, + 0.392269f,0.390771f,0.389274f,0.387778f,0.386283f,0.384789f,0.383296f,0.381804f,0.380314f,0.378824f,0.377336f, + 0.375848f,0.374362f,0.372877f,0.371394f,0.369911f,0.368430f,0.366950f,0.365471f,0.363994f,0.362517f,0.361042f, + 0.359569f,0.358097f,0.356626f,0.355156f,0.353688f,0.352221f,0.350755f,0.349291f,0.347829f,0.346368f,0.344908f, + 0.343449f,0.341993f,0.340537f,0.339083f,0.337631f,0.336180f,0.334731f,0.333283f,0.331837f,0.330393f,0.328950f, + 0.327509f,0.326069f,0.324631f,0.323194f,0.321760f,0.320327f,0.318895f,0.317466f,0.316038f,0.314611f,0.313187f, + 0.311764f,0.310343f,0.308924f,0.307507f,0.306091f,0.304678f,0.303266f,0.301856f,0.300448f,0.299041f,0.297637f, + 0.296235f,0.294834f,0.293435f,0.292039f,0.290644f,0.289251f,0.287861f,0.286472f,0.285085f,0.283700f,0.282318f, + 0.280937f,0.279558f,0.278182f,0.276808f,0.275435f,0.274065f,0.272697f,0.271331f,0.269967f,0.268606f,0.267247f, + 0.265889f,0.264534f,0.263182f,0.261831f,0.260483f,0.259137f,0.257793f,0.256451f,0.255112f,0.253775f,0.252441f, + 0.251108f,0.249779f,0.248451f,0.247126f,0.245803f,0.244483f,0.243165f,0.241849f,0.240536f,0.239225f,0.237917f, + 0.236611f,0.235308f,0.234007f,0.232709f,0.231413f,0.230120f,0.228829f,0.227541f,0.226255f,0.224972f,0.223692f, + 0.222414f,0.221139f,0.219867f,0.218597f,0.217329f,0.216065f,0.214803f,0.213544f,0.212287f,0.211033f,0.209782f, + 0.208534f,0.207288f,0.206045f,0.204805f,0.203568f,0.202333f,0.201102f,0.199873f,0.198647f,0.197423f,0.196203f, + 0.194986f,0.193771f,0.192559f,0.191350f,0.190144f,0.188941f,0.187741f,0.186544f,0.185350f,0.184158f,0.182970f, + 0.181785f,0.180603f,0.179423f,0.178247f,0.177074f,0.175904f,0.174737f,0.173572f,0.172411f,0.171254f,0.170099f, + 0.168947f,0.167799f,0.166653f,0.165511f,0.164372f,0.163236f,0.162103f,0.160973f,0.159847f,0.158723f,0.157603f, + 0.156487f,0.155373f,0.154263f,0.153156f,0.152052f,0.150951f,0.149854f,0.148760f,0.147670f,0.146582f,0.145498f, + 0.144418f,0.143340f,0.142266f,0.141196f,0.140129f,0.139065f,0.138005f,0.136948f,0.135894f,0.134844f,0.133797f, + 0.132754f,0.131714f,0.130678f,0.129645f,0.128616f,0.127590f,0.126568f,0.125549f,0.124534f,0.123522f,0.122514f, + 0.121509f,0.120508f,0.119511f,0.118517f,0.117526f,0.116540f,0.115557f,0.114577f,0.113601f,0.112629f,0.111661f, + 0.110696f,0.109734f,0.108777f,0.107823f,0.106873f,0.105926f,0.104984f,0.104045f,0.103109f,0.102178f,0.101250f, + 0.100326f,0.099406f,0.098489f,0.097576f,0.096667f,0.095762f,0.094861f,0.093963f,0.093070f,0.092180f,0.091294f, + 0.090412f,0.089533f,0.088659f,0.087788f,0.086922f,0.086059f,0.085200f,0.084345f,0.083494f,0.082647f,0.081804f, + 0.080964f,0.080129f,0.079298f,0.078470f,0.077647f,0.076828f,0.076012f,0.075201f,0.074393f,0.073590f,0.072790f, + 0.071995f,0.071204f,0.070416f,0.069633f,0.068854f,0.068078f,0.067307f,0.066540f,0.065777f,0.065019f,0.064264f, + 0.063513f,0.062767f,0.062024f,0.061286f,0.060552f,0.059822f,0.059096f,0.058374f,0.057657f,0.056943f,0.056234f, + 0.055529f,0.054828f,0.054132f,0.053439f,0.052751f,0.052067f,0.051387f,0.050711f,0.050040f,0.049373f,0.048710f, + 0.048052f,0.047397f,0.046747f,0.046101f,0.045460f,0.044822f,0.044189f,0.043561f,0.042936f,0.042316f,0.041701f, + 0.041089f,0.040482f,0.039879f,0.039281f,0.038687f,0.038097f,0.037512f,0.036930f,0.036354f,0.035781f,0.035214f, + 0.034650f,0.034091f,0.033536f,0.032986f,0.032440f,0.031898f,0.031361f,0.030828f,0.030300f,0.029776f,0.029256f, + 0.028741f,0.028231f,0.027724f,0.027223f,0.026725f,0.026233f,0.025744f,0.025260f,0.024781f,0.024306f,0.023836f, + 0.023370f,0.022908f,0.022451f,0.021999f,0.021551f,0.021107f,0.020668f,0.020234f,0.019804f,0.019379f,0.018958f, + 0.018541f,0.018130f,0.017722f,0.017320f,0.016921f,0.016528f,0.016139f,0.015754f,0.015374f,0.014999f,0.014628f, + 0.014262f,0.013900f,0.013543f,0.013191f,0.012843f,0.012499f,0.012161f,0.011827f,0.011497f,0.011172f,0.010852f, + 0.010536f,0.010225f,0.009919f,0.009617f,0.009319f,0.009027f,0.008739f,0.008455f,0.008177f,0.007903f,0.007633f, + 0.007368f,0.007108f,0.006853f,0.006602f,0.006355f,0.006114f,0.005877f,0.005645f,0.005417f,0.005194f,0.004976f, + 0.004762f,0.004553f,0.004349f,0.004149f,0.003954f,0.003764f,0.003578f,0.003397f,0.003221f,0.003049f,0.002883f, + 0.002720f,0.002563f,0.002410f,0.002262f,0.002118f,0.001980f,0.001845f,0.001716f,0.001591f,0.001471f,0.001356f, + 0.001245f,0.001140f,0.001038f,0.000942f,0.000850f,0.000763f,0.000681f,0.000603f,0.000530f,0.000462f,0.000398f, + 0.000339f,0.000285f,0.000236f,0.000191f,0.000151f,0.000115f,0.000085f,0.000059f,0.000038f,0.000021f,0.000009f, + 0.000002f,0.000000f }; + +// ----------------------------------------------------------------------------- + +OptFFT::OptFFT(const size_t maxDataSize) +{ + assert( maxDataSize % OVERLAPSAMPLES == 0 ); + + // DOUBLE + //m_pIn = static_cast( fftw_malloc(sizeof(double) * FRAMESIZE) ); + //m_pOut = static_cast( fftw_malloc(sizeof(fftw_complex) * (FRAMESIZE/2 + 1)) ); + //m_p = fftw_plan_dft_r2c_1f(FRAMESIZE, m_pIn, m_pOut, FFTW_ESTIMATE); // FFTW_ESTIMATE or FFTW_MEASURE + + // FLOAT + // m_pIn = static_cast( fftwf_malloc(sizeof(float) * FRAMESIZE) ); + // m_pOut = static_cast( fftwf_malloc(sizeof(fftwf_complex) * (FRAMESIZE/2 + 1)) ); + + //// in destroyed when line executed + //m_p = fftwf_plan_dft_r2c_1d(FRAMESIZE, m_pIn, m_pOut, FFTW_ESTIMATE); // FFTW_ESTIMATE or FFTW_MEASURE + + //----------------------------------------------------------------- + + int numSamplesPerFrame = FRAMESIZE; + int numSamplesPerFrameOut = FRAMESIZE/2+1; + + m_maxFrames = static_cast ( (maxDataSize - FRAMESIZE) / OVERLAPSAMPLES + 1 ); + + m_pIn = static_cast ( fftwf_malloc(sizeof(float) * (numSamplesPerFrame * m_maxFrames) ) ); + if ( !m_pIn ) + { + ostringstream oss; + oss << "fftwf_malloc failed on m_pIn. Trying to allocate <" + << sizeof(float) * (numSamplesPerFrame * m_maxFrames) + << "> bytes"; + throw std::runtime_error(oss.str()); + } + + m_pOut = static_cast( fftwf_malloc(sizeof(fftwf_complex) * (numSamplesPerFrameOut* m_maxFrames) ) ); + if ( !m_pOut ) + { + ostringstream oss; + oss << "fftwf_malloc failed on m_pOut. Trying to allocate <" + << sizeof(fftwf_complex) * (numSamplesPerFrameOut* m_maxFrames) + << "> bytes"; + + throw std::runtime_error(oss.str()); + } + + // in destroyed when line executed + m_p = fftwf_plan_many_dft_r2c(1, &numSamplesPerFrame, m_maxFrames, + m_pIn, &numSamplesPerFrame, 1, numSamplesPerFrame, + m_pOut, &numSamplesPerFrameOut, + 1, numSamplesPerFrameOut, + FFTW_ESTIMATE | FFTW_DESTROY_INPUT); + + if ( !m_p ) + throw std::runtime_error ("fftwf_plan_many_dft_r2c failed"); + + double base = exp( log( static_cast(MAXFREQ) / static_cast(MINFREQ) ) / + static_cast(Filter::NBANDS) + ); + + m_powTable.resize( Filter::NBANDS+1 ); + for ( unsigned int i = 0; i < Filter::NBANDS + 1; ++i ) + m_powTable[i] = static_cast( (pow(base, static_cast(i)) - 1.0) * MINCOEF ); + + m_pFrames = new float*[m_maxFrames]; + + if ( !m_pFrames ) + { + ostringstream oss; + oss << "Allocation failed on m_pFrames. Trying to allocate <" + << sizeof(float*) * m_maxFrames + << "> bytes"; + + throw std::runtime_error(oss.str()); + } + + + for (int i = 0; i < m_maxFrames; ++i) + { + m_pFrames[i] = new float[Filter::NBANDS]; + if ( !m_pFrames[i] ) + throw std::runtime_error("Allocation failed on m_pFrames"); + } + +} + +// ---------------------------------------------------------------------- + +OptFFT::~OptFFT() +{ + fftwf_destroy_plan(m_p); + + fftwf_free(m_pIn); + fftwf_free(m_pOut); + + for (int i = 0; i < m_maxFrames; ++i) + delete [] m_pFrames[i]; + + delete [] m_pFrames; +} + +// ---------------------------------------------------------------------- + +int OptFFT::process(float* pInData, const size_t dataSize) +{ + // generally is the same of the one we used in the constructor (m_maxFrames) but + // might be less at the end of the stream + int nFrames = static_cast( (dataSize - FRAMESIZE) / OVERLAPSAMPLES + 1 ); + + float* pIn_It = m_pIn; + + for (int i = 0; i < nFrames; ++i) + { + memcpy( pIn_It, &pInData[i*OVERLAPSAMPLES], sizeof(float) * FRAMESIZE); + // apply hanning window + applyHann(pIn_It, FRAMESIZE); + + pIn_It += FRAMESIZE; + } + + // fill the rest with zeroes + if ( nFrames < m_maxFrames ) + memset( pIn_It, 0, sizeof(float) * (m_maxFrames-nFrames) * FRAMESIZE ); + + fftwf_execute(m_p); + + int totSamples = (FRAMESIZE/2+1) * // numSamplesPerFrameOut + nFrames; // the frames actually in the input + + // scaling (?) + float scalingFactor = static_cast(FRAMESIZE) / 2.0f; + for (int k = 0; k < totSamples; ++k) + { + m_pOut[k][0] /= scalingFactor; + m_pOut[k][1] /= scalingFactor; + } + + int frameStart; + unsigned int outBlocStart; + unsigned int outBlocEnd; + + for (int i = 0; i < nFrames; ++i) + { + frameStart = i * (FRAMESIZE/2+1); + + // compute bands + for (unsigned int j = 0; j < Filter::NBANDS; j++) + { + outBlocStart = m_powTable[j] + frameStart; + outBlocEnd = m_powTable[j+1] + frameStart; + + m_pFrames[i][j] = 0; + + // WARNING: We're double counting the last one here. + // this bug is to match matlab's implementation bug in power2band.m + unsigned int end_k = outBlocEnd + static_cast(MINCOEF); + for (unsigned int k = outBlocStart + static_cast(MINCOEF); k <= end_k; k++) + { + m_pFrames[i][j] += m_pOut[k][0] * m_pOut[k][0] + + m_pOut[k][1] * m_pOut[k][1]; + } + + // WARNING: if we change the k<=end to k(outBlocEnd - outBlocStart + 1); + } + } + + return nFrames; +} + +// ----------------------------------------------------------------------------- + +void OptFFT::applyHann( float* pInData, const size_t dataSize ) +{ + assert (dataSize == 2048); + + for ( size_t i = 0; i < dataSize; ++i ) + pInData[i] *= hann[i]; +} + +// ----------------------------------------------------------------------------- + +} // end of namespace + +// ---------------------------------------------------------------------- diff --git a/thirdparty/liblastfm2/src/fingerprint/fplib/OptFFT.h b/thirdparty/liblastfm2/src/fingerprint/fplib/OptFFT.h new file mode 100644 index 000000000..2a704ee73 --- /dev/null +++ b/thirdparty/liblastfm2/src/fingerprint/fplib/OptFFT.h @@ -0,0 +1,63 @@ +/* + Copyright 2005-2009 Last.fm Ltd. + + This file is part of liblastfm. + + liblastfm 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. + + liblastfm 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 liblastfm. If not, see . +*/ +#ifndef __OPT_FFT_H +#define __OPT_FFT_H + +#include +#include + +namespace fingerprint +{ + +class OptFFT +{ +public: + + OptFFT(const size_t maxDataSize); + ~OptFFT(); + + int + process(float* pInData, const size_t dataSize); + + float** + getFrames() { return m_pFrames; } + +private: + + void applyHann(float* pInData, const size_t dataSize); + + fftwf_plan m_p; + fftwf_complex * m_pOut; + float* m_pIn; + + //float m_base; + + int m_numSamples; + int m_numOutSamples; + + float** m_pFrames; + int m_maxFrames; + + std::vector m_powTable; + +}; + +} // end of namespace + +#endif // OPT_FFT diff --git a/thirdparty/liblastfm2/src/fingerprint/fplib/fp_helper_fun.h b/thirdparty/liblastfm2/src/fingerprint/fplib/fp_helper_fun.h new file mode 100644 index 000000000..947b63037 --- /dev/null +++ b/thirdparty/liblastfm2/src/fingerprint/fplib/fp_helper_fun.h @@ -0,0 +1,443 @@ +/* + Copyright 2005-2009 Last.fm Ltd. + + This file is part of liblastfm. + + liblastfm 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. + + liblastfm 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 liblastfm. If not, see . +*/ +#ifndef __FINGERPRINT_HELPER_FUNCTIONS_H +#define __FINGERPRINT_HELPER_FUNCTIONS_H + +#include +#include +#include + +namespace fingerprint +{ + +// ----------------------------------------------------------------------------- + +static const size_t FINGERPRINT_LIB_VERSION = 1; +static const float QUERY_START_SECS = 20; +static const float QUERY_SIZE_SECS = 14; +static const float UPDATE_SIZE_SECS = 10; +//FFT needs also a buffer that depends on the input freq. 3 secs should be enough up to 48Khz +static const float GUARD_SIZE_SECS = 3; +static const float NORMALIZATION_SKIP_SECS = 2.5; +static const int MIN_UNIQUE_KEYS = 75; +static const unsigned int MAX_GOOD_GROUP_SIZE = 200; +static const int SHA_SIZE = 32; + +///////////////////////////////////////////////////// +// For FFT. DO NOT TOUCH THEM! +// number of samples in a frame +static const int FRAMESIZE = 2048; +static const int OVERLAP = 32; +static const int OVERLAPSAMPLES = (FRAMESIZE/OVERLAP); // 64 + +// down-sampled frequency +static const int DFREQ = 5512; +static const float FDFREQ = 5512.5f; + +// ----------------------------------------------------------------------------- + +struct GroupData +{ + unsigned int key; // the key (or local descriptor) + unsigned int count; // the number of frames sharing this key +}; + +// ----------------------------------------------------------------------------- +// ----------------------------------------------------------------------------- + +inline +unsigned int getTotalKeys( + int mSecs ) +{ + return static_cast((static_cast(mSecs) / (1000.0 * OVERLAPSAMPLES) ) * DFREQ ) + 1; +} + +// ----------------------------------------------------------------------------- + +template +void simpleSkip( + GroupDataIt& begIt, const GroupDataIt& endIt, + unsigned int numSkipKeys ) +{ + if ( numSkipKeys <= 0 ) + return; + + unsigned int nKeys; + for ( nKeys = 0; nKeys < numSkipKeys && begIt != endIt; ++begIt ) + nKeys += begIt->count; + + // clear crop at the end + if ( nKeys > numSkipKeys ) + { + --begIt; + begIt->count = nKeys - numSkipKeys; + } + +} + +// ----------------------------------------------------------------------------- + +template +void cutGroups( + std::vector& groups, + const unsigned int startMS, + const unsigned int lengthMS ) +{ + typename std::vector::iterator itBeg = groups.begin(), itEnd = groups.begin(); + + unsigned int keys_begin, keys_end; + + for (keys_begin = getTotalKeys(startMS); + itBeg != groups.end() && keys_begin > itBeg->count; ++itBeg) + keys_begin -= itBeg->count; + + for (keys_end = getTotalKeys(startMS + lengthMS); + itEnd != groups.end() && keys_end > itEnd->count; ++itEnd) + keys_end -= itEnd->count; + + if (itBeg == groups.end()) // in the umpossible scenario that you try to cut past the size of the groups + { + groups.clear(); + return; + } + + itBeg->count -= keys_begin; + if (keys_end > 0 && itEnd != groups.end()) + { + itEnd->count = keys_end; + ++itEnd; + } + + copy(itBeg, itEnd, groups.begin()); + groups.resize(itEnd - itBeg); + + keys_begin = getTotalKeys(lengthMS); + for (typename std::vector::iterator it = groups.begin(); it != groups.end(); ++it) + keys_begin -= it->count; +} + +// ------------------------------------------------------------------------- + +template +void keys2GroupData( + const std::vector& keys, // in + std::vector& groupData, + bool clearDst = true ) // out +{ + if (clearDst) + groupData.clear(); + + if (keys.empty()) + return; + + TGroupData tmpGroup; + std::vector::const_iterator it = keys.begin(); + + if ( !groupData.empty() ) + { + // get the last group + tmpGroup = groupData.back(); + groupData.pop_back(); + } + else + { + // new group! + tmpGroup.key = *it; + tmpGroup.count = 1; + ++it; // move to the next key + } + + for (; it != keys.end(); ++it) + { + if ( *it != tmpGroup.key ) + { + // new group ready! + groupData.push_back( tmpGroup ); + tmpGroup.key = *it; + tmpGroup.count = 0; + } + + ++tmpGroup.count; + } + + // last group + groupData.push_back( tmpGroup ); +} + +// ------------------------------------------------------------------------- + +template +void keys2GroupData( + const std::vector& keys, // in + std::deque& groupData, + bool clearDst = true ) // out +{ + if (clearDst) + groupData.clear(); + + if (keys.empty()) + return; + + TGroupData tmpGroup; + std::vector::const_iterator it = keys.begin(); + + if ( !groupData.empty() ) + { + // get the last group + tmpGroup = groupData.back(); + groupData.pop_back(); + } + else + { + // new group! + tmpGroup.key = *it; + tmpGroup.count = 1; + ++it; // move to the next key + } + + for (; it != keys.end(); ++it) + { + if ( *it != tmpGroup.key ) + { + // new group ready! + groupData.push_back( tmpGroup ); + tmpGroup.key = *it; + tmpGroup.count = 0; + } + + ++tmpGroup.count; + } + + // last group + groupData.push_back( tmpGroup ); +} + +// ------------------------------------------------------------------------- + +template +inline +void groupData2Keys( + const std::vector& groupData, // in + std::vector& keys ) // out +{ + keys.clear(); + + typename std::vector::const_iterator it; + + for (it = groupData.begin(); it != groupData.end(); ++it) + { + for (unsigned int j = 0; j < it->count; ++j) + keys.push_back(it->key); + } +} + +// ------------------------------------------------------------------------- + +template +bool findSignificantGroups( + GroupDataIt& beg, GroupDataIt& end, unsigned int& offset_left, unsigned int& offset_right, + unsigned int windowKeySize, unsigned int subWindowKeySize, unsigned int minUniqueKeys) +{ + GroupDataIt itBeg = beg, itEnd = beg, itWindowBeg = beg, itWindowEnd = beg; + + offset_left = 0; + unsigned int window_offset_left; + unsigned int window_offset_right; + + // this amounts to around a 500 ms hop for, say, a 20 second sub-window + unsigned int key_hop_size = subWindowKeySize / 40; + + // trail out itEnd + for (offset_right = windowKeySize; itEnd != end && offset_right > itEnd->count; ++itEnd) + offset_right -= itEnd->count; + + // dang man, we don't even have enough groups to span the window size + if (itEnd == end && offset_right > 0) + return false; + + // 0 window size means just scan the whole range + if (windowKeySize == 0) + itEnd = end; + + // trail out itWindowBeg + for (window_offset_left = (windowKeySize - subWindowKeySize) / 2; + window_offset_left > itWindowBeg->count; ++itWindowBeg) + window_offset_left -= itWindowBeg->count; + + // trail out itWindowEnd + for (window_offset_right = (windowKeySize + subWindowKeySize) / 2; + window_offset_right > itWindowEnd->count; ++itWindowEnd) + window_offset_right -= itWindowEnd->count; + + while (itEnd != end) + { + if (enoughUniqueGoodGroups(itWindowBeg, itWindowEnd, minUniqueKeys)) + { + beg = itBeg; + end = itEnd; + return true; + } + + // okay, jump key_hop_size on end iterator + for (offset_right += key_hop_size; itEnd != end && offset_right > itEnd->count; ++itEnd) + offset_right -= itEnd->count; + + // if we didn't hop the full hop size, modify the hop size to only hop as far as we hopped + if (itEnd == end) + key_hop_size -= offset_right; + + for (offset_left += key_hop_size; offset_left > itBeg->count; ++itBeg) + offset_left -= itBeg->count; + for (window_offset_right += key_hop_size; window_offset_right > itWindowEnd->count; ++itWindowEnd) + window_offset_right -= itWindowEnd->count; + for (window_offset_left += key_hop_size; window_offset_left > itWindowBeg->count; ++itWindowBeg) + window_offset_left -= itWindowBeg->count; + } + + beg = itBeg; + end = itEnd; + + return enoughUniqueGoodGroups(itWindowBeg, itWindowEnd, minUniqueKeys); +} + +// ----------------------------------------------------------------------------- + +template +bool +reduceGroups( + std::vector& groups, unsigned int startKeySize, + unsigned int windowKeySize, unsigned int subWindowKeySize, unsigned int minUniqueKeys ) +{ + unsigned int offset_left = 0; + unsigned int offset_right = 0; + + typename std::vector::iterator begIt = groups.begin(); + typename std::vector::iterator endIt = groups.end(); + + simpleSkip(begIt, endIt, startKeySize); + bool result = findSignificantGroups( begIt, endIt, + offset_left, offset_right, + windowKeySize, subWindowKeySize, minUniqueKeys ); + + if ( !result ) + { + groups.clear(); + return false; + } + + begIt->count -= offset_left; + if (offset_right > 0 && endIt != groups.end()) + { + endIt->count = offset_right; + ++endIt; + } + + std::vector resGrups(begIt, endIt); + groups.swap(resGrups); + + return true; +} + + +// ------------------------------------------------------------------------- + +template +inline bool enoughUniqueGoodGroups( + const GroupDataIt& beg, + const GroupDataIt& end, + unsigned int minUniqueKeys) +{ + std::set groupKeys; + + for (GroupDataIt it = beg; it != end && static_cast(groupKeys.size()) < minUniqueKeys; ++it) + { + if (it->count > MAX_GOOD_GROUP_SIZE) + return false; + + groupKeys.insert(it->key); + } + + return static_cast(groupKeys.size()) >= minUniqueKeys; +} + +// ----------------------------------------------------------------------------- +////////////////////////////////////////////////////////////////////////// +////////////////////////////////////////////////////////////////////////// +// Used by the fingerprint keys operation + +// minimum and maximum frequency to consider +#define MINFREQ 300 +#define MAXFREQ 2000 + +// amount of time in a frame +#define FRAME_TLEN ((float) FRAMESIZE / (float) DFREQ) +#define MINCOEF (FRAME_TLEN * MINFREQ) + +#define round__(x) ((int)(x + .5)) + +struct RawFilter +{ + unsigned int ftid; + float thresh; + float weight; +}; + +const RawFilter rFilters[] = { + { 26752, -4.37515e-07f, 0.260836f }, // filterID, threshold, alpha (weight) + { 23871, -2.44615e-05f, 0.263986f }, + { 26777, -3.69244e-08f, 0.267763f }, + { 4635, -1.13672e-05f, 0.269428f }, + { 2937, 5.28804e-09f, 0.271896f }, + { 27405, -0.000126494f, 0.272362f }, + { 10782, 4.27478e-08f, 0.272609f }, + { 21033, -6.7912e-07f, 0.276099f }, + { 27117, 8.07178e-06f, 0.277762f }, + { 27072, 2.46044e-05f, 0.27883f }, + { 24228, 4.11255e-07f, 0.281743f }, + { 23838, 0.000228396f, 0.284479f }, + { 17165, -1.19495e-07f, 0.286304f }, + { 25263, 0.000398279f, 0.287066f }, + { 20721, 7.15095e-07f, 0.288913f }, + { 8502, -2.78361e-07f, 0.290424f }, + { 17175, -1.08429e-08f, 0.292219f }, + { 17811, -3.29527e-08f, 0.292554f }, + { 27495, -4.47575e-07f, 0.290119f }, + { 23538, -3.04273e-09f, 0.294539f }, + { 8205, 4.02691e-07f, 0.293525f }, + { 12177, 1.16873e-06f, 0.293832f }, + { 27051, -0.000902544f, 0.296453f }, + { 27111, -2.38425e-05f, 0.297428f }, + { 21779, -1.0669e-07f, 0.297302f }, + { 14817, -9.52849e-09f, 0.299f }, + { 27087, 1.22163e-05f, 0.296502f }, + { 27081, -2.8758e-09f, 0.300112f }, + { 20394, 1.28237e-06f, 0.298693f }, + { 28209, 0.000624447f, 0.29812f }, + { 23533, -2.19406e-06f, 0.299773f }, + { 23865, -1.28037e-08f, 0.300777f } // this is iteration 1 +}; + +// ----------------------------------------------------------------------------- + +} + +// ----------------------------------------------------------------------------- + +#endif // __FINGERPRINT_HELPER_FUNCTIONS_H + diff --git a/thirdparty/liblastfm2/src/global.h b/thirdparty/liblastfm2/src/global.h new file mode 100644 index 000000000..d297ae702 --- /dev/null +++ b/thirdparty/liblastfm2/src/global.h @@ -0,0 +1,136 @@ +/* + Copyright 2009 Last.fm Ltd. + - Primarily authored by Max Howell, Jono Cole and Doug Mansell + + This file is part of liblastfm. + + liblastfm 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. + + liblastfm 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 liblastfm. If not, see . +*/ + +#ifndef LASTFM_GLOBAL_H +#define LASTFM_GLOBAL_H + +#define LASTFM_VERSION 0x00000400 +#define LASTFM_VERSION_STRING "0.4.0" +#define LASTFM_MAJOR_VERSION 0 +#define LASTFM_MINOR_VERSION 4 +#define LASTFM_PATCH_VERSION 0 + + +#include + +#ifdef Q_CC_MSVC + #ifdef LASTFM_LIB + #define LASTFM_DLLEXPORT __declspec(dllexport) + #else + #define LASTFM_DLLEXPORT __declspec(dllimport) + #endif + #ifdef LASTFM_FINGERPRINT_LIB + #define LASTFM_FINGERPRINT_DLLEXPORT __declspec(dllexport) + #else + #define LASTFM_FINGERPRINT_DLLEXPORT __declspec(dllimport) + #endif +#elif __GNUC__ >= 4 + #define LASTFM_DLLEXPORT __attribute__ ((visibility("default"))) + #define LASTFM_FINGERPRINT_DLLEXPORT __attribute__ ((visibility("default"))) +#else + #define LASTFM_DLLEXPORT + #define LASTFM_FINGERPRINT_DLLEXPORT +#endif + + + +#include +#include + +namespace lastfm +{ + /** http://labs.trolltech.com/blogs/2008/10/09/coding-tip-pretty-printing-enum-values + * Tips for making this take a single parameter welcome! :) + * + * eg. lastfm::qMetaEnumString( error, "NetworkError" ); + */ + template static inline QString qMetaEnumString( int enum_value, const char* enum_name ) + { + QMetaObject meta = T::staticMetaObject; + for (int i=0; i < meta.enumeratorCount(); ++i) + { + QMetaEnum m = meta.enumerator(i); + if (m.name() == QLatin1String(enum_name)) + return QLatin1String(m.valueToKey(enum_value)); + } + return QString("Unknown enum value for \"%1\": %2").arg( enum_name ).arg( enum_value ); + } + + + enum ImageSize + { + Small, + Medium, + Large, /** seemingly 174x174 */ + ExtraLarge, + Mega + }; + + + //convenience + class Album; + class Artist; + class Audioscrobbler; + class AuthenticatedUser; + class Fingerprint; + class FingerprintableSource; + class FingerprintId; + class Mbid; + class MutableTrack; + class NetworkAccessManager; + class Playlist; + class User; + class RadioStation; + class Tag; + class Track; + class XmlQuery; + class Xspf; +} + + +#ifdef LASTFM_COLLAPSE_NAMESPACE +using lastfm::Album; +using lastfm::Artist; +using lastfm::Audioscrobbler; +using lastfm::AuthenticatedUser; +using lastfm::Fingerprint; +using lastfm::FingerprintId; +using lastfm::Mbid; +using lastfm::MutableTrack; +using lastfm::Playlist; +using lastfm::User; +using lastfm::RadioStation; +using lastfm::Tag; +using lastfm::Track; +using lastfm::XmlQuery; +using lastfm::Xspf; +#endif + + +//convenience +class QDomDocument; +class QNetworkAccessManager; +class QNetworkReply; + + +//convenience for development +#include + +#endif //LASTFM_GLOBAL_H diff --git a/thirdparty/liblastfm2/src/lastfm.pro b/thirdparty/liblastfm2/src/lastfm.pro new file mode 100644 index 000000000..c0b306f84 --- /dev/null +++ b/thirdparty/liblastfm2/src/lastfm.pro @@ -0,0 +1,20 @@ +TEMPLATE = lib +QT = core network xml +include( _files.qmake ) + +INSTALLS = target +target.path = /lib + +win32{ + DEFINES += LASTFM_LIB _ATL_DLL + LIBS += winhttp.lib wbemuuid.lib # ws configuration +} +mac{ + LIBS += -framework SystemConfiguration # ws configuration + #TODO we should only use these with the carbon version of Qt! + LIBS += -framework Carbon -framework CoreFoundation # various +} + +linux*{ + QT += dbus +} diff --git a/thirdparty/liblastfm2/src/radio/RadioStation.cpp b/thirdparty/liblastfm2/src/radio/RadioStation.cpp new file mode 100755 index 000000000..38e9c7d3c --- /dev/null +++ b/thirdparty/liblastfm2/src/radio/RadioStation.cpp @@ -0,0 +1,201 @@ +/* + Copyright 2009 Last.fm Ltd. + + This file is part of liblastfm. + + liblastfm 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. + + liblastfm 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 liblastfm. If not, see . +*/ + +#include +#include + +#include "RadioStation.h" +#include "../core/XmlQuery.h" + +QRegExp rxDisco("opt:discovery\\|(\\S+)", Qt::CaseSensitive, QRegExp::RegExp2); +QRegExp rxRep("opt:rep\\|([\\d\\.]+)", Qt::CaseSensitive, QRegExp::RegExp2); +QRegExp rxMainstr("opt:mainstr\\|([\\d\\.]+)", Qt::CaseSensitive, QRegExp::RegExp2); + + +const float k_defaultRep(0.5); +const float k_defaultMainstr(0.5); +const bool k_defaultDisco(false); + +//static +QList +lastfm::RadioStation::list( QNetworkReply* r ) +{ + QList result; + try { + foreach (XmlQuery xq, XmlQuery(ws::parse(r)).children("station")) { + lastfm::RadioStation rs( QUrl::fromPercentEncoding( xq["url"].text().toUtf8() ) ); + rs.setTitle(xq["name"].text()); + result.append(rs); + } + } + catch (ws::ParseError& e) + { + qWarning() << e.what(); + } + return result; +} + +void +lastfm::RadioStation::setString( const QString& string ) +{ + QString replaceString( string ); + QString decodedString = QUrl::fromPercentEncoding( replaceString.replace( QChar('+'), QChar(' ') ).toUtf8() ); + + QRegExp rxRql( "lastfm:\\/\\/rql\\/(.+)$" ); + QRegExp rxPersonal( "lastfm:\\/\\/user\\/(.+)\\/personal" ); + QRegExp rxRecommended( "lastfm://user/(.+)\\/recommended" ); + QRegExp rxNeighbours( "lastfm:\\/\\/user\\/(.+)\\/neighbours" ); + QRegExp rxLoved( "lastfm:\\/\\/user\\/(.+)\\/loved" ); + QRegExp rxGlobalTags( "lastfm:\\/\\/globaltags\\/(.+)" ); + QRegExp rxSimilarArtists( "lastfm:\\/\\/artist\\/(.+)\\/similarartists" ); + QRegExp rxUserTags( "lastfm:\\/\\/usertags\\/(.+)\\/(.+)" ); + QRegExp rxPlaylist( "lastfm:\\/\\/playlist/(.+)\\/shuffle" ); + + if (rxRql.indexIn(decodedString) == 0) + setRql( QByteArray::fromBase64( rxRql.capturedTexts()[1].toAscii() ) ); + else if (rxPersonal.indexIn(decodedString) == 0) + setRql( libraryStr( rxPersonal.capturedTexts()[1] ) ); + else if ( rxRecommended.indexIn(decodedString) == 0) + setRql( recommendationsStr( rxRecommended.capturedTexts()[1] ) ); + else if ( rxNeighbours.indexIn(decodedString) == 0) + setRql( neighbourhoodStr( rxNeighbours.capturedTexts()[1] ) ); + else if ( rxLoved.indexIn(decodedString) == 0) + setRql( lovedTracksStr( rxLoved.capturedTexts()[1] ) ); + else if ( rxGlobalTags.indexIn(decodedString) == 0) + setRql( globalTagStr( rxGlobalTags.capturedTexts()[1] ) ); + else if ( rxSimilarArtists.indexIn(decodedString) == 0) + setRql( similarStr( rxSimilarArtists.capturedTexts()[1] ) ); + else if ( rxUserTags.indexIn(decodedString) == 0) + setRql( userTagStr( rxUserTags.capturedTexts()[1], rxUserTags.capturedTexts()[2] ) ); + else if ( rxPlaylist.indexIn(decodedString) == 0) + setRql( playlistStr( rxPlaylist.capturedTexts()[1].toInt() ) ); + else + { + m_url = string; + } +} + +bool +lastfm::RadioStation::setRep(float rep) +{ + if ( m_rql.isEmpty() ) + return false; + + int indexIn = rxRep.indexIn(m_rql); + + if ( indexIn != -1 ) + { + if (rep != k_defaultRep) + m_rql.replace( indexIn, rxRep.capturedTexts()[0].length(), QString("opt:rep|%1").arg(rep) ); + else + m_rql.replace( indexIn, rxRep.capturedTexts()[0].length(), "" ); + } + else + { + // the rql doesn't have rep in it + // so append it to the end + if (rep != k_defaultRep) + m_rql.append( QString(" opt:rep|%1").arg(rep) ); + } + + setRql(m_rql); + + return true; +} + +bool +lastfm::RadioStation::setMainstr(float mainstr) +{ + if ( m_rql.isEmpty() ) + return false; + + int indexIn = rxMainstr.indexIn(m_rql); + + if ( indexIn != -1 ) + { + if (mainstr != k_defaultMainstr) + m_rql.replace( indexIn, rxMainstr.capturedTexts()[0].length(), QString("opt:mainstr|%1").arg(mainstr) ); + else + m_rql.replace( indexIn, rxMainstr.capturedTexts()[0].length(), "" ); + } + else + { + // the rql doesn't have rep in it + // so append it to the end + if ( mainstr != k_defaultMainstr ) + m_rql.append( QString(" opt:mainstr|%1").arg(mainstr) ); + } + + setRql(m_rql); + + return true; +} + +bool +lastfm::RadioStation::setDisco(bool disco) +{ + if ( m_rql.isEmpty() ) + return false; + + int indexIn = rxDisco.indexIn(m_rql); + + if ( indexIn != -1 ) + { + if (disco) + m_rql.replace( indexIn, rxDisco.capturedTexts()[0].length(), "opt:discovery|true" ); + else + m_rql.replace( indexIn, rxDisco.capturedTexts()[0].length(), "" ); + } + else + { + // the rql doesn't have disco in it + // so append it to the end if it is set + + if (disco) + m_rql.append( " opt:discovery|true" ); + } + + setRql(m_rql); + + return true; +} + +float lastfm::RadioStation::rep() const +{ + if ( rxRep.indexIn(m_rql) != -1 ) + return rxRep.capturedTexts()[1].toFloat(); + + return k_defaultRep; +} + +float lastfm::RadioStation::mainstr() const +{ + if ( rxMainstr.indexIn(m_rql) != -1 ) + return rxMainstr.capturedTexts()[1].toFloat(); + + return k_defaultMainstr; +} + +bool lastfm::RadioStation::disco() const +{ + if ( rxDisco.indexIn(m_rql) != -1 ) + return rxDisco.capturedTexts()[1] == "true"; + + return k_defaultDisco; +} diff --git a/thirdparty/liblastfm2/src/radio/RadioStation.h b/thirdparty/liblastfm2/src/radio/RadioStation.h new file mode 100644 index 000000000..09ee27743 --- /dev/null +++ b/thirdparty/liblastfm2/src/radio/RadioStation.h @@ -0,0 +1,123 @@ +/* + Copyright 2009 Last.fm Ltd. + - Primarily authored by Max Howell, Jono Cole and Doug Mansell + + This file is part of liblastfm. + + liblastfm 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. + + liblastfm 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 liblastfm. If not, see . +*/ +#ifndef LASTFM_RADIO_STATION_H +#define LASTFM_RADIO_STATION_H + +#include +#include +#include + +namespace lastfm +{ + /** @author + */ + class LASTFM_DLLEXPORT RadioStation + { + public: + RadioStation() + {} + RadioStation( const QString& s ) + { + setString( s ); + } + + static RadioStation library( const lastfm::User& user ) { return rql( libraryStr( user ) ); } + static RadioStation recommendations( const lastfm::User& user ) { return rql( recommendationsStr( user ) ); } + static RadioStation neighbourhood( const lastfm::User& user ) { return rql( neighbourhoodStr( user ) ); } + static RadioStation lovedTracks( const lastfm::User& user ) { return rql( lovedTracksStr( user ) ); } + static RadioStation globalTag( const lastfm::Tag& tag ) { return rql( globalTagStr( tag ) ); } + static RadioStation similar( const lastfm::Artist& artist ) { return rql( similarStr( artist ) ); } + static RadioStation userTag( const lastfm::User& user, const lastfm::Tag& tag) { return rql( userTagStr( user, tag ) ); } + static RadioStation playlist( int playlistId ) { return rql( playlistStr( playlistId ) ); } + static RadioStation adventure( const lastfm::User& user ) { return rql( adventureStr( user ) ); } + + static RadioStation rql( const QString& rql ) + { + RadioStation station; + station.setRql( rql ); + return station; + } + + /** eg. "mxcl's Loved Tracks" + * It is worth noting that the Radio doesn't set the title of RadioStation + * object until we have tuned to it, and then we only set the one we give + * you back. + */ + QString title() const { return m_title; } + /** the Last.fm url, eg. lastfm://user/mxcl/loved */ + QString url() const { return m_url; } + QString rql() const { return m_rql; } + + void setTitle( const QString& s ) { m_title = s; } + + bool setRep(float rep); + bool setMainstr(float mainstr); + bool setDisco(bool disco); + + float rep() const; + float mainstr() const; + bool disco() const; + + bool isLegacyPlaylist() const + { + return m_url.startsWith( "lastfm://play/" ) || + m_url.startsWith( "lastfm://preview/" ) || + m_url.startsWith( "lastfm://track/" ) || + m_url.startsWith( "lastfm://playlist/" ); + } + + // good for getRecentStations: + static QList list( QNetworkReply* ); + + private: + void setRql( const QString& rql ) + { + m_rql = rql; + m_url = "lastfm://rql/" + QString(rql.toUtf8().toBase64()); + } + + void setString( const QString& s ); + + static QString libraryStr( const lastfm::User& user ) { return "library:" + user ; } + static QString recommendationsStr( const lastfm::User& user ) { return "rec:" + user ; } + static QString neighbourhoodStr( const lastfm::User& user ) { return "neigh:" + user ; } + static QString lovedTracksStr( const lastfm::User& user ) { return "loved:" + user ; } + static QString globalTagStr( const lastfm::Tag& tag ) { return "tag:\"" + tag + "\"" ; } + static QString similarStr( const lastfm::Artist& artist ) { return "simart:\"" + artist + "\""; } + static QString userTagStr( const lastfm::User& user, const lastfm::Tag& tag) { return "ptag:\"" + tag + "\"|" + user ; } + static QString playlistStr( int playlistId ) { return "playlist:" + QString::number(playlistId) ; } + static QString adventureStr( const lastfm::User& user ) { return "adv:" + user ; } + private: + QString m_rql; + QString m_url; + QString m_title; + }; +} + + +Q_DECLARE_METATYPE( lastfm::RadioStation ) + + +inline QDebug operator<<( QDebug d, const lastfm::RadioStation& station ) +{ + return d << station.url(); +} + +#endif diff --git a/thirdparty/liblastfm2/src/radio/RadioTuner.cpp b/thirdparty/liblastfm2/src/radio/RadioTuner.cpp new file mode 100644 index 000000000..f09b560e1 --- /dev/null +++ b/thirdparty/liblastfm2/src/radio/RadioTuner.cpp @@ -0,0 +1,152 @@ +/* + Copyright 2009 Last.fm Ltd. + - Primarily authored by Max Howell, Jono Cole and Doug Mansell + + This file is part of liblastfm. + + liblastfm 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. + + liblastfm 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 liblastfm. If not, see . +*/ +#include "RadioTuner.h" +#include "../core/XmlQuery.h" +#include "../types/Xspf.h" +#include "../ws/ws.h" +using namespace lastfm; + +//TODO skips left +//TODO multiple locations for the same track +//TODO set rtp flag in getPlaylist (whether user is scrobbling this radio session or not) + +// limit the number of retries following empty playlists: +#define MAX_TUNING_ATTEMPTS 3 + + +RadioTuner::RadioTuner( const RadioStation& station ) + : m_retry_counter( 0 ) +{ + //Empty RadioStation implies that the radio + //should tune to the previous station. + if( station.url().isEmpty() ) { + fetchFiveMoreTracks(); + return; + } + + QMap map; + map["method"] = "radio.tune"; + map["station"] = station.url(); + map["additional_info"] = "1"; + QNetworkReply* reply = ws::post(map); + connect( reply, SIGNAL(finished()), SLOT(onTuneReturn()) ); +} + +void +RadioTuner::retune( const RadioStation& station) +{ + m_queue.clear(); + + QMap map; + map["method"] = "radio.tune"; + map["station"] = station.url(); + map["additional_info"] = "1"; + QNetworkReply* reply = ws::post(map); + connect( reply, SIGNAL(finished()), SLOT(onTuneReturn()) ); +} + + +void +RadioTuner::onTuneReturn() +{ + try { + XmlQuery lfm = ws::parse( (QNetworkReply*)sender() ); + // TODO: uncomment this is we are to get a radio station + // name when we tune to an rql radio station + //emit title( lfm["station"]["name"].text() ); + + qDebug() << lfm; + + emit supportsDisco( lfm["station"]["supportsdiscovery"].text() == "1" ); + fetchFiveMoreTracks(); + } + catch (ws::ParseError& e) + { + emit error( e.enumValue() ); + } +} + + +bool +RadioTuner::fetchFiveMoreTracks() +{ + //TODO check documentation, I figure this needs a session key + QMap map; + map["method"] = "radio.getPlaylist"; + map["additional_info"] = "1"; + map["rtp"] = "1"; // see above + QNetworkReply* reply = ws::post( map ); + connect( reply, SIGNAL(finished()), SLOT(onGetPlaylistReturn()) ); + return true; +} + + +bool +RadioTuner::tryAgain() +{ + qDebug() << "Bad response count" << m_retry_counter; + + if (++m_retry_counter > MAX_TUNING_ATTEMPTS) + return false; + fetchFiveMoreTracks(); + return true; +} + + +void +RadioTuner::onGetPlaylistReturn() +{ + try { + XmlQuery lfm = ws::parse( (QNetworkReply*)sender() ); + Xspf xspf( lfm["playlist"] ); + QList tracks( xspf.tracks() ); + if (tracks.isEmpty()) { + // give up after too many empty playlists :( + if (!tryAgain()) + emit error( ws::NotEnoughContent ); + } else { + m_retry_counter = 0; + foreach (Track t, tracks) + MutableTrack( t ).setSource( Track::LastFmRadio ); + m_queue += tracks; + emit trackAvailable(); + } + } + catch (ws::ParseError& e) + { + qWarning() << e.what(); + emit error( e.enumValue() ); + } +} + + +Track +RadioTuner::takeNextTrack() +{ + //TODO presumably, we should check if fetchMoreTracks is working? + if (m_queue.isEmpty()) + return Track(); + + Track result = m_queue.takeFirst(); + if (m_queue.isEmpty()) + fetchFiveMoreTracks(); + + return result; +} diff --git a/thirdparty/liblastfm2/src/radio/RadioTuner.h b/thirdparty/liblastfm2/src/radio/RadioTuner.h new file mode 100644 index 000000000..7032dfd08 --- /dev/null +++ b/thirdparty/liblastfm2/src/radio/RadioTuner.h @@ -0,0 +1,76 @@ +/* + Copyright 2009 Last.fm Ltd. + - Primarily authored by Max Howell, Jono Cole and Doug Mansell + + This file is part of liblastfm. + + liblastfm 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. + + liblastfm 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 liblastfm. If not, see . +*/ +#ifndef LASTFM_TUNER_H +#define LASTFM_TUNER_H + +#include +#include +#include +#include + +namespace lastfm +{ + /** With regard to error handling. We handle Ws::TryAgain up to 5 times, + * don't try again after that! Just tell the user to try again later. + */ + class LASTFM_DLLEXPORT RadioTuner : public QObject + { + Q_OBJECT + + public: + /** You need to have assigned Ws::* for this to work, creating the tuner + * automatically fetches the first 5 tracks for the station */ + explicit RadioTuner( const RadioStation& ); + + Track takeNextTrack(); + + void retune( const RadioStation& ); + + signals: + void title( const QString& ); + void supportsDisco( bool supportsDisco ); + void trackAvailable(); + void error( lastfm::ws::Error ); + + private slots: + void onTuneReturn(); + void onGetPlaylistReturn(); + + private: + /** Tries again up to 5 times + * @returns true if we tried again, otherwise you should emit error */ + bool tryAgain(); + /** Will emit 5 tracks from tracks(), they have to played within an hour + * or the streamer will refuse to stream them. Also the previous five are + * invalidated apart from the one that is currently playing, so sorry, you + * can't build up big lists of tracks. + * + * I feel I must point out that asking the user which one they want to play + * is also not allowed according to our terms and conditions, which you + * already agreed to in order to get your API key. Sorry about that dude. + */ + bool fetchFiveMoreTracks(); + + QList m_queue; + uint m_retry_counter; + }; +} + +#endif diff --git a/thirdparty/liblastfm2/src/scrobble/Audioscrobbler.cpp b/thirdparty/liblastfm2/src/scrobble/Audioscrobbler.cpp new file mode 100644 index 000000000..3606bd1a4 --- /dev/null +++ b/thirdparty/liblastfm2/src/scrobble/Audioscrobbler.cpp @@ -0,0 +1,202 @@ +/* + Copyright 2009 Last.fm Ltd. + - Primarily authored by Max Howell, Jono Cole and Doug Mansell + + This file is part of liblastfm. + + liblastfm 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. + + liblastfm 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 liblastfm. If not, see . +*/ + +#include + +#include "Audioscrobbler.h" +#include "ScrobbleCache.h" + +#include "../types/User.h" +#include "../types/Track.h" +#include "../ws/ws.h" +#include "../core/XmlQuery.h" + + +namespace lastfm +{ + class AudioscrobblerPrivate + { + public: + AudioscrobblerPrivate(const QString& id) + : m_id( id ) + , m_cache( ws::Username ) + {} + + ~AudioscrobblerPrivate() + { + } + + const QString m_id; + ScrobbleCache m_cache; + QList m_batch; + QPointer m_nowPlayingReply; + QPointer m_scrobbleReply; + Track m_nowPlayingTrack; + }; +} + + +lastfm::Audioscrobbler::Audioscrobbler( const QString& id ) + : d( new AudioscrobblerPrivate(id) ) +{ + submit(); +} + + +lastfm::Audioscrobbler::~Audioscrobbler() +{ + delete d; +} + + +void +lastfm::Audioscrobbler::nowPlaying( const Track& track ) +{ + if ( d->m_nowPlayingReply.isNull()) + { + d->m_nowPlayingTrack = track; + d->m_nowPlayingReply = track.updateNowPlaying(); + connect( d->m_nowPlayingReply, SIGNAL(finished()), SLOT(onNowPlayingReturn())); + } +} + + +void +lastfm::Audioscrobbler::cache( const Track& track ) +{ + QList tracks; + tracks.append( track ); + cacheBatch( tracks ); +} + + +void +lastfm::Audioscrobbler::cacheBatch( const QList& tracks ) +{ + d->m_cache.add( tracks ); + + foreach ( const Track& track, d->m_cache.tracks() ) + MutableTrack( track ).setScrobbleStatus( Track::Cached ); + + emit scrobblesCached( tracks ); + submit(); +} + + +void +lastfm::Audioscrobbler::submit() +{ + if (d->m_cache.tracks().isEmpty() // there are no tracks to submit + || !d->m_scrobbleReply.isNull() ) // we are already submitting scrobbles + return; + + // copy tracks to be submitted to a temporary list + d->m_batch = d->m_cache.tracks().mid( 0, 50 ); + + // if there is only one track use track.scrobble, otherwise use track.scrobbleBatch + if (d->m_batch.count() == 1) + d->m_scrobbleReply = d->m_batch[0].scrobble(); + else + d->m_scrobbleReply = lastfm::Track::scrobble( d->m_batch ); + + connect( d->m_scrobbleReply, SIGNAL(finished()), SLOT(onTrackScrobbleReturn())); +} + +void +lastfm::Audioscrobbler::parseTrack( const XmlQuery& trackXml, const Track& track ) +{ + MutableTrack mTrack = MutableTrack( track ); + bool isScrobble = QDomElement(trackXml).tagName() == "scrobble"; + + if ( trackXml["ignoredMessage"].attribute("code") == "0" ) + { + if ( isScrobble ) mTrack.setScrobbleStatus( Track::Submitted ); + + // corrections! + if ( trackXml["track"].attribute("corrected") == "1" + || trackXml["artist"].attribute("corrected") == "1" + || trackXml["album"].attribute("corrected") == "1" + || trackXml["albumArtist"].attribute("corrected") == "1") + { + mTrack.setCorrections(trackXml["track"].text(), + trackXml["album"].text(), + trackXml["artist"].text(), + trackXml["albumArtist"].text()); + } + } + else if ( isScrobble ) + { + mTrack.setScrobbleError( static_cast(trackXml["ignoredMessage"].attribute("code").toInt()) ); + mTrack.setScrobbleStatus( Track::Error ); + } +} + +void +lastfm::Audioscrobbler::onNowPlayingReturn() +{ + lastfm::XmlQuery lfm = static_cast(sender())->readAll(); + qDebug() << lfm; + + if ( lfm.attribute("status") == "ok" ) + parseTrack( lfm["nowplaying"], d->m_nowPlayingTrack ); + else + emit nowPlayingError( lfm["error"].attribute("code").toInt(), lfm["error"].text() ); + + d->m_nowPlayingTrack = Track(); + d->m_nowPlayingReply = 0; +} + + +void +lastfm::Audioscrobbler::onTrackScrobbleReturn() +{ + lastfm::XmlQuery lfm = d->m_scrobbleReply->readAll(); + qDebug() << lfm; + + if (lfm.attribute("status") == "ok") + { + int index = 0; + + foreach ( const XmlQuery& scrobble, lfm["scrobbles"].children("scrobble") ) + parseTrack( scrobble, d->m_batch.at( index++ ) ); + + d->m_cache.remove( d->m_batch ); + d->m_batch.clear(); + } + else + { + // The scrobble submission failed + + if ( !(lfm["error"].attribute("code") == "9" // Bad session + || lfm["error"].attribute("code") == "11" // Service offline + || lfm["error"].attribute("code") == "16") ) // Service temporarily unavailable + { + // clear the cache if it was not one of these error codes + d->m_cache.remove( d->m_batch ); + d->m_batch.clear(); + } + else + { + Q_ASSERT(false); + } + } + + d->m_scrobbleReply = 0; +} diff --git a/thirdparty/liblastfm2/src/scrobble/Audioscrobbler.h b/thirdparty/liblastfm2/src/scrobble/Audioscrobbler.h new file mode 100644 index 000000000..8ad93c026 --- /dev/null +++ b/thirdparty/liblastfm2/src/scrobble/Audioscrobbler.h @@ -0,0 +1,75 @@ +/* + Copyright 2009 Last.fm Ltd. + - Primarily authored by Max Howell, Jono Cole and Doug Mansell + + This file is part of liblastfm. + + liblastfm 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. + + liblastfm 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 liblastfm. If not, see . +*/ +#ifndef LASTFM_AUDIOSCROBBLER_H +#define LASTFM_AUDIOSCROBBLER_H + +#include +#include +#include +#include +#include +#include + +namespace lastfm +{ + /** @author Max Howell + * An implementation of the Audioscrobbler Realtime Submissions Protocol + * version 1.2.1 for a single Last.fm user + * http://www.audioscrobbler.net/development/protocol/ + */ + class LASTFM_DLLEXPORT Audioscrobbler : public QObject + { + Q_OBJECT + + public: + /** You will need to do QCoreApplication::setVersion and + * QCoreApplication::setApplicationName for this to work, also you will + * need to have set all the keys in the Ws namespace in WsKeys.h */ + Audioscrobbler( const QString& clientId ); + ~Audioscrobbler(); + + signals: + void scrobblesCached( const QList& tracks ); + void nowPlayingError( int code, QString message ); + + public slots: + /** will ask Last.fm to update the now playing information for the + * authenticated user */ + void nowPlaying( const Track& ); + /** will cache the track and call submit() */ + void cache( const Track& ); + void cacheBatch( const QList& ); + + /** will submit the submission cache for this user */ + void submit(); + + private slots: + void onNowPlayingReturn(); + void onTrackScrobbleReturn(); + + private: + void parseTrack( const XmlQuery& trackXml, const Track& track ); + + private: + class AudioscrobblerPrivate* d; + }; +} + +#endif diff --git a/thirdparty/liblastfm2/src/scrobble/ScrobbleCache.cpp b/thirdparty/liblastfm2/src/scrobble/ScrobbleCache.cpp new file mode 100644 index 000000000..583d4b4d0 --- /dev/null +++ b/thirdparty/liblastfm2/src/scrobble/ScrobbleCache.cpp @@ -0,0 +1,163 @@ +/* + Copyright 2009 Last.fm Ltd. + - Primarily authored by Max Howell, Jono Cole and Doug Mansell + + This file is part of liblastfm. + + liblastfm 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. + + liblastfm 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 liblastfm. If not, see . +*/ +#include "ScrobbleCache.h" +#include "ScrobblePoint.h" +#include +#include +#include +#include +#include + +#if LASTFM_VERSION >= 0x00010000 +using lastfm::ScrobbleCache; +#endif + +ScrobbleCache::ScrobbleCache( const QString& username ) +{ + Q_ASSERT( username.length() ); + + m_path = lastfm::dir::runtimeData().filePath( username + "_subs_cache.xml" ); + m_username = username; + + QDomDocument xml; + read( xml ); +} + + +bool +ScrobbleCache::isValid( const Track& track, Invalidity* v ) +{ + #define TEST( test, x ) \ + if (test) { \ + if (v) *v = x; \ + return false; \ + } + + TEST( track.duration() < ScrobblePoint::kScrobbleMinLength, TooShort ); + + TEST( !track.timestamp().isValid(), NoTimestamp ); + + // actual spam prevention is something like 12 hours, but we are only + // trying to weed out obviously bad data, server side criteria for + // "the future" may change, so we should let the server decide, not us + TEST( track.timestamp() > QDateTime::currentDateTime().addMonths( 1 ), FromTheFuture ); + + TEST( track.timestamp() < QDateTime::fromString( "2003-01-01", Qt::ISODate ), FromTheDistantPast ); + + // Check if any required fields are empty + TEST( track.artist().isNull(), ArtistNameMissing ); + TEST( track.title().isEmpty(), TrackNameMissing ); + + TEST( (QStringList() << "unknown artist" + << "unknown" + << "[unknown]" + << "[unknown artist]").contains( track.artist().name().toLower() ), + ArtistInvalid ); + + return true; +} + + +void +ScrobbleCache::read( QDomDocument& xml ) +{ + m_tracks.clear(); + + QFile file( m_path ); + file.open( QFile::Text | QFile::ReadOnly ); + QTextStream stream( &file ); + stream.setCodec( "UTF-8" ); + + xml.setContent( stream.readAll() ); + + for (QDomNode n = xml.documentElement().firstChild(); !n.isNull(); n = n.nextSibling()) + if (n.nodeName() == "track") + m_tracks += Track( n.toElement() ); +} + + +void +ScrobbleCache::write() +{ + if (m_tracks.isEmpty()) + { + QFile::remove( m_path ); + } + else { + QDomDocument xml; + QDomElement e = xml.createElement( "submissions" ); + e.setAttribute( "product", QCoreApplication::applicationName() ); + e.setAttribute( "version", "2" ); + + foreach (Track i, m_tracks) + e.appendChild( i.toDomElement( xml ) ); + + xml.appendChild( e ); + + QFile file( m_path ); + file.open( QIODevice::WriteOnly | QIODevice::Text ); + + QTextStream stream( &file ); + stream.setCodec( "UTF-8" ); + stream << "\n"; + stream << xml.toString( 2 ); + file.close(); + } +} + + +void +ScrobbleCache::add( const QList& tracks ) +{ + foreach (const Track& track, tracks) + { + ScrobbleCache::Invalidity invalidity; + + if ( !isValid( track, &invalidity ) ) + { + qWarning() << invalidity; + } + else if (track.isNull()) + qDebug() << "Will not cache an empty track"; + else + m_tracks += track; + } + + write(); +} + + +int +ScrobbleCache::remove( const QList& toremove ) +{ + QMutableListIterator i( m_tracks ); + while (i.hasNext()) { + Track t = i.next(); + for (int x = 0; x < toremove.count(); ++x) + if (toremove[x] == t) + i.remove(); + } + + write(); + + // yes we return # remaining, rather # removed, but this is an internal + // function and the behaviour is documented so it's alright imo --mxcl + return m_tracks.count(); +} diff --git a/thirdparty/liblastfm2/src/scrobble/ScrobbleCache.h b/thirdparty/liblastfm2/src/scrobble/ScrobbleCache.h new file mode 100644 index 000000000..31219263b --- /dev/null +++ b/thirdparty/liblastfm2/src/scrobble/ScrobbleCache.h @@ -0,0 +1,84 @@ +/* + Copyright 2009 Last.fm Ltd. + - Primarily authored by Max Howell, Jono Cole and Doug Mansell + + This file is part of liblastfm. + + liblastfm 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. + + liblastfm 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 liblastfm. If not, see . +*/ +#ifndef LASTFM_SCROBBLE_CACHE_H +#define LASTFM_SCROBBLE_CACHE_H + +#include "lastfm/Track" +#include +#include + +#if LASTFM_VERSION >= 0x00010000 +namespace lastfm { +#else +using lastfm::Track; +#endif + +/** absolutely not thread-safe */ +class LASTFM_DLLEXPORT ScrobbleCache +{ + QString m_username; + + void write(); /// writes m_tracks to m_path + +protected: + ScrobbleCache() + {} + + QString m_path; + QList m_tracks; + + void read( QDomDocument& xml ); /// reads from m_path into m_tracks + +public: + explicit ScrobbleCache( const QString& username ); + + /** note this is unique for Track::sameAs() and equal timestamps + * obviously playcounts will not be increased for the same timestamp */ + void add( const QList& ); + + /** returns the number of tracks left in the queue */ + int remove( const QList& ); + + QList tracks() const { return m_tracks; } + QString path() const { return m_path; } + QString username() const { return m_username; } + +private: + bool operator==( const ScrobbleCache& ); //undefined + + enum Invalidity + { + TooShort, + ArtistNameMissing, + TrackNameMissing, + ArtistInvalid, + NoTimestamp, + FromTheFuture, + FromTheDistantPast + }; + + bool isValid( const Track& track, Invalidity* = 0 ); +}; + +#if LASTFM_VERSION >= 0x00010000 +} +#endif + +#endif diff --git a/thirdparty/liblastfm2/src/scrobble/ScrobblePoint.h b/thirdparty/liblastfm2/src/scrobble/ScrobblePoint.h new file mode 100644 index 000000000..fce857d46 --- /dev/null +++ b/thirdparty/liblastfm2/src/scrobble/ScrobblePoint.h @@ -0,0 +1,59 @@ +/* + Copyright 2009 Last.fm Ltd. + - Primarily authored by Max Howell, Jono Cole and Doug Mansell + + This file is part of liblastfm. + + liblastfm 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. + + liblastfm 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 liblastfm. If not, see . +*/ +#ifndef LASTFM_SCROBBLE_POINT_H +#define LASTFM_SCROBBLE_POINT_H + +#include +#include + + +class LASTFM_DLLEXPORT ScrobblePoint +{ + uint i; + +public: + ScrobblePoint() : i( kScrobbleTimeMax ) + {} + + /** j is in seconds, and should be 50% the duration of a track */ + explicit ScrobblePoint( uint j ) + { + // we special case 0, returning kScrobbleTimeMax because we are + // cruel and callous people + if (j == 0) --j; + + i = qBound( uint(kScrobbleMinLength), + j, + uint(kScrobbleTimeMax) ); + } + operator uint() const { return i; } + + // scrobbles can occur between these two percentages of track duration + static const uint kScrobblePointMin = 50; + static const uint kScrobblePointMax = 100; + static const uint kDefaultScrobblePoint = 50; + + // Shortest track length allowed to scrobble in seconds + static const uint kScrobbleMinLength = 31; + // Upper limit for scrobble time in seconds + static const uint kScrobbleTimeMax = 240; +}; + +#endif diff --git a/thirdparty/liblastfm2/src/types/AbstractType.h b/thirdparty/liblastfm2/src/types/AbstractType.h new file mode 100755 index 000000000..873b5dec9 --- /dev/null +++ b/thirdparty/liblastfm2/src/types/AbstractType.h @@ -0,0 +1,43 @@ +/* + Copyright 2010 Last.fm Ltd. + - Primarily authored by Micahel Coffey and Jono Cole + + This file is part of liblastfm. + + liblastfm 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. + + liblastfm 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 liblastfm. If not, see . +*/ + +#ifndef ABSTRACTTYPE_H +#define ABSTRACTTYPE_H + +#include +#include +#include + +#include + +namespace lastfm +{ + class LASTFM_DLLEXPORT AbstractType + { + public: + virtual QString toString() const = 0; + virtual QDomElement toDomElement( QDomDocument& ) const = 0; + virtual QUrl www() const = 0; + virtual QUrl imageUrl( ImageSize size, bool square ) const = 0; + virtual ~AbstractType() {;} + }; +}; + +#endif // ABSTRACTTYPE_H diff --git a/thirdparty/liblastfm2/src/types/Album.cpp b/thirdparty/liblastfm2/src/types/Album.cpp new file mode 100644 index 000000000..fce542911 --- /dev/null +++ b/thirdparty/liblastfm2/src/types/Album.cpp @@ -0,0 +1,87 @@ +/* + Copyright 2009-2010 Last.fm Ltd. + - Primarily authored by Max Howell, Jono Cole and Doug Mansell + + This file is part of liblastfm. + + liblastfm 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. + + liblastfm 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 liblastfm. If not, see . +*/ +#include "Album.h" +#include "Artist.h" +#include "User.h" +#include "../core/UrlBuilder.h" +#include "../core/XmlQuery.h" +#include "../ws/ws.h" +#include +#include +#include + +QNetworkReply* +lastfm::Album::getInfo(const QString& user, const QString& sk) const +{ + QMap map; + map["method"] = "album.getInfo"; + map["artist"] = m_artist; + map["album"] = m_title; + if (!user.isEmpty()) map["username"] = user; + if (!sk.isEmpty()) map["sk"] = sk; + return lastfm::ws::get(map); +} + + +QNetworkReply* +lastfm::Album::getTags() const +{ + QMap map; + map["method"] = "album.getTags"; + map["artist"] = m_artist; + map["album"] = m_title; + return lastfm::ws::get(map); +} + + +QNetworkReply* +lastfm::Album::share( const QStringList& recipients, const QString& message, bool isPublic ) const +{ + QMap map; + map["method"] = "album.share"; + map["artist"] = m_artist; + map["album"] = m_title; + map["recipient"] = recipients.join(","); + map["public"] = isPublic ? "1" : "0"; + if (message.size()) map["message"] = message; + return lastfm::ws::post(map); +} + + +QUrl +lastfm::Album::www() const +{ + return lastfm::UrlBuilder( "music" ).slash( m_artist ).slash( m_title ).url(); +} + + +QNetworkReply* +lastfm::Album::addTags( const QStringList& tags ) const +{ + if (tags.isEmpty()) + return 0; + + QMap map; + map["method"] = "album.addTags"; + map["artist"] = m_artist; + map["album"] = m_title; + map["tags"] = tags.join( QChar(',') ); + return lastfm::ws::post(map); +} diff --git a/thirdparty/liblastfm2/src/types/Album.h b/thirdparty/liblastfm2/src/types/Album.h new file mode 100644 index 000000000..afbd52e05 --- /dev/null +++ b/thirdparty/liblastfm2/src/types/Album.h @@ -0,0 +1,73 @@ +/* + Copyright 2009 Last.fm Ltd. + - Primarily authored by Max Howell, Jono Cole and Doug Mansell + + This file is part of liblastfm. + + liblastfm 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. + + liblastfm 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 liblastfm. If not, see . +*/ +#ifndef LASTFM_ALBUM_H +#define LASTFM_ALBUM_H + +#include +#include +#include +#include + +namespace lastfm +{ + class LASTFM_DLLEXPORT Album + { + Mbid m_mbid; + Artist m_artist; + QString m_title; + + public: + Album() + {} + + explicit Album( Mbid mbid ) : m_mbid( mbid ) + {} + + Album( Artist artist, QString title ) : m_artist( artist ), m_title( title ) + {} + + bool operator==( const Album& that ) const { return m_title == that.m_title && m_artist == that.m_artist; } + bool operator!=( const Album& that ) const { return m_title != that.m_title || m_artist != that.m_artist; } + + operator QString() const { return title(); } + QString title() const { return m_title.isEmpty() ? "[unknown]" : m_title; } + Artist artist() const { return m_artist; } + Mbid mbid() const { return m_mbid; } + + /** artist may have been set, since we allow that in the ctor, but should we handle untitled albums? */ + bool isNull() const { return m_title.isEmpty() && m_mbid.isNull(); } + + /** Album.getInfo WebService */ + QNetworkReply* getInfo(const QString& user = "", const QString& sk = "") const; + QNetworkReply* share( const QStringList& recipients, const QString& message = "", bool isPublic = true ) const; + + /** use Tag::list to get the tag list out of the finished reply */ + QNetworkReply* getTags() const; + QNetworkReply* getTopTags() const; + + /** Last.fm dictates that you may submit at most 10 of these */ + QNetworkReply* addTags( const QStringList& ) const; + + /** the Last.fm website url for this album */ + QUrl www() const; + }; +} + +#endif //LASTFM_ALBUM_H diff --git a/thirdparty/liblastfm2/src/types/Artist.cpp b/thirdparty/liblastfm2/src/types/Artist.cpp new file mode 100644 index 000000000..242834e83 --- /dev/null +++ b/thirdparty/liblastfm2/src/types/Artist.cpp @@ -0,0 +1,202 @@ +/* + Copyright 2009 Last.fm Ltd. + - Primarily authored by Max Howell, Jono Cole and Doug Mansell + + This file is part of liblastfm. + + liblastfm 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. + + liblastfm 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 liblastfm. If not, see . +*/ +#include "Artist.h" +#include "User.h" +#include "../core/UrlBuilder.h" +#include "../core/XmlQuery.h" +#include "../ws/ws.h" +#include +#include + +using lastfm::Artist; +using lastfm::User; +using lastfm::ImageSize; +using lastfm::XmlQuery; + +QUrl +Artist::imageUrl( ImageSize size, bool square ) const +{ + if( !square ) return m_images.value( size ); + + QUrl url = m_images.value( size ); + QRegExp re( "/serve/(\\d*)s?/" ); + return QUrl( url.toString().replace( re, "/serve/\\1s/" )); +} + +static inline QList images( const lastfm::XmlQuery& e ) +{ + QList images; + images += e["image size=small"].text(); + images += e["image size=medium"].text(); + images += e["image size=large"].text(); + return images; +} + + +Artist::Artist( const XmlQuery& xml ) + :AbstractType() +{ + m_name = xml["name"].text(); + m_images = images( xml ); +} + + +QMap //private +Artist::params( const QString& method ) const +{ + QMap map; + map["method"] = "artist."+method; + map["artist"] = m_name; + return map; +} + + +QNetworkReply* +Artist::share( const QStringList& recipients, const QString& message, bool isPublic ) const +{ + QMap map = params("share"); + map["recipient"] = recipients.join(","); + map["public"] = isPublic ? "1" : "0"; + if (message.size()) map["message"] = message; + return lastfm::ws::post(map); +} + + +QUrl +Artist::www() const +{ + return UrlBuilder( "music" ).slash( Artist::name() ).url(); +} + +QNetworkReply* +Artist::getEvents(int limit) const +{ + QMap map = params("getEvents"); + if (limit) map["limit"] = QString::number(limit); + return ws::get( map ); +} + +QNetworkReply* +Artist::getInfo(const QString& user, const QString& sk) const +{ + QMap map = params("getInfo"); + if (!user.isEmpty()) map["username"] = user; + if (!sk.isEmpty()) map["sk"] = sk; + return ws::get( map ); +} + + +QNetworkReply* +Artist::getTags() const +{ + return ws::get( params("getTags") ); +} + +QNetworkReply* +Artist::getTopTags() const +{ + return ws::get( params("getTopTags") ); +} + + +QNetworkReply* +Artist::getSimilar() const +{ + return ws::get( params("getSimilar") ); +} + + +QNetworkReply* +Artist::search( int limit ) const +{ + QMap map = params("search"); + if (limit > 0) map["limit"] = QString::number(limit); + return ws::get(map); +} + + +QMap /* static */ +Artist::getSimilar( QNetworkReply* r ) +{ + QMap artists; + try + { + XmlQuery lfm = ws::parse(r); + foreach (XmlQuery e, lfm.children( "artist" )) + { + // convert floating percentage to int in range 0 to 10,000 + int const match = e["match"].text().toFloat() * 100; + artists.insertMulti( match, e["name"].text() ); + } + } + catch (ws::ParseError& e) + { + qWarning() << e.what(); + } + return artists; +} + + + +QList /* static */ +Artist::list( QNetworkReply* r ) +{ + QList artists; + try { + XmlQuery lfm = ws::parse(r); + foreach (XmlQuery xq, lfm.children( "artist" )) { + Artist artist( xq ); + artists += artist; + } + } + catch (ws::ParseError& e) + { + qWarning() << e.what(); + } + return artists; +} + + +Artist +Artist::getInfo( QNetworkReply* r ) +{ + try { + XmlQuery lfm = ws::parse(r); + Artist artist = lfm["artist"]["name"].text(); + artist.m_images = images( lfm["artist"] ); + return artist; + } + catch (ws::ParseError& e) + { + qWarning() << e.what(); + return Artist(); + } +} + + +QNetworkReply* +Artist::addTags( const QStringList& tags ) const +{ + if (tags.isEmpty()) + return 0; + QMap map = params("addTags"); + map["tags"] = tags.join( QChar(',') ); + return ws::post(map); +} diff --git a/thirdparty/liblastfm2/src/types/Artist.h b/thirdparty/liblastfm2/src/types/Artist.h new file mode 100644 index 000000000..2bfd6d007 --- /dev/null +++ b/thirdparty/liblastfm2/src/types/Artist.h @@ -0,0 +1,100 @@ +/* + Copyright 2009-2010 Last.fm Ltd. + - Primarily authored by Max Howell, Jono Cole, Doug Mansell and Michael Coffey + + This file is part of liblastfm. + + liblastfm 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. + + liblastfm 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 liblastfm. If not, see . +*/ +#ifndef LASTFM_ARTIST_H +#define LASTFM_ARTIST_H + +#include +#include +#include + +#include +#include + + +namespace lastfm +{ + class LASTFM_DLLEXPORT Artist : public AbstractType + { + private: + QString m_name; + QList m_images; + + public: + Artist() : AbstractType() + {} + + Artist( const QString& name ) : AbstractType(), m_name( name ) + {} + + Artist( const class XmlQuery& xml ); + + /** will be QUrl() unless you got this back from a getInfo or something call */ + QUrl imageUrl( ImageSize size = Large, bool square = false ) const; + + bool isNull() const { return m_name.isEmpty(); } + + /** the url for this artist's page at www.last.fm */ + QUrl www() const; + + bool operator==( const Artist& that ) const { return m_name == that.m_name; } + bool operator!=( const Artist& that ) const { return m_name != that.m_name; } + + operator QString() const + { + /** if no artist name is set, return the musicbrainz unknown identifier + * in case some part of the GUI tries to display it anyway. Note isNull + * returns false still. So you should have queried that! */ + return m_name.isEmpty() ? "[unknown]" : m_name; + } + + QString toString() const { return name(); } + QString name() const { return QString(*this); } + + QDomElement toDomElement( QDomDocument& ) const { return QDomElement(); } + + QNetworkReply* share( const QStringList& recipients, const QString& message = "", bool isPublic = true ) const; + + QNetworkReply* getEvents(int limit = 0) const; + QNetworkReply* getInfo(const QString& user = "", const QString& sk = "") const; + static Artist getInfo( QNetworkReply* ); + + QNetworkReply* getSimilar() const; + /** The match percentage is returned from last.fm as a 4 significant + * figure floating point value. So we multply it by 100 to make an + * integer in the range of 0 to 10,000. This is possible confusing + * for you, but I felt it best not to lose any precision, and floats + * aren't much fun. */ + static QMap getSimilar( QNetworkReply* ); + + /** use Tag::list to get the tag list out of the finished reply */ + QNetworkReply* getTags() const; + QNetworkReply* getTopTags() const; + + /** Last.fm dictates that you may submit at most 10 of these */ + QNetworkReply* addTags( const QStringList& ) const; + + QNetworkReply* search( int limit = -1 ) const; + static QList list( QNetworkReply* ); + + QMap params( const QString& method ) const; + }; +} + +#endif diff --git a/thirdparty/liblastfm2/src/types/FingerprintId.cpp b/thirdparty/liblastfm2/src/types/FingerprintId.cpp new file mode 100644 index 000000000..b201534a0 --- /dev/null +++ b/thirdparty/liblastfm2/src/types/FingerprintId.cpp @@ -0,0 +1,55 @@ +/* + Copyright 2009 Last.fm Ltd. + - Primarily authored by Max Howell, Jono Cole and Doug Mansell + + This file is part of liblastfm. + + liblastfm 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. + + liblastfm 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 liblastfm. If not, see . +*/ +#include "FingerprintId.h" +#include "../ws/ws.h" +#include +#include + + +QNetworkReply* +lastfm::FingerprintId::getSuggestions() const +{ + if (isNull()) return 0; + + QUrl const url( "http://ws.audioscrobbler.com/fingerprint/" + QString(*this) + ".xml" ); + QNetworkRequest const request( url ); + return lastfm::nam()->get( request ); +} + + +QMap //static +lastfm::FingerprintId::getSuggestions( QNetworkReply* reply ) +{ + QDomDocument xml; + xml.setContent( reply->readAll() ); + QDomNodeList nodes = xml.documentElement().elementsByTagName( "track" ); + + QMap tracks; + for (int x = 0; x < nodes.count(); ++x) + { + QDomElement const e = nodes.at(x).toElement(); + + MutableTrack t; + t.setTitle( e.firstChildElement( "title" ).text() ); + t.setArtist( e.firstChildElement( "artist" ).text() ); + tracks.insert( e.attribute( "confidence", "0.0" ).toFloat(), t ); + } + return tracks; +} diff --git a/thirdparty/liblastfm2/src/types/FingerprintId.h b/thirdparty/liblastfm2/src/types/FingerprintId.h new file mode 100644 index 000000000..5028c7736 --- /dev/null +++ b/thirdparty/liblastfm2/src/types/FingerprintId.h @@ -0,0 +1,62 @@ +/* + Copyright 2009 Last.fm Ltd. + - Primarily authored by Max Howell, Jono Cole and Doug Mansell + + This file is part of liblastfm. + + liblastfm 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. + + liblastfm 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 liblastfm. If not, see . +*/ +#ifndef LASTFM_FINGERPRINT_ID_H +#define LASTFM_FINGERPRINT_ID_H + +#include +#include + +namespace lastfm +{ + class LASTFM_DLLEXPORT FingerprintId + { + int id; + + public: + FingerprintId() : id( -1 ) + {} + + FingerprintId( uint i ) : id( i ) + {} + + bool isNull() const { return id == -1; } + + /** we query Last.fm for suggested metadata, how awesome is that? + * @returns null if isNull() */ + QNetworkReply* getSuggestions() const; + static QMap getSuggestions( QNetworkReply* ); + + /** -1 if you need to generate it */ + operator int() const { return id; } + /** isEmpty() if you need to generate it */ + operator QString() const { return id == -1 ? "" : QString::number( id ); } + }; +} + + +inline QDebug operator<<( QDebug d, lastfm::FingerprintId id) +{ + if (id.isNull()) + return d << "(null)"; + else + return d << int(id); +} + +#endif diff --git a/thirdparty/liblastfm2/src/types/Mbid.cpp b/thirdparty/liblastfm2/src/types/Mbid.cpp new file mode 100644 index 000000000..1ce1d90c5 --- /dev/null +++ b/thirdparty/liblastfm2/src/types/Mbid.cpp @@ -0,0 +1,36 @@ +/* + Copyright 2009 Last.fm Ltd. + - Primarily authored by Max Howell, Jono Cole and Doug Mansell + + This file is part of liblastfm. + + liblastfm 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. + + liblastfm 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 liblastfm. If not, see . +*/ +#include "Mbid.h" +#include "mbid_mp3.c" +#include + +namespace lastfm +{ + Mbid //static + Mbid::fromLocalFile( const QString& path ) + { + char out[MBID_BUFFER_SIZE]; + QByteArray const bytes = QFile::encodeName( path ); + int const r = getMP3_MBID( bytes.data(), out ); + Mbid mbid; + if (r == 0) mbid.id = QString::fromLatin1( out ); + return mbid; + } +} diff --git a/thirdparty/liblastfm2/src/types/Mbid.h b/thirdparty/liblastfm2/src/types/Mbid.h new file mode 100644 index 000000000..071d4d1fc --- /dev/null +++ b/thirdparty/liblastfm2/src/types/Mbid.h @@ -0,0 +1,45 @@ +/* + Copyright 2009 Last.fm Ltd. + - Primarily authored by Max Howell, Jono Cole and Doug Mansell + + This file is part of liblastfm. + + liblastfm 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. + + liblastfm 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 liblastfm. If not, see . +*/ +#ifndef LASTFM_MBID_H +#define LASTFM_MBID_H + +#include +#include + +namespace lastfm +{ + class LASTFM_DLLEXPORT Mbid + { + QString id; + + public: + explicit Mbid( const QString& p = "" ) : id( p ) + {} + + bool isNull() const { return id.isNull() || id.isEmpty(); } + operator QString() const { return id; } + + /** if this is not an mp3 file you will be wasting time, as it won't work + * but we will do what you say anyway because you are the boss */ + static Mbid fromLocalFile( const QString& path ); + }; +} + +#endif diff --git a/thirdparty/liblastfm2/src/types/Playlist.cpp b/thirdparty/liblastfm2/src/types/Playlist.cpp new file mode 100644 index 000000000..807da1c6a --- /dev/null +++ b/thirdparty/liblastfm2/src/types/Playlist.cpp @@ -0,0 +1,63 @@ +/* + Copyright 2009 Last.fm Ltd. + - Primarily authored by Max Howell, Jono Cole and Doug Mansell + + This file is part of liblastfm. + + liblastfm 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. + + liblastfm 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 liblastfm. If not, see . +*/ +#include "Playlist.h" +#include "Track.h" +#include "../ws/ws.h" + + +QNetworkReply* +lastfm::Playlist::addTrack( const Track& t ) const +{ + QMap map; + map["method"] = "playlist.addTrack"; + map["playlistID"] = m_id; + map["artist"] = t.artist(); + map["track"] = t.title(); + return lastfm::ws::post(map); +} + + +QNetworkReply* +lastfm::Playlist::fetch() const +{ + return fetch( QUrl("lastfm://playlist/" + QString::number( m_id )) ); +} + + +QNetworkReply* //static +lastfm::Playlist::fetch( const QUrl& url ) +{ + QMap map; + map["method"] = "playlist.fetch"; + map["playlistURL"] = url.toString(); + return lastfm::ws::get(map); +} + + +QNetworkReply* //static +lastfm::Playlist::create( const QString& title, const QString& description /*=""*/ ) +{ + QMap map; + map["method"] = "playlist.create"; + map["title"] = title; + if (description.size()) + map["description"] = description; + return lastfm::ws::post(map); +} diff --git a/thirdparty/liblastfm2/src/types/Playlist.h b/thirdparty/liblastfm2/src/types/Playlist.h new file mode 100644 index 000000000..a271e7d5b --- /dev/null +++ b/thirdparty/liblastfm2/src/types/Playlist.h @@ -0,0 +1,53 @@ +/* + Copyright 2009 Last.fm Ltd. + - Primarily authored by Max Howell, Jono Cole and Doug Mansell + + This file is part of liblastfm. + + liblastfm 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. + + liblastfm 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 liblastfm. If not, see . +*/ +#ifndef LASTFM_PLAYLIST_H +#define LASTFM_PLAYLIST_H + +#include +#include +#include +#include + +namespace lastfm +{ + class LASTFM_DLLEXPORT Playlist + { + int m_id; + + Playlist() : m_id( -1 ) + {} + + public: + Playlist( int id ) : m_id( id ) + {} + + int id() const { return m_id; } + + QNetworkReply* addTrack( const Track& ) const; + QNetworkReply* fetch() const; + + static QNetworkReply* create( const QString& title, const QString& description = "" ); + static QNetworkReply* fetch( const QUrl& url ); + + static Xspf fetch( QNetworkReply* ); + }; +} + +#endif diff --git a/thirdparty/liblastfm2/src/types/Tag.cpp b/thirdparty/liblastfm2/src/types/Tag.cpp new file mode 100644 index 000000000..40ddfb43b --- /dev/null +++ b/thirdparty/liblastfm2/src/types/Tag.cpp @@ -0,0 +1,77 @@ +/* + Copyright 2009 Last.fm Ltd. + - Primarily authored by Max Howell, Jono Cole and Doug Mansell + + This file is part of liblastfm. + + liblastfm 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. + + liblastfm 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 liblastfm. If not, see . +*/ +#include "Tag.h" +#include "User.h" +#include "../core/UrlBuilder.h" +#include "../core/XmlQuery.h" +#include "../ws/ws.h" +using lastfm::Tag; +using lastfm::User; + + +QUrl +Tag::www() const +{ + return UrlBuilder( "tag" ).slash( m_name ).url(); +} + + +QUrl +Tag::www( const User& user ) const +{ + return UrlBuilder( "user" ).slash( user.name() ).slash( "tags" ).slash( Tag::name() ).url(); +} + + +QNetworkReply* +Tag::search() const +{ + QMap map; + map["method"] = "tag.search"; + map["tag"] = m_name; + return ws::get(map); +} + +//static +QNetworkReply* +Tag::getTopTags() +{ + QMap map; + map["method"] = "tag.getTopTags"; + return ws::get(map); +} + +QMap //static +Tag::list( QNetworkReply* r ) +{ + QMap tags; + try { + foreach (XmlQuery xq, XmlQuery(ws::parse(r)).children("tag")) + // we toLower always as otherwise it is ugly mixed case, as first + // ever tag decides case, and Last.fm is case insensitive about it + // anyway + tags.insertMulti( xq["count"].text().toInt(), xq["name"].text().toLower() ); + } + catch (ws::ParseError& e) + { + qWarning() << e.what(); + } + return tags; +} diff --git a/thirdparty/liblastfm2/src/types/Tag.h b/thirdparty/liblastfm2/src/types/Tag.h new file mode 100644 index 000000000..bf4d5cfca --- /dev/null +++ b/thirdparty/liblastfm2/src/types/Tag.h @@ -0,0 +1,60 @@ +/* + Copyright 2009 Last.fm Ltd. + - Primarily authored by Max Howell, Jono Cole and Doug Mansell + + This file is part of liblastfm. + + liblastfm 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. + + liblastfm 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 liblastfm. If not, see . +*/ +#ifndef LASTFM_TAG_H +#define LASTFM_TAG_H + +#include +#include +#include +#include + +namespace lastfm +{ + class LASTFM_DLLEXPORT Tag + { + QString m_name; + + public: + Tag( const QString& name ) : m_name( name ) + {} + + operator QString() const { return m_name; } + QString name() const { return m_name; } + + /** the global tag page at www.last.fm */ + QUrl www() const; + /** the tag page for user @p user at www.last.fm */ + QUrl www( const class User& user ) const; + /** pass the finished QNetworkReply to Tag::list() */ + class QNetworkReply* search() const; + + /** the top global tags on Last.fm, sorted by popularity (number of times used) */ + static class QNetworkReply* getTopTags(); + + /** the integer is the weighting, not all list type return requests + * have a weighting, so the int may just be zero, if you don't care + * about the weight just do this: + * QStringList tags = Tag::list( reply ).values(); + */ + static QMap list( QNetworkReply* ); + }; +} + +#endif diff --git a/thirdparty/liblastfm2/src/types/Track.cpp b/thirdparty/liblastfm2/src/types/Track.cpp new file mode 100644 index 000000000..cececee1b --- /dev/null +++ b/thirdparty/liblastfm2/src/types/Track.cpp @@ -0,0 +1,481 @@ +/* + Copyright 2009 Last.fm Ltd. + - Primarily authored by Max Howell, Jono Cole and Doug Mansell + + This file is part of liblastfm. + + liblastfm 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. + + liblastfm 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 liblastfm. If not, see . +*/ +#include "Track.h" +#include "User.h" +#include "../core/UrlBuilder.h" +#include "../core/XmlQuery.h" +#include "../ws/ws.h" +#include +#include + + +lastfm::TrackData::TrackData() + : trackNumber( 0 ), + duration( 0 ), + source( Track::Unknown ), + rating( 0 ), + fpid( -1 ), + loved( false ), + null( false ), + scrobbleStatus( Track::Null ), + scrobbleError( Track::None ) +{} + +lastfm::Track::Track() + :AbstractType() +{ + d = new TrackData; + d->null = true; +} + +lastfm::Track::Track( const QDomElement& e ) + :AbstractType() +{ + d = new TrackData; + + if (e.isNull()) { d->null = true; return; } + + d->artist = e.namedItem( "artist" ).toElement().text(); + d->albumArtist = e.namedItem( "albumArtist" ).toElement().text(); + d->album = e.namedItem( "album" ).toElement().text(); + d->title = e.namedItem( "track" ).toElement().text(); + d->correctedArtist = e.namedItem( "correctedArtist" ).toElement().text(); + d->correctedAlbumArtist = e.namedItem( "correctedAlbumArtist" ).toElement().text(); + d->correctedAlbum = e.namedItem( "correctedAlbum" ).toElement().text(); + d->correctedTitle = e.namedItem( "correctedTrack" ).toElement().text(); + d->trackNumber = 0; + d->duration = e.namedItem( "duration" ).toElement().text().toInt(); + d->url = e.namedItem( "url" ).toElement().text(); + d->rating = e.namedItem( "rating" ).toElement().text().toUInt(); + d->source = e.namedItem( "source" ).toElement().text().toInt(); //defaults to 0, or lastfm::Track::Unknown + d->time = QDateTime::fromTime_t( e.namedItem( "timestamp" ).toElement().text().toUInt() ); + d->loved = e.namedItem( "loved" ).toElement().text().toInt(); + d->scrobbleStatus = e.namedItem( "scrobbleStatus" ).toElement().text().toInt(); + d->scrobbleError = e.namedItem( "scrobbleError" ).toElement().text().toInt(); + + for (QDomElement image(e.firstChildElement("image")) ; !image.isNull() ; image = e.nextSiblingElement("image")) + { + d->m_images[static_cast(image.attribute("size").toInt())] = image.text(); + } + + QDomNodeList nodes = e.namedItem( "extras" ).childNodes(); + for (int i = 0; i < nodes.count(); ++i) + { + QDomNode n = nodes.at(i); + QString key = n.nodeName(); + d->extras[key] = n.toElement().text(); + } +} + +void +lastfm::TrackData::onLoveFinished() +{ + XmlQuery lfm = static_cast(sender())->readAll(); + if ( lfm.attribute( "status" ) == "ok") + loved = true; + emit loveToggled( loved ); +} + + +void +lastfm::TrackData::onUnloveFinished() +{ + XmlQuery lfm = static_cast(sender())->readAll(); + if ( lfm.attribute( "status" ) == "ok") + loved = false; + emit loveToggled( loved ); +} + +void +lastfm::TrackData::onGotInfo() +{ + lastfm::XmlQuery lfm( static_cast(sender())->readAll() ); + + QString imageUrl = lfm["track"]["image size=small"].text(); + if ( !imageUrl.isEmpty() ) m_images[lastfm::Small] = imageUrl; + imageUrl = lfm["track"]["image size=medium"].text(); + if ( !imageUrl.isEmpty() ) m_images[lastfm::Medium] = imageUrl; + imageUrl = lfm["track"]["image size=large"].text(); + if ( !imageUrl.isEmpty() ) m_images[lastfm::Large] = imageUrl; + imageUrl = lfm["track"]["image size=extralarge"].text(); + if ( !imageUrl.isEmpty() ) m_images[lastfm::ExtraLarge] = imageUrl; + imageUrl = lfm["track"]["image size=mega"].text(); + if ( !imageUrl.isEmpty() ) m_images[lastfm::Mega] = imageUrl; + + loved = lfm["track"]["userloved"].text().toInt(); + + emit gotInfo( lfm ); + emit loveToggled( loved ); + + // you should connect everytime you call getInfo + disconnect( this, SIGNAL(gotInfo(const XmlQuery&)), 0, 0); +} + + +QDomElement +lastfm::Track::toDomElement( QDomDocument& xml ) const +{ + QDomElement item = xml.createElement( "track" ); + + #define makeElement( tagname, getter ) { \ + QString v = getter; \ + if (!v.isEmpty()) \ + { \ + QDomElement e = xml.createElement( tagname ); \ + e.appendChild( xml.createTextNode( v ) ); \ + item.appendChild( e ); \ + } \ + } + + makeElement( "artist", d->artist ); + makeElement( "albumArtist", d->albumArtist ); + makeElement( "album", d->album ); + makeElement( "track", d->title ); + makeElement( "correctedArtist", d->correctedArtist ); + makeElement( "correctedAlbumArtist", d->correctedAlbumArtist ); + makeElement( "correctedAlbum", d->correctedAlbum ); + makeElement( "correctedTrack", d->correctedTitle ); + makeElement( "duration", QString::number( d->duration ) ); + makeElement( "timestamp", QString::number( d->time.toTime_t() ) ); + makeElement( "url", d->url.toString() ); + makeElement( "source", QString::number( d->source ) ); + makeElement( "rating", QString::number(d->rating) ); + makeElement( "fpId", QString::number(d->fpid) ); + makeElement( "mbId", mbid() ); + makeElement( "loved", QString::number( isLoved() ) ); + makeElement( "scrobbleStatus", QString::number( scrobbleStatus() ) ); + makeElement( "scrobbleError", QString::number( scrobbleError() ) ); + + // put the images urls in the dom + QMapIterator imageIter( d->m_images ); + while (imageIter.hasNext()) { + QDomElement e = xml.createElement( "image" ); + e.appendChild( xml.createTextNode( imageIter.next().value().toString() ) ); + e.setAttribute( "size", imageIter.key() ); + item.appendChild( e ); + } + + // add the extras to the dom + QDomElement extras = xml.createElement( "extras" ); + QMapIterator extrasIter( d->extras ); + while (extrasIter.hasNext()) { + QDomElement e = xml.createElement( extrasIter.next().key() ); + e.appendChild( xml.createTextNode( extrasIter.value() ) ); + extras.appendChild( e ); + } + item.appendChild( extras ); + + return item; +} + + +bool +lastfm::Track::corrected() const +{ + // If any of the corrected string have been set and they are different + // from the initial strings then this track has been corrected. + return ( (!d->correctedTitle.isEmpty() && (d->correctedTitle != d->title)) + || (!d->correctedAlbum.isEmpty() && (d->correctedAlbum != d->album)) + || (!d->correctedArtist.isEmpty() && (d->correctedArtist != d->artist)) + || (!d->correctedAlbumArtist.isEmpty() && (d->correctedAlbumArtist != d->albumArtist))); +} + +lastfm::Artist +lastfm::Track::artist( Corrections corrected ) const +{ + if ( corrected == Corrected && !d->correctedArtist.isEmpty() ) + return Artist( d->correctedArtist ); + + return Artist( d->artist ); +} + +lastfm::Artist +lastfm::Track::albumArtist( Corrections corrected ) const +{ + if ( corrected == Corrected && !d->correctedAlbumArtist.isEmpty() ) + return Artist( d->correctedAlbumArtist ); + + return Artist( d->albumArtist ); +} + +lastfm::Album +lastfm::Track::album( Corrections corrected ) const +{ + if ( corrected == Corrected && !d->correctedAlbum.isEmpty() ) + return Album( artist( corrected ), d->correctedAlbum ); + + return Album( artist( corrected ), d->album ); +} + +QString +lastfm::Track::title( Corrections corrected ) const +{ + /** if no title is set, return the musicbrainz unknown identifier + * in case some part of the GUI tries to display it anyway. Note isNull + * returns false still. So you should have queried this! */ + + if ( corrected == Corrected && !d->correctedTitle.isEmpty() ) + return d->correctedTitle; + + return d->title.isEmpty() ? "[unknown]" : d->title; +} + + +QUrl +lastfm::Track::imageUrl( lastfm::ImageSize size, bool square ) const +{ + if( !square ) return d->m_images.value( size ); + + QUrl url = d->m_images.value( size ); + QRegExp re( "/serve/(\\d*)s?/" ); + return QUrl( url.toString().replace( re, "/serve/\\1s/" )); +} + + +QString +lastfm::Track::toString( const QChar& separator, Corrections corrections ) const +{ + if ( d->artist.isEmpty() ) + { + if ( d->title.isEmpty() ) + return QFileInfo( d->url.path() ).fileName(); + else + return title( corrections ); + } + + if ( d->title.isEmpty() ) + return artist( corrections ); + + return artist( corrections ) + ' ' + separator + ' ' + title( corrections ); +} + + +QString //static +lastfm::Track::durationString( int const duration ) +{ + QTime t = QTime().addSecs( duration ); + if (duration < 60*60) + return t.toString( "m:ss" ); + else + return t.toString( "hh:mm:ss" ); +} + + +QNetworkReply* +lastfm::Track::share( const QStringList& recipients, const QString& message, bool isPublic ) const +{ + QMap map = params("share"); + map["recipient"] = recipients.join(","); + map["public"] = isPublic ? "1" : "0"; + if (message.size()) map["message"] = message; + return ws::post(map); +} + + +void +lastfm::MutableTrack::setFromLfm( const XmlQuery& lfm ) +{ + QString imageUrl = lfm["track"]["image size=small"].text(); + if ( !imageUrl.isEmpty() ) d->m_images[lastfm::Small] = imageUrl; + imageUrl = lfm["track"]["image size=medium"].text(); + if ( !imageUrl.isEmpty() ) d->m_images[lastfm::Medium] = imageUrl; + imageUrl = lfm["track"]["image size=large"].text(); + if ( !imageUrl.isEmpty() ) d->m_images[lastfm::Large] = imageUrl; + imageUrl = lfm["track"]["image size=extralarge"].text(); + if ( !imageUrl.isEmpty() ) d->m_images[lastfm::ExtraLarge] = imageUrl; + imageUrl = lfm["track"]["image size=mega"].text(); + if ( !imageUrl.isEmpty() ) d->m_images[lastfm::Mega] = imageUrl; + + d->loved = lfm["track"]["userloved"].text().toInt(); + + d->forceLoveToggled( d->loved ); +} + + +void +lastfm::MutableTrack::love() +{ + QNetworkReply* reply = ws::post(params("love")); + QObject::connect( reply, SIGNAL(finished()), signalProxy(), SLOT(onLoveFinished())); +} + + +void +lastfm::MutableTrack::unlove() +{ + QNetworkReply* reply = ws::post(params("unlove")); + QObject::connect( reply, SIGNAL(finished()), signalProxy(), SLOT(onUnloveFinished())); +} + + +QNetworkReply* +lastfm::MutableTrack::ban() +{ + d->extras["rating"] = "B"; + return ws::post(params("ban")); +} + + +QMap +lastfm::Track::params( const QString& method, bool use_mbid ) const +{ + QMap map; + map["method"] = "Track."+method; + if (d->mbid.size() && use_mbid) + map["mbid"] = d->mbid; + else { + map["artist"] = d->artist; + map["track"] = d->title; + } + return map; +} + + +QNetworkReply* +lastfm::Track::getTopTags() const +{ + return ws::get( params("getTopTags", true) ); +} + + +QNetworkReply* +lastfm::Track::getTopFans() const +{ + return ws::get( params("getTopFans", true) ); +} + + +QNetworkReply* +lastfm::Track::getTags() const +{ + return ws::get( params("getTags", true) ); +} + +void +lastfm::Track::getInfo(const QString& user, const QString& sk) const +{ + QMap map = params("getInfo", true); + if (!user.isEmpty()) map["username"] = user; + if (!sk.isEmpty()) map["sk"] = sk; + QObject::connect( ws::get( map ), SIGNAL(finished()), d.data(), SLOT(onGotInfo())); +} + + +QNetworkReply* +lastfm::Track::addTags( const QStringList& tags ) const +{ + if (tags.isEmpty()) + return 0; + QMap map = params("addTags"); + map["tags"] = tags.join( QChar(',') ); + return ws::post(map); +} + + +QNetworkReply* +lastfm::Track::removeTag( const QString& tag ) const +{ + if (tag.isEmpty()) + return 0; + QMap map = params( "removeTag" ); + map["tags"] = tag; + return ws::post(map); +} + + +QNetworkReply* +lastfm::Track::updateNowPlaying() const +{ + QMap map = params("updateNowPlaying"); + map["duration"] = QString::number( duration() ); + if ( !album().isNull() ) map["album"] = album(); + map["context"] = extra("playerId"); + + qDebug() << map; + + return ws::post(map); +} + + +QNetworkReply* +lastfm::Track::scrobble() const +{ + QMap map = params("scrobble"); + map["duration"] = QString::number( d->duration ); + map["timestamp"] = QString::number( d->time.toTime_t() ); + map["context"] = extra("playerId"); + map["albumArtist"] = d->albumArtist; + if ( !d->album.isEmpty() ) map["album"] = d->album; + + qDebug() << map; + + return ws::post(map); +} + +QNetworkReply* +lastfm::Track::scrobble(const QList& tracks) +{ + QMap map; + map["method"] = "track.scrobble"; + + for ( int i(0) ; i < tracks.count() ; ++i ) + { + map["duration[" + QString::number(i) + "]"] = QString::number( tracks[i].duration() ); + map["timestamp[" + QString::number(i) + "]"] = QString::number( tracks[i].timestamp().toTime_t() ); + map["track[" + QString::number(i) + "]"] = tracks[i].title(); + map["context[" + QString::number(i) + "]"] = tracks[i].extra("playerId"); + if ( !tracks[i].album().isNull() ) map["album[" + QString::number(i) + "]"] = tracks[i].album(); + map["artist[" + QString::number(i) + "]"] = tracks[i].artist(); + map["albumArtist[" + QString::number(i) + "]"] = tracks[i].albumArtist(); + if ( !tracks[i].mbid().isNull() ) map["mbid[" + QString::number(i) + "]"] = tracks[i].mbid(); + } + + qDebug() << map; + + return ws::post(map); +} + + +QUrl +lastfm::Track::www() const +{ + return UrlBuilder( "music" ).slash( d->artist ).slash( album().isNull() ? QString("_") : album()).slash( d->title ).url(); +} + + +bool +lastfm::Track::isMp3() const +{ + //FIXME really we should check the file header? + return d->url.scheme() == "file" && + d->url.path().endsWith( ".mp3", Qt::CaseInsensitive ); +} + +void +lastfm::MutableTrack::setCorrections( QString title, QString album, QString artist, QString albumArtist ) +{ + d->correctedTitle = title; + d->correctedAlbum = album; + d->correctedArtist = artist; + d->correctedAlbumArtist = albumArtist; + + d->forceCorrected( toString() ); +} + diff --git a/thirdparty/liblastfm2/src/types/Track.h b/thirdparty/liblastfm2/src/types/Track.h new file mode 100644 index 000000000..eb759d0a2 --- /dev/null +++ b/thirdparty/liblastfm2/src/types/Track.h @@ -0,0 +1,323 @@ +/* + Copyright 2009-2010 Last.fm Ltd. + - Primarily authored by Max Howell, Jono Cole, Doug Mansell and Michael Coffey + + This file is part of liblastfm. + + liblastfm 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. + + liblastfm 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 liblastfm. If not, see . +*/ +#ifndef LASTFM_TRACK_H +#define LASTFM_TRACK_H + +#include +#include +#include +#include +#include +#include +#include +#include +#include + + +namespace lastfm { + +class TrackData : public QObject, public QSharedData +{ + Q_OBJECT + + friend class Track; + friend class MutableTrack; +public: + TrackData(); + +public: + QString artist; + QString albumArtist; + QString album; + QString title; + QString correctedArtist; + QString correctedAlbumArtist; + QString correctedAlbum; + QString correctedTitle; + uint trackNumber; + uint duration; + short source; + short rating; + QString mbid; /// musicbrainz id + uint fpid; + QUrl url; + QDateTime time; /// the time the track was started at + bool loved; + QMap m_images; + short scrobbleStatus; + short scrobbleError; + + //FIXME I hate this, but is used for radio trackauth etc. + QMap extras; + + bool null; + +private: + void forceLoveToggled( bool love ) { emit loveToggled( love );} + void forceScrobbleStatusChanged() { emit scrobbleStatusChanged(); } + void forceCorrected( QString correction ) { emit corrected( correction ); } + +private slots: + void onLoveFinished(); + void onUnloveFinished(); + void onGotInfo(); + +signals: + void loveToggled( bool love ); + void loveFinished(); + void unlovedFinished(); + void gotInfo( const XmlQuery& ); + void scrobbleStatusChanged(); + void corrected( QString correction ); +}; + + + +/** Our track type. It's quite good, you may want to use it as your track type + * in general. It is explicitly shared. Which means when you make a copy, they + * both point to the same data still. This is like Qt's implicitly shared + * classes, eg. QString, however if you mod a copy of a QString, the copy + * detaches first, so then you have two copies. Our Track object doesn't + * detach, which is very handy for our usage in the client, but perhaps not + * what you want. If you need a deep copy for eg. work in a thread, call + * clone(). */ +class LASTFM_DLLEXPORT Track : public AbstractType +{ +public: + friend class TrackSignalProxy; + + enum Source + { + // DO NOT UNDER ANY CIRCUMSTANCES CHANGE THE ORDER OR VALUES OF THIS ENUM! + // you will cause broken settings and b0rked scrobbler cache submissions + + Unknown = 0, + LastFmRadio, + Player, + MediaDevice, + NonPersonalisedBroadcast, // eg Shoutcast, BBC Radio 1, etc. + PersonalisedRecommendation, // eg Pandora, but not Last.fm + }; + + enum ScrobbleStatus + { + Null = 0, + Cached, + Submitted, + Error + }; + + enum Corrections + { + Original = 0, + Corrected + }; + + enum ScrobbleError + { + None = 0, + FilteredArtistName = 113, + FilteredTrackName = 114, + FilteredAlbumName = 115, + FilteredTimestamp = 116, + ExceededMaxDailyScrobbles = 118, + InvalidStreamAuth = 119 + }; + + Track(); + explicit Track( const QDomElement& ); + + /** this track and that track point to the same object, so they are the same + * in fact. This doesn't do a deep data comparison. So even if all the + * fields are the same it will return false if they aren't in fact spawned + * from the same initial Track object */ + bool sameObject( const Track& that ) + { + return (this->d == that.d); + } + + bool operator==( const Track& that ) const + { + return ( this->title() == that.title() && + this->album() == that.album() && + this->artist() == that.artist()); + } + bool operator!=( const Track& that ) const + { + return !operator==( that ); + } + + QObject* signalProxy() const { return d.data(); } + + /** only a Track() is null */ + bool isNull() const { return d->null; } + + bool corrected() const; + + Artist artist( Corrections corrected = Original ) const; + Artist albumArtist( Corrections corrected = Original ) const; + Album album( Corrections corrected = Original ) const; + QString title( Corrections corrected = Original ) const; + + uint trackNumber() const { return d->trackNumber; } + uint duration() const { return d->duration; } /// in seconds + Mbid mbid() const { return Mbid(d->mbid); } + QUrl url() const { return d->url; } + QDateTime timestamp() const { return d->time; } + Source source() const { return static_cast(d->source); } + uint fingerprintId() const { return d->fpid; } + bool isLoved() const { return d->loved; } + QUrl imageUrl( lastfm::ImageSize size, bool square ) const; + + QString durationString() const { return durationString( d->duration ); } + static QString durationString( int seconds ); + + ScrobbleStatus scrobbleStatus() const { return static_cast(d->scrobbleStatus); } + ScrobbleError scrobbleError() const { return static_cast(d->scrobbleError); } + + /** default separator is an en-dash */ + QString toString() const { return toString( Corrected ); } + QString toString( Corrections corrections ) const { return toString( QChar(8211), corrections );} + QString toString( const QChar& separator, Corrections corrections = Original ) const; + /** the standard representation of this object as an XML node */ + QDomElement toDomElement( class QDomDocument& ) const; + + QString extra( const QString& key ) const{ return d->extras[ key ]; } + + bool operator<( const Track &that ) const + { + return this->d->time < that.d->time; + } + + bool isMp3() const; + + operator QVariant() const { return QVariant::fromValue( *this ); } + +//////////// lastfm::Ws + + /** See last.fm/api Track section */ + QNetworkReply* share( const QStringList& recipients, const QString& message = "", bool isPublic = true ) const; + + /** you can get any QNetworkReply TagList using Tag::list( QNetworkReply* ) */ + QNetworkReply* getTags() const; // for the logged in user + QNetworkReply* getTopTags() const; + QNetworkReply* getTopFans() const; + void getInfo(const QString& user = "", const QString& sk = "") const; + + /** you can only add 10 tags, we submit everything you give us, but the + * docs state 10 only. Will return 0 if the list is empty. */ + QNetworkReply* addTags( const QStringList& ) const; + /** will return 0 if the string is "" */ + QNetworkReply* removeTag( const QString& ) const; + + /** scrobble the track */ + QNetworkReply* updateNowPlaying() const; + QNetworkReply* scrobble() const; + static QNetworkReply* scrobble(const QList& tracks); + + /** the url for this track's page at last.fm */ + QUrl www() const; + +protected: + QExplicitlySharedDataPointer d; + QMap params( const QString& method, bool use_mbid = false ) const; + +private: + Track( TrackData* that_d ) : d( that_d ) + {} +}; + + + +/** This class allows you to change Track objects, it is easy to use: + * MutableTrack( some_track_object ).setTitle( "Arse" ); + * + * We have a separate MutableTrack class because in our usage, tracks + * only get mutated once, and then after that, very rarely. This pattern + * encourages such usage, which is generally sensible. You can feel more + * comfortable that the data hasn't accidently changed behind your back. + */ +class LASTFM_DLLEXPORT MutableTrack : public Track +{ +public: + MutableTrack() + { + d->null = false; + } + + /** NOTE that passing a Track() to this ctor will automatically make it non + * null. Which may not be what you want. So be careful + * Rationale: this is the most maintainable way to do it + */ + MutableTrack( const Track& that ) : Track( that ) + { + d->null = false; + } + + void setFromLfm( const XmlQuery& lfm ); + + void setArtist( QString artist ) { d->artist = artist.trimmed(); } + void setAlbumArtist( QString albumArtist ) { d->albumArtist = albumArtist.trimmed(); } + void setAlbum( QString album ) { d->album = album.trimmed(); } + void setTitle( QString title ) { d->title = title.trimmed(); } + void setCorrections( QString title, QString album, QString artist, QString albumArtist ); + void setTrackNumber( uint n ) { d->trackNumber = n; } + void setDuration( uint duration ) { d->duration = duration; } + void setUrl( QUrl url ) { d->url = url; } + void setSource( Source s ) { d->source = s; } + void setLoved( bool loved ) { d->loved = loved; } + + void setMbid( Mbid id ) { d->mbid = id; } + void setFingerprintId( uint id ) { d->fpid = id; } + + void setScrobbleStatus( ScrobbleStatus scrobbleStatus ) + { + d->scrobbleStatus = scrobbleStatus; + d->forceScrobbleStatusChanged(); + } + void setScrobbleError( ScrobbleError scrobbleError ) { d->scrobbleError = scrobbleError; } + + /** you also must scrobble this track for the love to become permenant */ + void love(); + void unlove(); + QNetworkReply* ban(); + + void stamp() { d->time = QDateTime::currentDateTime(); } + + void setExtra( const QString& key, const QString& value ) { d->extras[key] = value; } + void removeExtra( QString key ) { d->extras.remove( key ); } + void setTimeStamp( const QDateTime& dt ) { d->time = dt; } +}; + + +} //namespace lastfm + + +inline QDebug operator<<( QDebug d, const lastfm::Track& t ) +{ + return !t.isNull() + ? d << t.toString( '-' ) << t.url() + : d << "Null Track object"; +} + + +Q_DECLARE_METATYPE( lastfm::Track ); + +#endif //LASTFM_TRACK_H diff --git a/thirdparty/liblastfm2/src/types/User.cpp b/thirdparty/liblastfm2/src/types/User.cpp new file mode 100644 index 000000000..03e1a1eda --- /dev/null +++ b/thirdparty/liblastfm2/src/types/User.cpp @@ -0,0 +1,286 @@ +/* + Copyright 2009 Last.fm Ltd. + - Primarily authored by Max Howell, Jono Cole and Doug Mansell + + This file is part of liblastfm. + + liblastfm 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. + + liblastfm 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 liblastfm. If not, see . +*/ +#include "User.h" +#include "Track.h" +#include "../core/UrlBuilder.h" +#include "../core/XmlQuery.h" +#include +#include + +using lastfm::User; +using lastfm::UserList; +using lastfm::UserDetails; +using lastfm::XmlQuery; +using lastfm::ImageSize; + +User::User( const XmlQuery& xml ) + :AbstractType(), m_match( -1.0f ) +{ + m_name = xml["name"].text(); + m_images << xml["image size=small"].text() + << xml["image size=medium"].text() + << xml["image size=large"].text(); + m_realName = xml["realname"].text(); +} + + +QUrl +User::imageUrl( ImageSize size, bool square ) const +{ + if( !square ) return m_images.value( size ); + + QUrl url = m_images.value( size ); + QRegExp re( "/serve/(\\d*)s?/" ); + return QUrl( url.toString().replace( re, "/serve/\\1s/" )); +} + + +QMap +User::params(const QString& method) const +{ + QMap map; + map["method"] = "user."+method; + map["user"] = m_name; + return map; +} + + +QNetworkReply* +User::getFriends( int perPage, int page ) const +{ + QMap map = params( "getFriends" ); + map["limit"] = QString::number(perPage); + map["page"] = QString::number(page); + return ws::get( map ); +} + + +QNetworkReply* +User::getTopTags() const +{ + return ws::get( params( "getTopTags" ) ); +} + + +QNetworkReply* +User::getTopArtists() const +{ + return ws::get( params( "getTopArtists" ) ); +} + + +QNetworkReply* +User::getRecentArtists() const +{ + return ws::get( params( "getRecentArtists" ) ); +} + + +QNetworkReply* +User::getRecentTracks() const +{ + return ws::get( params( "getRecentTracks" ) ); +} + +QNetworkReply* +User::getRecentStations() const +{ + return ws::post( params( "getRecentStations" ) ); +} + +QNetworkReply* +User::getNeighbours() const +{ + return ws::get( params( "getNeighbours" ) ); +} + + +QNetworkReply* +User::getPlaylists() const +{ + return ws::get( params( "getPlaylists" ) ); +} + + +UserList //static +User::list( QNetworkReply* r ) +{ + UserList users; + try { + XmlQuery lfm = ws::parse(r); + foreach (XmlQuery e, lfm.children( "user" )) + { + User u( e ); + users += u; + } + + users.total = lfm["friends"].attribute("total").toInt(); + users.page = lfm["friends"].attribute("page").toInt(); + users.perPage = lfm["friends"].attribute("perPage").toInt(); + users.totalPages = lfm["friends"].attribute("totalPages").toInt(); + } + catch (ws::ParseError& e) + { + qWarning() << e.what(); + } + return users; +} + + +QNetworkReply* //static +UserDetails::getInfo( const QString& username ) +{ + QMap map; + map["method"] = "user.getInfo"; + map["user"] = username; + return ws::post( map ); +} + + + + +/* +QNetworkReply* //static +UserDetails::getRecommendedArtists() +{ + QMap map; + map["method"] = "user.getRecommendedArtists"; + return ws::post( map ); +} +*/ + +QUrl +User::www() const +{ + return UrlBuilder( "user" ).slash( m_name ).url(); +} + +UserDetails::UserDetails() + : User() + , m_age( 0 ) + , m_scrobbles( 0 ) + , m_registered( QDateTime() ) + , m_isSubscriber( false ) + , m_canBootstrap( false ) +{} + +UserDetails::UserDetails( QNetworkReply* reply ) +{ + try + { + XmlQuery user = XmlQuery(ws::parse(reply))["user"]; + m_age = user["age"].text().toUInt(); + m_scrobbles = user["playcount"].text().toUInt(); + m_registered = QDateTime::fromTime_t(user["registered"].attribute("unixtime").toUInt()); + m_country = user["country"].text(); + m_isSubscriber = ( user["subscriber"].text() == "1" ); + m_canBootstrap = ( user["bootstrap"].text() == "1" ); + m_gender = user["gender"].text(); + m_realName = user["realname"].text(); + m_name = user["name"].text(); + m_images << user["image size=small"].text() + << user["image size=medium"].text() + << user["image size=large"].text(); + } + catch (ws::ParseError& e) + { + qWarning() << e.what(); + } +} + + +QString +UserDetails::getInfoString() const +{ + #define tr QObject::tr +; + + QString text; + if (m_gender.known() && m_age > 0 && m_scrobbles > 0) + { + text = tr("A %1, %2 years of age with %L3 scrobbles") + .arg( m_gender.toString() ) + .arg( m_age ) + .arg( m_scrobbles ); + } + else if (m_scrobbles > 0) + { + text = tr("%L1 scrobbles").arg( m_scrobbles ); + } + + return text; + + #undef tr +} + +void +UserDetails::setScrobbleCount( quint32 scrobbleCount ) +{ + m_scrobbles = scrobbleCount; +} + + +void +UserDetails::setDateRegistered( const QDateTime& date ) +{ + m_registered = date; +} + +void +UserDetails::setImages( const QList& images ) +{ + m_images = images; +} + +void +UserDetails::setRealName( const QString& realName ) +{ + m_realName = realName; +} +void +UserDetails::setAge( unsigned short age ) +{ + m_age = age; +} + +void +UserDetails::setIsSubscriber( bool subscriber ) +{ + m_isSubscriber = subscriber; +} + +void +UserDetails::setCanBootstrap( bool canBootstrap ) +{ + m_canBootstrap = canBootstrap; +} + +void +UserDetails::setGender( const QString& s ) +{ + m_gender = Gender( s ); +} + +void +UserDetails::setCountry( const QString& country ) +{ + m_country = country; +} + diff --git a/thirdparty/liblastfm2/src/types/User.h b/thirdparty/liblastfm2/src/types/User.h new file mode 100644 index 000000000..2d3eb0d84 --- /dev/null +++ b/thirdparty/liblastfm2/src/types/User.h @@ -0,0 +1,181 @@ +/* + Copyright 2009-2010 Last.fm Ltd. + - Primarily authored by Max Howell, Jono Cole, Doug Mansell and Michael Coffey + + This file is part of liblastfm. + + liblastfm 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. + + liblastfm 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 liblastfm. If not, see . +*/ +#ifndef LASTFM_USER_H +#define LASTFM_USER_H + +#include +#include +#include + +#include +#include + +namespace lastfm +{ + class UserList; + + class LASTFM_DLLEXPORT User : public AbstractType + { + public: + User() : AbstractType(), m_name( lastfm::ws::Username ), m_match( -1.0f ) + {} + + User( const QString& name ) : AbstractType(), m_name( name ), m_match( -1.0f ) + {} + + User( const class XmlQuery& xml ); + + bool operator==(const lastfm::User& that) const { return m_name == that.m_name; } + + operator QString() const { return m_name; } + QString name() const { return m_name; } + void setName( const QString& name ){ m_name = name; } + + /** use Tag::list() on the response to get a WeightedStringList */ + QNetworkReply* getTopTags() const; + + /** use User::list() on the response to get a QList */ + QNetworkReply* getFriends(int perPage = 50, int page = 1) const; + QNetworkReply* getNeighbours() const; + + QNetworkReply* getPlaylists() const; + QNetworkReply* getTopArtists() const; + QNetworkReply* getRecentTracks() const; + QNetworkReply* getRecentArtists() const; + QNetworkReply* getRecentStations() const; + + static UserList list( QNetworkReply* ); + + QString toString() const { return name(); } + QDomElement toDomElement( QDomDocument& ) const { return QDomElement(); } + + ////// + QUrl imageUrl( ImageSize size = Large, bool square = false ) const; + + QString realName() const { return m_realName; } + + /** the user's profile page at www.last.fm */ + QUrl www() const; + + /** Returns the match between the logged in user and the user which this + * object represents (if < 0.0f then not set) */ + float match() const { return m_match; } + + protected: + QString m_name; + + QList m_images; + + float m_match; + + QString m_realName; + + QMap params( const QString& method ) const; + }; + + + /** The Extended User contains extra information about a user's account */ + class LASTFM_DLLEXPORT UserDetails : public User + { + public: + UserDetails(); + /** User details */ + UserDetails( QNetworkReply* ); + + /** you can only get information about the any user */ + static QNetworkReply* getInfo( const QString& username = lastfm::ws::Username ); + + /** a verbose string, eg. "A man with 36,153 scrobbles" */ + QString getInfoString() const; + + bool isSubscriber() const{ return m_isSubscriber; } + bool canBootstrap() const{ return m_canBootstrap; } + quint32 scrobbleCount() const{ return m_scrobbles; } + QDateTime dateRegistered() const { return m_registered; } + + void setScrobbleCount( quint32 scrobblesCount ); + void setDateRegistered( const QDateTime& date ); + void setImages( const QList& images ); + void setRealName( const QString& realName ); + void setAge( unsigned short age ); + void setIsSubscriber( bool subscriber ); + void setCanBootstrap( bool canBootstrap ); + void setGender( const QString& s ); + void setCountry( const QString& country ); + + + // pass the result to Artist::list(), if you want the other data + // you have to parse the lfm() yourself members + // http://www.last.fm/api/show?service=388 + // static QNetworkReply* getRecommendedArtists(); + + protected: + + class Gender + { + QString s; + + public: + Gender() :s(/*confused!*/){} + + Gender( const QString& ss ) :s( ss.toLower() ) + {} + + bool known() const { return male() || female(); } + bool male() const { return s == "m"; } + bool female() const { return s == "f"; } + + QString toString() const + { + #define tr QObject::tr + QStringList list; + if (male()) + list << tr("boy") << tr("lad") << tr("chap") << tr("guy"); + else if (female()) + // I'm not sexist, it's just I'm gutless and couldn't think + // of any other non offensive terms for women! + list << tr("girl") << tr("lady") << tr("lass"); + else + return tr("person"); + + return list.value( QDateTime::currentDateTime().toTime_t() % list.count() ); + #undef tr + } + } m_gender; + + unsigned short m_age; + unsigned int m_scrobbles; + QDateTime m_registered; + QString m_country; + bool m_isSubscriber; + bool m_canBootstrap; + }; + + class LASTFM_DLLEXPORT UserList : public QList + { + public: + int total; + int page; + int perPage; + int totalPages; + }; +} + +#endif diff --git a/thirdparty/liblastfm2/src/types/Xspf.cpp b/thirdparty/liblastfm2/src/types/Xspf.cpp new file mode 100644 index 000000000..2100ec265 --- /dev/null +++ b/thirdparty/liblastfm2/src/types/Xspf.cpp @@ -0,0 +1,49 @@ +/* + Copyright 2009 Last.fm Ltd. + - Primarily authored by Max Howell, Jono Cole and Doug Mansell + + This file is part of liblastfm. + + liblastfm 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. + + liblastfm 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 liblastfm. If not, see . +*/ +#include "Xspf.h" +#include "../core/XmlQuery.h" +#include + +lastfm::Xspf::Xspf( const QDomElement& playlist_node ) +{ + XmlQuery e( playlist_node ); + + m_title = e["title"].text(); + + //FIXME should we use UnicornUtils::urlDecode()? + //The title is url encoded, has + instead of space characters + //and has a + at the begining. So it needs cleaning up: + m_title.replace( '+', ' ' ); + m_title = QUrl::fromPercentEncoding( m_title.toAscii()); + m_title = m_title.trimmed(); + + foreach (XmlQuery e, e["trackList"].children( "track" )) + { + MutableTrack t; + t.setUrl( e["location"].text() ); + t.setExtra( "trackauth", e["extension"]["trackauth"].text() ); + t.setTitle( e["title"].text() ); + t.setArtist( e["creator"].text() ); + t.setAlbum( e["album"].text() ); + t.setDuration( e["duration"].text().toInt() / 1000 ); + t.setLoved( e["extension"]["loved"].text() == "1" ); + m_tracks += t; // outside try block since location is enough basically + } +} diff --git a/thirdparty/liblastfm2/src/types/Xspf.h b/thirdparty/liblastfm2/src/types/Xspf.h new file mode 100644 index 000000000..e8bb55a15 --- /dev/null +++ b/thirdparty/liblastfm2/src/types/Xspf.h @@ -0,0 +1,43 @@ +/* + Copyright 2009 Last.fm Ltd. + - Primarily authored by Max Howell, Jono Cole and Doug Mansell + + This file is part of liblastfm. + + liblastfm 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. + + liblastfm 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 liblastfm. If not, see . +*/ +#ifndef LASTFM_XSPF_H +#define LASTFM_XSPF_H + +#include +#include + +namespace lastfm +{ + class LASTFM_DLLEXPORT Xspf + { + public: + /** pass in the playlist node! */ + Xspf( const class QDomElement& playlist_node ); + + QList tracks() const { return m_tracks; } + QString title() const{ return m_title; } + + private: + QList m_tracks; + QString m_title; + }; +} + +#endif diff --git a/thirdparty/liblastfm2/src/types/mbid_mp3.c b/thirdparty/liblastfm2/src/types/mbid_mp3.c new file mode 100644 index 000000000..ab494a32f --- /dev/null +++ b/thirdparty/liblastfm2/src/types/mbid_mp3.c @@ -0,0 +1,181 @@ +/* +* LICENSE +* +* Copyright (c) 2006, David Nicolson +* All rights reserved. +* +* Redistribution and use in source and binary forms, with or without +* modification, are permitted provided that the following conditions +* are met: +* +* 1. Redistributions of source code must retain the above copyright +* notice, this list of conditions and the following disclaimer. +* 2. Redistributions in binary form must reproduce the above copyright +* notice, this list of conditions and the following disclaimer in the +* documentation and/or other materials provided with the distribution. +* 3. Neither the name of the author nor the names of its contributors +* may be used to endorse or promote products derived from this software +* without specific prior written permission. +* +* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS +* "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT +* LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR +* A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT UNLESS REQUIRED BY +* LAW OR AGREED TO IN WRITING WILL ANY COPYRIGHT HOLDER OR CONTRIBUTOR +* BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, +* OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT +* OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, PROFITS; OR +* BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, +* WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE +* OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, +* EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. +*/ + +#ifndef __MBID_MP3_H +#define __MBID_MP3_H + +#define MBID_BUFFER_SIZE 37 + +// ----------------------------------------------------------------------------- + +void mfile(size_t length, char ret[], FILE *fp, int *s) { + size_t bytes = fread(ret,1,length,fp); + + if (bytes != length) { + *s = 0; + } +} + +// ----------------------------------------------------------------------------- + +int to_synch_safe(char bytes[]) { + return ((int)bytes[0] << 21) + ((int)bytes[1] << 14) + ((int)bytes[2] << 7) + (int)bytes[3]; +} + +int to_integer(char bytes[]) { + size_t size = 0; + uint i; + for (i=0; i < sizeof(bytes); i++) { + size = size * 256 + ((int)bytes[i] & 0x000000FF); + } + return static_cast(size); +} + +// ----------------------------------------------------------------------------- + +int getMP3_MBID(const char *path, char mbid[MBID_BUFFER_SIZE]) +{ + FILE *fp; + static int s = 1; + char head[3]; + char version[2]; + char flag[1]; + char size[4]; + char size_extended[4]; + int tag_size = 0; + int extended_size = 0; + char frame[4]; + char frame_header[4]; + int frame_size; + int version_major, version_minor; + + if (path == NULL) { + //debug("Received null path\n"); + return -1; + } + + fp = fopen(path,"rb"); + if (fp == NULL) { + //debug("Failed to open music file: %s\n",path); + return -1; + } + + while (s) { + mfile(3,head,fp,&s); + if (!strncmp(head,"ID3",3) == 0) { + //debug("No ID3v2 tag found: %s\n",path); + break; + } + + mfile(2,version,fp,&s); + version_major = (int)version[0]; + version_minor = (int)version[1]; + if (version_major == 2) { + //debug("ID3v2.2.0 does not support MBIDs: %s\n",path); + break; + } + if (version_major != 3 && version_major != 4) { + //debug("Unsupported ID3 version: v2.%d.%d\n",version_major,version_minor); + break; + } + + mfile(1,flag,fp,&s); + if ((unsigned int)flag[0] & 0x00000040) { + //debug("Extended header found\n"); + if (version[0] == 4) { + mfile(4,size_extended,fp,&s); + extended_size = to_synch_safe(size_extended); + } else { + mfile(4,size_extended,fp,&s); + extended_size = to_integer(size_extended); + } + //debug("Extended header size: %d\n",extended_size); + fseek(fp,extended_size,SEEK_CUR); + } + + mfile(4,size,fp,&s); + tag_size = to_synch_safe(size); + //debug("Tag size: %d\n",tag_size); + + while (s) { + if (ftell(fp) > tag_size || ftell(fp) > 1048576) { + break; + } + + mfile(4,frame,fp,&s); + if (frame[0] == 0x00) { + break; + } + if (version_major == 4) { + mfile(4,frame_header,fp,&s); + frame_size = to_synch_safe(frame_header); + } else { + mfile(4,frame_header,fp,&s); + frame_size = to_integer(frame_header); + } + + fseek(fp,2,SEEK_CUR); + //debug("Reading %d bytes from frame %s\n",frame_size,frame); + + if (strncmp(frame,"UFID",4) == 0) { + //char frame_data[frame_size]; + char frame_data[59]; + mfile(59,frame_data,fp,&s); + if (frame_size >= 59 && strncmp(frame_data,"http://musicbrainz.org",22) == 0) { + char *tmbid = frame_data; + tmbid = frame_data + 23; + strncpy(mbid,tmbid,MBID_BUFFER_SIZE-1); + mbid[MBID_BUFFER_SIZE-1] = 0x00; + fclose(fp); + return 0; + } + } else { + fseek(fp,frame_size,SEEK_CUR); + } + } + break; + } + + if (fp) { + fclose(fp); + } + //if (!s) { + // debug("Failed to read music file: %s\n",path); + //} + return -1; + +} + +#endif + +// ----------------------------------------------------------------------------- diff --git a/thirdparty/liblastfm2/src/ws/InternetConnectionMonitor.cpp b/thirdparty/liblastfm2/src/ws/InternetConnectionMonitor.cpp new file mode 100644 index 000000000..44eb51b4d --- /dev/null +++ b/thirdparty/liblastfm2/src/ws/InternetConnectionMonitor.cpp @@ -0,0 +1,113 @@ +/* + Copyright 2009 Last.fm Ltd. + - Primarily authored by Max Howell, Jono Cole and Doug Mansell + + This file is part of liblastfm. + + liblastfm 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. + + liblastfm 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 liblastfm. If not, see . +*/ + +#include "InternetConnectionMonitor.h" +#include "linux/LNetworkConnectionMonitor.h" +#include "mac/MNetworkConnectionMonitor.h" +#include "win/WNetworkConnectionMonitor.h" +#include "NetworkConnectionMonitor.h" +#include "ws.h" + +lastfm::InternetConnectionMonitor::InternetConnectionMonitor( QObject *parent ) + : QObject( parent ) + , m_up( true ) +{ + m_networkMonitor = createNetworkConnectionMonitor(); + + if ( m_networkMonitor ) + { + connect( m_networkMonitor, SIGNAL( networkUp() ), this, SLOT( onNetworkUp() ) ); + connect( m_networkMonitor, SIGNAL( networkDown() ), this, SLOT( onNetworkDown() ) ); + } + + connect( lastfm::nam(), SIGNAL( finished( QNetworkReply* ) ), this, SLOT( onFinished( QNetworkReply* ) ) ); +} + +void +lastfm::InternetConnectionMonitor::onFinished( QNetworkReply* reply ) +{ + switch( reply->error() ) + { + case QNetworkReply::NoError: + if ( !m_up ) + { + m_up = true; + emit up(); + emit connectivityChanged( m_up ); + } + break; + case QNetworkReply::HostNotFoundError: + case QNetworkReply::TimeoutError: + case QNetworkReply::ProxyConnectionRefusedError: + case QNetworkReply::ProxyConnectionClosedError: + case QNetworkReply::ProxyNotFoundError: + case QNetworkReply::ProxyTimeoutError: + case QNetworkReply::ProxyAuthenticationRequiredError: + if ( m_up ) + { + m_up = false; + emit down(); + emit connectivityChanged( m_up ); + } + break; + default: + break; + } +} + +void +lastfm::InternetConnectionMonitor::onNetworkUp() +{ +#ifdef Q_OS_MAC + // We don't need to check on mac as the + // check is done as part of the reach api + m_up = true; + emit up(); + emit connectivityChanged( m_up ); +#else + qDebug() << "Network seems to be up again. Let's try if there's internet connection!"; + lastfm::nam()->head( QNetworkRequest( QUrl( tr( "http://www.last.fm/" ) ) ) ); +#endif +} + +void +lastfm::InternetConnectionMonitor::onNetworkDown() +{ + qDebug() << "Internet is down :( boo!!"; + m_up = false; + emit down(); + emit connectivityChanged( m_up ); +} + +NetworkConnectionMonitor* +lastfm::InternetConnectionMonitor::createNetworkConnectionMonitor() +{ + NetworkConnectionMonitor* ncm = 0; + +#ifdef Q_WS_X11 + ncm = new LNetworkConnectionMonitor( this ); +#elif defined(Q_WS_WIN) + ncm = new WNetworkConnectionMonitor( this ); +#elif defined(Q_WS_MAC) + ncm = new MNetworkConnectionMonitor( this ); +#endif + + return ncm; +} diff --git a/thirdparty/liblastfm2/src/ws/InternetConnectionMonitor.h b/thirdparty/liblastfm2/src/ws/InternetConnectionMonitor.h new file mode 100644 index 000000000..3f770125c --- /dev/null +++ b/thirdparty/liblastfm2/src/ws/InternetConnectionMonitor.h @@ -0,0 +1,80 @@ +/* + Copyright 2009 Last.fm Ltd. + - Primarily authored by Max Howell, Jono Cole and Doug Mansell + + This file is part of liblastfm. + + liblastfm 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. + + liblastfm 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 liblastfm. If not, see . +*/ + +#ifndef LASTFM_CONNECTION_MONITOR_H +#define LASTFM_CONNECTION_MONITOR_H + +#include +#include + +class NetworkConnectionMonitor; + +#ifdef Q_WS_X11 +class LNetworkConnectionMonitor; +#endif + +namespace lastfm { + +class LASTFM_DLLEXPORT InternetConnectionMonitor : public QObject +{ + Q_OBJECT + enum NMState + { + Unknown, + Asleep, + Connecting, + Connected, + Disconnected + }; + +public: + /** if internet is unavailable you will get a down() signal soon, otherwise + * you won't get a signal until the net goes down */ + InternetConnectionMonitor( QObject *parent = 0 ); + + bool isDown() const { return !m_up; } + bool isUp() const { return m_up; } + + NetworkConnectionMonitor* createNetworkConnectionMonitor(); + +signals: + /** yay! internet has returned */ + void up( const QString& connectionName = "" ); + + /** we think the internet is unavailable, but well, still try, but show + * an unhappy face in the statusbar or something */ + void down( const QString& connectionName = "" ); + + /** emitted after the above */ + void connectivityChanged( bool ); + +private slots: + void onFinished( QNetworkReply* reply ); + void onNetworkUp(); + void onNetworkDown(); + +private: + bool m_up; + NetworkConnectionMonitor* m_networkMonitor; +}; + +} //namespace lastfm + +#endif diff --git a/thirdparty/liblastfm2/src/ws/NetworkAccessManager.cpp b/thirdparty/liblastfm2/src/ws/NetworkAccessManager.cpp new file mode 100644 index 000000000..2d628b1ad --- /dev/null +++ b/thirdparty/liblastfm2/src/ws/NetworkAccessManager.cpp @@ -0,0 +1,159 @@ +/* + Copyright 2009 Last.fm Ltd. + - Primarily authored by Max Howell, Jono Cole and Doug Mansell + + This file is part of liblastfm. + + liblastfm 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. + + liblastfm 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 liblastfm. If not, see . +*/ +#include "NetworkAccessManager.h" +#include "InternetConnectionMonitor.h" +#include +#include +#include +#include +#ifdef WIN32 +#include "win/IeSettings.h" +#include "win/Pac.h" +#endif +#ifdef __APPLE__ +#include "mac/ProxyDict.h" +#endif + + +static struct NetworkAccessManagerInit +{ + // We do this upfront because then our Firehose QTcpSocket will have a proxy + // set by default. As well as any plain QNetworkAcessManager stuff, and the + // scrobbler + // In theory we should do this every request in case the configuration + // changes but that is fairly unlikely use case, init? Maybe we should + // anyway.. + + NetworkAccessManagerInit() + { + #ifdef WIN32 + IeSettings s; + // if it's autodetect, we determine the proxy everytime in proxy() + // we don't really want to do a PAC lookup here, as it times out + // at two seconds, so that hangs startup + if (!s.fAutoDetect && s.lpszProxy) + { + QUrl url( QString::fromUtf16(s.lpszProxy) ); + QNetworkProxy proxy( QNetworkProxy::HttpProxy ); + proxy.setHostName( url.host() ); + proxy.setPort( url.port() ); + QNetworkProxy::setApplicationProxy( proxy ); + } + #endif + #ifdef __APPLE__ + ProxyDict dict; + if (dict.isProxyEnabled()) + { + QNetworkProxy proxy( QNetworkProxy::HttpProxy ); + proxy.setHostName( dict.host ); + proxy.setPort( dict.port ); + + QNetworkProxy::setApplicationProxy( proxy ); + } + #endif + } +} init; + + +namespace lastfm +{ + LASTFM_DLLEXPORT QByteArray UserAgent; +} + + +lastfm::NetworkAccessManager::NetworkAccessManager( QObject* parent ) + : QNetworkAccessManager( parent ) + #ifdef WIN32 + , m_pac( 0 ) + , m_monitor( 0 ) + #endif +{ + // can't be done in above init, as applicationName() won't be set + if (lastfm::UserAgent.isEmpty()) + { + QByteArray name = QCoreApplication::applicationName().toUtf8(); + QByteArray version = QCoreApplication::applicationVersion().toUtf8(); + if (version.size()) version.prepend( ' ' ); + lastfm::UserAgent = name + version + " (" + lastfm::platform() + ")"; + } +} + + +lastfm::NetworkAccessManager::~NetworkAccessManager() +{ +#ifdef WIN32 + delete m_pac; +#endif +} + + +QNetworkProxy +lastfm::NetworkAccessManager::proxy( const QNetworkRequest& request ) +{ + Q_UNUSED( request ); + +#ifdef WIN32 + IeSettings s; + if (s.fAutoDetect) + { + if (!m_pac) { + m_pac = new Pac; + if ( !m_monitor ) + { + m_monitor = new InternetConnectionMonitor( this ); + connect( m_monitor, SIGNAL( connectivityChanged( bool ) ), SLOT( onConnectivityChanged( bool ) ) ); + } + } + return m_pac->resolve( request, s.lpszAutoConfigUrl ); + } +#endif + + return QNetworkProxy::applicationProxy(); +} + + +QNetworkReply* +lastfm::NetworkAccessManager::createRequest( Operation op, const QNetworkRequest& request_, QIODevice* outgoingData ) +{ + QNetworkRequest request = request_; + + request.setAttribute( QNetworkRequest::CacheLoadControlAttribute, QNetworkRequest::PreferCache ); + request.setRawHeader( "User-Agent", lastfm::UserAgent ); + +#ifdef WIN32 + // PAC proxies can vary by domain, so we have to check everytime :( + QNetworkProxy proxy = this->proxy( request ); + if (proxy.type() != QNetworkProxy::NoProxy) + QNetworkAccessManager::setProxy( proxy ); +#endif + + return QNetworkAccessManager::createRequest( op, request, outgoingData ); +} + + +void +lastfm::NetworkAccessManager::onConnectivityChanged( bool up ) +{ + Q_UNUSED( up ); + +#ifdef WIN32 + if (up && m_pac) m_pac->resetFailedState(); +#endif +} diff --git a/thirdparty/liblastfm2/src/ws/NetworkAccessManager.h b/thirdparty/liblastfm2/src/ws/NetworkAccessManager.h new file mode 100644 index 000000000..a3022ce25 --- /dev/null +++ b/thirdparty/liblastfm2/src/ws/NetworkAccessManager.h @@ -0,0 +1,66 @@ +/* + Copyright 2009 Last.fm Ltd. + - Primarily authored by Max Howell, Jono Cole and Doug Mansell + + This file is part of liblastfm. + + liblastfm 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. + + liblastfm 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 liblastfm. If not, see . +*/ +#ifndef LASTFM_WS_ACCESS_MANAGER_H +#define LASTFM_WS_ACCESS_MANAGER_H + +#include +#include +#include +#include + + +namespace lastfm { + +/** Sets useragent and proxy. Auto detecting the proxy where possible. */ +class LASTFM_DLLEXPORT NetworkAccessManager : public QNetworkAccessManager +{ + Q_OBJECT + +#ifdef Q_WS_WIN + class Pac *m_pac; + class InternetConnectionMonitor* m_monitor; +#endif + +public: + NetworkAccessManager( QObject *parent = 0 ); + ~NetworkAccessManager(); + + /** PAC allows different proxy configurations depending on the request + * URL and even UserAgent! Thus we allow you to pass that in, we + * automatically configure the proxy for every request through + * WsAccessManager */ + QNetworkProxy proxy( const QNetworkRequest& = QNetworkRequest() ); + +protected: + virtual QNetworkReply* createRequest( Operation, const QNetworkRequest&, QIODevice* outgoingdata = 0 ); + +private slots: + void onConnectivityChanged( bool ); + +private: + /** this function calls QNetworkAccessManager::setProxy, and thus + * configures the proxy correctly for the next request created by + * createRequest. This is necessary due */ + void applyProxy( const QNetworkRequest& ); +}; + +} //namespace lastfm + +#endif diff --git a/thirdparty/liblastfm2/src/ws/NetworkConnectionMonitor.cpp b/thirdparty/liblastfm2/src/ws/NetworkConnectionMonitor.cpp new file mode 100644 index 000000000..b4a62732b --- /dev/null +++ b/thirdparty/liblastfm2/src/ws/NetworkConnectionMonitor.cpp @@ -0,0 +1,51 @@ +/* + Copyright 2010 Last.fm Ltd. + - Primarily authored by Max Howell, Jono Cole and Doug Mansell + + This file is part of liblastfm. + + liblastfm 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. + + liblastfm 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 liblastfm. If not, see . +*/ + +#include "NetworkConnectionMonitor.h" + +NetworkConnectionMonitor::NetworkConnectionMonitor( QObject* /*parent*/ ) + : m_connected( true ) +{ +} + +NetworkConnectionMonitor::~NetworkConnectionMonitor() +{ +} + +bool +NetworkConnectionMonitor::isConnected() const +{ + return m_connected; +} + +void +NetworkConnectionMonitor::setConnected( bool connected ) +{ + if ( m_connected != connected ) + { + m_connected = connected; + + if ( connected ) + emit networkUp(); + else + emit networkDown(); + } +} + diff --git a/thirdparty/liblastfm2/src/ws/NetworkConnectionMonitor.h b/thirdparty/liblastfm2/src/ws/NetworkConnectionMonitor.h new file mode 100644 index 000000000..a21cf73e3 --- /dev/null +++ b/thirdparty/liblastfm2/src/ws/NetworkConnectionMonitor.h @@ -0,0 +1,46 @@ +/* + Copyright 2010 Last.fm Ltd. + - Primarily authored by Jono Cole, Michael Coffey, and William Viana + + This file is part of liblastfm. + + liblastfm 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. + + liblastfm 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 liblastfm. If not, see . +*/ + +#ifndef NETWORK_CONNECTION_MONITOR_H +#define NETWORK_CONNECTION_MONITOR_H + +#include +#include + +class LASTFM_DLLEXPORT NetworkConnectionMonitor : public QObject +{ + Q_OBJECT +public: + NetworkConnectionMonitor( QObject *parent = 0 ); + ~NetworkConnectionMonitor(); + bool isConnected() const; + +signals: + void networkUp(); + void networkDown(); + +protected: + void setConnected( bool connected ); + +private: + bool m_connected; +}; + +#endif // NETWORK_CONNECTION_MONITOR_H diff --git a/thirdparty/liblastfm2/src/ws/linux/LNetworkConnectionMonitor.h b/thirdparty/liblastfm2/src/ws/linux/LNetworkConnectionMonitor.h new file mode 100644 index 000000000..9f7895bed --- /dev/null +++ b/thirdparty/liblastfm2/src/ws/linux/LNetworkConnectionMonitor.h @@ -0,0 +1,54 @@ +/* + Copyright 2010 Last.fm Ltd. + - Primarily authored by Max Howell, Jono Cole and Doug Mansell + + This file is part of liblastfm. + + liblastfm 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. + + liblastfm 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 liblastfm. If not, see . +*/ + + +#ifndef LNETWORK_CONNECTION_MONITOR_H +#define LNETWORK_CONNECTION_MONITOR_H + +#include "../NetworkConnectionMonitor.h" +#include +#include + +class QDBusConnection; +class QDBusInterface; + +class LNetworkConnectionMonitor : public NetworkConnectionMonitor +{ + Q_OBJECT + + enum NMState + { + Unknown=1, + Asleep, + Connected, + Disconnected + }; + +public: + LNetworkConnectionMonitor( QObject* parent = 0 ); + ~LNetworkConnectionMonitor(); +private slots: + void onStateChange( uint newState ); +private: + QDBusInterface* m_nmInterface; +}; + +#endif // LNETWORK_CONNECTION_MONITOR_H + diff --git a/thirdparty/liblastfm2/src/ws/linux/LNetworkConnectionMonitor_linux.cpp b/thirdparty/liblastfm2/src/ws/linux/LNetworkConnectionMonitor_linux.cpp new file mode 100644 index 000000000..d8dea9dd6 --- /dev/null +++ b/thirdparty/liblastfm2/src/ws/linux/LNetworkConnectionMonitor_linux.cpp @@ -0,0 +1,86 @@ +/* + Copyright 2010 Last.fm Ltd. + - Primarily authored by Max Howell, Jono Cole and Doug Mansell + + This file is part of liblastfm. + + liblastfm 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. + + liblastfm 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 liblastfm. If not, see . +*/ + + +#include "LNetworkConnectionMonitor.h" + +#include +#include +#include + +LNetworkConnectionMonitor::LNetworkConnectionMonitor( QObject* parent ) : + NetworkConnectionMonitor( parent ) +{ + m_nmInterface = new QDBusInterface( QString( "org.freedesktop.NetworkManager" ), + QString( "/org/freedesktop/NetworkManager" ), + QString( "org.freedesktop.NetworkManager" ), + QDBusConnection::systemBus(), + this ); + + //get current connection state + QDBusInterface* dbusInterface = new QDBusInterface( QString( "org.freedesktop.NetworkManager" ), + QString( "/org/freedesktop/NetworkManager" ), + QString( "org.freedesktop.DBus.Properties" ), + QDBusConnection::systemBus(), + this ); + + QDBusReply reply = dbusInterface->call( "Get", "org.freedesktop.NetworkManager", "state" ); + if ( reply.isValid() ) + { + if ( reply.value() == Connected ) + { + setConnected( true ); + } + else if ( reply.value() == Disconnected ) + { + setConnected( false ); + } + } + else + { + qDebug() << "Error: " << reply.error(); + } + delete dbusInterface; + + //connect network manager signals + connect( m_nmInterface, SIGNAL( StateChange( uint ) ), this, SLOT( onStateChange( uint ) ) ); + +} + +LNetworkConnectionMonitor::~LNetworkConnectionMonitor() +{ + delete m_nmInterface; +} + + +void +LNetworkConnectionMonitor::onStateChange( uint newState ) +{ + qDebug() << "Networkmanager state change!"; + + if ( newState == Disconnected ) + { + setConnected( false ); + } + else if ( newState == Connected ) + { + setConnected( true ); + } +} diff --git a/thirdparty/liblastfm2/src/ws/mac/MNetworkConnectionMonitor.h b/thirdparty/liblastfm2/src/ws/mac/MNetworkConnectionMonitor.h new file mode 100644 index 000000000..96aca0767 --- /dev/null +++ b/thirdparty/liblastfm2/src/ws/mac/MNetworkConnectionMonitor.h @@ -0,0 +1,52 @@ +/* + Copyright 2009 Last.fm Ltd. + - Primarily authored by Jono Cole and Michael Coffey + + This file is part of liblastfm. + + liblastfm 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. + + liblastfm 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 liblastfm. If not, see . +*/ + +#ifndef MNETWORK_CONNECTION_MONITOR_H +#define MNETWORK_CONNECTION_MONITOR_H + +#include "../NetworkConnectionMonitor.h" +#include +#include + +#ifdef Q_WS_MAC +#include //TODO remove +#include +#endif + +class __SCNetworkReachability; + +class MNetworkConnectionMonitor : public NetworkConnectionMonitor +{ + Q_OBJECT +public: + MNetworkConnectionMonitor( QObject* parent = 0 ); + ~MNetworkConnectionMonitor(); +private slots: + +private: +#ifdef Q_WS_MAC + static void callback( SCNetworkReachabilityRef target, + SCNetworkConnectionFlags flags, + void *info ); +#endif +}; + +#endif // MNETWORK_CONNECTION_MONITOR_H + diff --git a/thirdparty/liblastfm2/src/ws/mac/MNetworkConnectionMonitor_mac.cpp b/thirdparty/liblastfm2/src/ws/mac/MNetworkConnectionMonitor_mac.cpp new file mode 100644 index 000000000..f9a03f47a --- /dev/null +++ b/thirdparty/liblastfm2/src/ws/mac/MNetworkConnectionMonitor_mac.cpp @@ -0,0 +1,71 @@ +/* + Copyright 2009 Last.fm Ltd. + - Primarily authored by Jono Cole and Michael Coffey + + This file is part of liblastfm. + + liblastfm 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. + + liblastfm 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 liblastfm. If not, see . +*/ + +#include "MNetworkConnectionMonitor.h" +#include "ws/ws.h" + +#include +#include + +MNetworkConnectionMonitor* context = 0; + +MNetworkConnectionMonitor::MNetworkConnectionMonitor( QObject* parent ) : + NetworkConnectionMonitor( parent ) +{ + context = this; + + SCNetworkReachabilityRef ref = SCNetworkReachabilityCreateWithName( NULL, LASTFM_WS_HOSTNAME ); + SCNetworkReachabilityScheduleWithRunLoop( ref, CFRunLoopGetCurrent(), kCFRunLoopDefaultMode ); + SCNetworkReachabilitySetCallback( ref, callback, NULL ); + CFRelease( ref ); +} + +MNetworkConnectionMonitor::~MNetworkConnectionMonitor() +{ +} + + +void +MNetworkConnectionMonitor::callback( SCNetworkReachabilityRef target, + SCNetworkConnectionFlags flags, + void * ) +{ + static bool up = true; + + // I couldn't find any diffinitive usage examples for these flags + // so I had to guess, since I can't test, eg. dial up :( + + bool b; + if (flags & kSCNetworkFlagsConnectionRequired) + b = false; + else + b = flags & (kSCNetworkFlagsReachable | kSCNetworkFlagsTransientConnection | kSCNetworkFlagsConnectionAutomatic); + + qDebug() << "Can reach " LASTFM_WS_HOSTNAME ":" << b << ", flags:" << flags; + + // basically, avoids telling everyone that we're up already on startup + if (up == b) + return; + + up = b; + + context->setConnected(b); +} + diff --git a/thirdparty/liblastfm2/src/ws/mac/ProxyDict.h b/thirdparty/liblastfm2/src/ws/mac/ProxyDict.h new file mode 100644 index 000000000..2d873f88a --- /dev/null +++ b/thirdparty/liblastfm2/src/ws/mac/ProxyDict.h @@ -0,0 +1,75 @@ +/* + Copyright 2009 Last.fm Ltd. + - Primarily authored by Max Howell, Jono Cole and Doug Mansell + + This file is part of liblastfm. + + liblastfm 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. + + liblastfm 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 liblastfm. If not, see . +*/ +#include + + +struct ProxyDict +{ + ProxyDict(); + + int port; + QString host; + + bool isProxyEnabled() const { return port > 0 && host.size(); } +}; + + +inline ProxyDict::ProxyDict() : port( 0 ) +{ + // Get the dictionary. + CFDictionaryRef proxyDict = SCDynamicStoreCopyProxies( NULL ); + bool result = (proxyDict != NULL); + + // Get the enable flag. This isn't a CFBoolean, but a CFNumber. + CFNumberRef enableNum; + int enable; + if (result) { + enableNum = (CFNumberRef) CFDictionaryGetValue( proxyDict, kSCPropNetProxiesHTTPEnable ); + result = (enableNum != NULL) && (CFGetTypeID(enableNum) == CFNumberGetTypeID()); + } + if (result) + result = CFNumberGetValue( enableNum, kCFNumberIntType, &enable ) && (enable != 0); + + // Get the proxy host. DNS names must be in ASCII. If you + // put a non-ASCII character in the "Secure Web Proxy" + // field in the Network preferences panel, the CFStringGetCString + // function will fail and this function will return false. + CFStringRef hostStr; + if (result) { + hostStr = (CFStringRef) CFDictionaryGetValue( proxyDict, kSCPropNetProxiesHTTPProxy ); + result = (hostStr != NULL) && (CFGetTypeID(hostStr) == CFStringGetTypeID()); + } + if (result) + host = lastfm::CFStringToQString( hostStr ); + + // get the proxy port + CFNumberRef portNum; + + if (result) { + portNum = (CFNumberRef) CFDictionaryGetValue( proxyDict, kSCPropNetProxiesHTTPPort ); + result = (portNum != NULL) && (CFGetTypeID(portNum) == CFNumberGetTypeID()); + } + if (result) + result = CFNumberGetValue( portNum, kCFNumberIntType, &port ); + + // clean up. + if (proxyDict != NULL) + CFRelease( proxyDict ); +} diff --git a/thirdparty/liblastfm2/src/ws/win/ComSetup.h b/thirdparty/liblastfm2/src/ws/win/ComSetup.h new file mode 100644 index 000000000..fc3816bd7 --- /dev/null +++ b/thirdparty/liblastfm2/src/ws/win/ComSetup.h @@ -0,0 +1,63 @@ +/* + Copyright 2009 Last.fm Ltd. + - Primarily authored by Max Howell, Jono Cole and Doug Mansell + + This file is part of liblastfm. + + liblastfm 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. + + liblastfm 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 liblastfm. If not, see . +*/ +#ifndef _WIN32_WINNT +// bring in CoInitializeSecurity from objbase.h +#define _WIN32_WINNT 0x0400 +#endif + +#include +#include +#include + + +/** @brief WsConnectionMonitor needs Com to work as early as possible so we do this + * @author + */ +class ComSetup +{ +public: + ComSetup() + { + HRESULT hr = CoInitialize(0); + m_bComInitialised = SUCCEEDED(hr); + _ASSERT(m_bComInitialised); + if (m_bComInitialised) { + setupSecurity(); + } + } + + void setupSecurity() + { + CSecurityDescriptor sd; + sd.InitializeFromThreadToken(); + HRESULT hr = CoInitializeSecurity(sd, -1, NULL, NULL, RPC_C_AUTHN_LEVEL_PKT, RPC_C_IMP_LEVEL_IMPERSONATE, NULL, EOAC_NONE, NULL); + _ASSERT(SUCCEEDED(hr)); + } + + ~ComSetup() + { + if (m_bComInitialised) { + CoUninitialize(); + } + } + +private: + bool m_bComInitialised; +}; diff --git a/thirdparty/liblastfm2/src/ws/win/IeSettings.h b/thirdparty/liblastfm2/src/ws/win/IeSettings.h new file mode 100644 index 000000000..5d756a5ea --- /dev/null +++ b/thirdparty/liblastfm2/src/ws/win/IeSettings.h @@ -0,0 +1,43 @@ +/* + Copyright 2009 Last.fm Ltd. + - Primarily authored by Max Howell, Jono Cole and Doug Mansell + + This file is part of liblastfm. + + liblastfm 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. + + liblastfm 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 liblastfm. If not, see . +*/ +#include +#include + + +/** @brief memory managing wrapper for WINHTTP_CURRENT_USER_IE_PROXY_CONFIG + * @author + */ +struct IeSettings : WINHTTP_CURRENT_USER_IE_PROXY_CONFIG +{ + IeSettings() + { + if (!WinHttpGetIEProxyConfigForCurrentUser(this)) { + fAutoDetect = FALSE; + lpszAutoConfigUrl = lpszProxy = lpszProxyBypass = 0; + } + } + + ~IeSettings() + { + if (lpszAutoConfigUrl) GlobalFree(lpszAutoConfigUrl); + if (lpszProxy) GlobalFree(lpszProxy); + if (lpszProxyBypass) GlobalFree(lpszProxyBypass); + } +}; diff --git a/thirdparty/liblastfm2/src/ws/win/NdisEvents.cpp b/thirdparty/liblastfm2/src/ws/win/NdisEvents.cpp new file mode 100644 index 000000000..7263af24b --- /dev/null +++ b/thirdparty/liblastfm2/src/ws/win/NdisEvents.cpp @@ -0,0 +1,87 @@ +/* + Copyright 2009 Last.fm Ltd. + - Primarily authored by Max Howell, Jono Cole and Doug Mansell + + This file is part of liblastfm. + + liblastfm 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. + + liblastfm 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 liblastfm. If not, see . +*/ +#include "NdisEvents.h" +#include "WmiSink.h" + +// see http://msdn.microsoft.com/en-us/magazine/cc301850.aspx for +// more about Ndis and wmi and getting these events + +// Link to wbemuuid.lib to resolve IWbemObjectSink and IWbemClassObject +// interface definitions. + +NdisEvents::NdisEvents() + : m_pSink(0) +{} + +NdisEvents::~NdisEvents() +{ + if (m_pSink) + m_pSink->disconnect(); + if (m_pServices && m_pSink) + m_pServices->CancelAsyncCall(m_pSink); + // and reference counting will take care of the WmiSink object +} + +HRESULT +NdisEvents::registerForNdisEvents() +{ + HRESULT hr = m_pLocator.CoCreateInstance(CLSID_WbemLocator); + if (FAILED(hr)) + return hr; + + // Connect to the root\wmi namespace with the current user. + hr = m_pLocator->ConnectServer(CComBSTR("ROOT\\WMI"), // strNetworkResource + NULL, // strUser + NULL, // strPassword + NULL, // strLocale + 0, // lSecurityFlags + CComBSTR(""), // strAuthority + NULL, // pCtx + &m_pServices + ); + if (FAILED(hr)) + return hr; + + m_pSink = new WmiSink(this); + + ////////////////////////// + + // other notifications we're not interested in right now include... + // MSNdis_NotifyAdapterArrival \DEVICE\ + // MSNdis_NotifyAdapterRemoval + // MSNdis_StatusLinkSpeedChange + // MSNdis_NotifyVcArrival + // MSNdis_NotifyVcRemoval + // MSNdis_StatusResetStart + // MSNdis_StatusResetEnd + // MSNdis_StatusProtocolBind + // MSNdis_StatusProtocolUnbind + // MSNdis_StatusMediaSpecificIndication + + CComBSTR wql("WQL"); + CComBSTR query("SELECT * FROM MSNdis_StatusMediaDisconnect"); + hr = m_pServices->ExecNotificationQueryAsync(wql, query, 0, 0, m_pSink); + + query = "SELECT * FROM MSNdis_StatusMediaConnect"; + hr = m_pServices->ExecNotificationQueryAsync(wql, query, 0, 0, m_pSink); + + return S_OK; +} + diff --git a/thirdparty/liblastfm2/src/ws/win/NdisEvents.h b/thirdparty/liblastfm2/src/ws/win/NdisEvents.h new file mode 100644 index 000000000..1e1cfcf9c --- /dev/null +++ b/thirdparty/liblastfm2/src/ws/win/NdisEvents.h @@ -0,0 +1,44 @@ +/* + Copyright 2009 Last.fm Ltd. + - Primarily authored by Max Howell, Jono Cole and Doug Mansell + + This file is part of liblastfm. + + liblastfm 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. + + liblastfm 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 liblastfm. If not, see . +*/ +#ifndef NDIS_EVENTS_H +#define NDIS_EVENTS_H + +#include +#include +#include + +class NdisEvents +{ +public: + NdisEvents(); + ~NdisEvents(); + HRESULT registerForNdisEvents(); + + virtual void onConnectionUp(BSTR name) = 0; + virtual void onConnectionDown(BSTR name) = 0; + +private: + CComPtr m_pLocator; + CComPtr m_pServices; + class WmiSink *m_pSink; +}; + +#endif + diff --git a/thirdparty/liblastfm2/src/ws/win/Pac.cpp b/thirdparty/liblastfm2/src/ws/win/Pac.cpp new file mode 100644 index 000000000..3e3d72b12 --- /dev/null +++ b/thirdparty/liblastfm2/src/ws/win/Pac.cpp @@ -0,0 +1,128 @@ +/* + Copyright 2009 Last.fm Ltd. + - Primarily authored by Max Howell, Jono Cole and Doug Mansell + + This file is part of liblastfm. + + liblastfm 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. + + liblastfm 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 liblastfm. If not, see . +*/ +#include "Pac.h" +#include +#include +#include +#include +#include + + +static bool +parsePacServer(const QString &s, QNetworkProxy &p) +{ + // remove optional leading "scheme=" portion + int start = s.indexOf('='); + QUrl url(s.mid(start+1), QUrl::TolerantMode); + + if (url.isValid()) + { + p.setHostName(url.host()); + p.setPort(url.port()); + return true; + } + return false; +} + + +static QList +parsePacResult(const QString &pacResult) +{ + // msdn says: "The proxy server list contains one or more of the + // following strings separated by semicolons or whitespace." + // ([=]["://"][":"]) + + QList result; + QStringList proxies = pacResult.split(QRegExp("[\\s;]"), QString::SkipEmptyParts); + foreach(const QString &s, proxies) + { + QNetworkProxy proxy( QNetworkProxy::HttpProxy ); + if (parsePacServer(s, proxy)) + { + result << proxy; + } + } + return result; +} + + +//////////////// + + +lastfm::Pac::Pac() + : m_bFailed( false ) + , m_hSession( 0 ) +{} + +lastfm::Pac::~Pac() +{ + if (m_hSession) + WinHttpCloseHandle(m_hSession); +} + +QNetworkProxy +lastfm::Pac::resolve(const QNetworkRequest &request, const wchar_t* pacUrl) +{ + QNetworkProxy out; + if (m_bFailed) return out; + + if (!m_hSession) + { + QByteArray user_agent = request.rawHeader("user-agent"); + m_hSession = WinHttpOpen(CA2W(user_agent), WINHTTP_ACCESS_TYPE_NO_PROXY, 0, 0, WINHTTP_FLAG_ASYNC); + } + if (m_hSession) + { + WINHTTP_PROXY_INFO info; + WINHTTP_AUTOPROXY_OPTIONS opts; + memset(&opts, 0, sizeof(opts)); + if (pacUrl) + { + opts.dwFlags = WINHTTP_AUTOPROXY_CONFIG_URL; + opts.lpszAutoConfigUrl = pacUrl; + } + else + { + opts.dwFlags = WINHTTP_AUTOPROXY_AUTO_DETECT; + opts.dwAutoDetectFlags = WINHTTP_AUTO_DETECT_TYPE_DHCP | WINHTTP_AUTO_DETECT_TYPE_DNS_A; + } + opts.fAutoLogonIfChallenged = TRUE; + + if (WinHttpGetProxyForUrl(m_hSession, request.url().toString().utf16(), &opts, &info)) { + if (info.lpszProxy) + { + QList proxies = parsePacResult(QString::fromUtf16(info.lpszProxy)); + if (!proxies.empty()) + { + out = proxies.at(0); + } + GlobalFree(info.lpszProxy); + } + if (info.lpszProxyBypass) + { + GlobalFree(info.lpszProxyBypass); + } + } else { + m_bFailed = true; + } + } + + return out; +} diff --git a/thirdparty/liblastfm2/src/ws/win/Pac.h b/thirdparty/liblastfm2/src/ws/win/Pac.h new file mode 100644 index 000000000..075d128f6 --- /dev/null +++ b/thirdparty/liblastfm2/src/ws/win/Pac.h @@ -0,0 +1,52 @@ +/* + Copyright 2009 Last.fm Ltd. + - Primarily authored by Max Howell, Jono Cole and Doug Mansell + + This file is part of liblastfm. + + liblastfm 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. + + liblastfm 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 liblastfm. If not, see . +*/ +#ifndef WS_AUTOPROXY_H +#define WS_AUTOPROXY_H + +#include +#include +#include +class QNetworkRequest; + +namespace lastfm +{ + /** @brief simple wrapper to do per url automatic proxy detection + * @author + */ + class Pac + { + HINTERNET m_hSession; + bool m_bFailed; + + public: + Pac(); + ~Pac(); + + QNetworkProxy resolve( const QNetworkRequest& url, const wchar_t* pacUrl ); + + void resetFailedState() { m_bFailed = false; } + + private: + Pac( const Pac& ); //undefined + Pac operator=( const Pac& ); //undefined + }; +} + +#endif \ No newline at end of file diff --git a/thirdparty/liblastfm2/src/ws/win/WNetworkConnectionMonitor.h b/thirdparty/liblastfm2/src/ws/win/WNetworkConnectionMonitor.h new file mode 100755 index 000000000..26fb58cdd --- /dev/null +++ b/thirdparty/liblastfm2/src/ws/win/WNetworkConnectionMonitor.h @@ -0,0 +1,44 @@ +/* + Copyright 2010 Last.fm Ltd. + - Primarily authored by Jono Cole, Michael Coffey, and William Viana + + This file is part of liblastfm. + + liblastfm 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. + + liblastfm 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 liblastfm. If not, see . +*/ + +#ifndef WNETWORK_CONNECTION_MONITOR_H +#define WNETWORK_CONNECTION_MONITOR_H + +#include "../NetworkConnectionMonitor.h" +#include +#include + +namespace lastfm { class NdisEventsProxy; } + +class WNetworkConnectionMonitor : public NetworkConnectionMonitor +{ + Q_OBJECT +public: + friend class lastfm::NdisEventsProxy; + + WNetworkConnectionMonitor( QObject* parent = 0 ); + ~WNetworkConnectionMonitor(); + +private: + lastfm::NdisEventsProxy* m_ndisEventsProxy; +}; + +#endif // WNETWORK_CONNECTION_MONITOR_H + diff --git a/thirdparty/liblastfm2/src/ws/win/WNetworkConnectionMonitor_win.cpp b/thirdparty/liblastfm2/src/ws/win/WNetworkConnectionMonitor_win.cpp new file mode 100755 index 000000000..09fd97cc5 --- /dev/null +++ b/thirdparty/liblastfm2/src/ws/win/WNetworkConnectionMonitor_win.cpp @@ -0,0 +1,67 @@ +/* + Copyright 2010 Last.fm Ltd. + - Primarily authored by Jono Cole, Michael Coffey, and William Viana + + This file is part of liblastfm. + + liblastfm 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. + + liblastfm 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 liblastfm. If not, see . +*/ + +#include "WNetworkConnectionMonitor.h" + +// WsAccessManager needs special init (on Windows), and it needs to be done +// early, so be careful about moving this +#include "../win/ComSetup.h" //must be first header or compile fail results! +#include "../win/NdisEvents.h" +static ComSetup com_setup; + +namespace lastfm { + +// bounce NdisEvents signals through here so we don't have to expose the +// NdisEvents interface in InternetConnectionMonitor :) +class NdisEventsProxy : public NdisEvents +{ +public: + NdisEventsProxy(WNetworkConnectionMonitor* icm) + :m_icm(icm) + { + } + + // WmiSink callbacks: + void onConnectionUp( BSTR /*name*/ ) + { + m_icm->setConnected( true ); + } + + void onConnectionDown( BSTR /*name*/ ) + { + m_icm->setConnected( false ); + } + + WNetworkConnectionMonitor* m_icm; +}; + +} + +WNetworkConnectionMonitor::WNetworkConnectionMonitor( QObject* parent ) : + NetworkConnectionMonitor( parent ) +{ + m_ndisEventsProxy = new lastfm::NdisEventsProxy( this ); + m_ndisEventsProxy->registerForNdisEvents(); +} + +WNetworkConnectionMonitor::~WNetworkConnectionMonitor() +{ + delete m_ndisEventsProxy; +} diff --git a/thirdparty/liblastfm2/src/ws/win/WmiSink.cpp b/thirdparty/liblastfm2/src/ws/win/WmiSink.cpp new file mode 100644 index 000000000..3d564266c --- /dev/null +++ b/thirdparty/liblastfm2/src/ws/win/WmiSink.cpp @@ -0,0 +1,202 @@ +/* + Copyright 2009 Last.fm Ltd. + - Primarily authored by Max Howell, Jono Cole and Doug Mansell + + This file is part of liblastfm. + + liblastfm 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. + + liblastfm 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 liblastfm. If not, see . +*/ +#include "WmiSink.h" +#include "NdisEvents.h" + + +WmiSink::WmiSink(NdisEvents *callback) + : m_cRef(1) + , m_callback(callback) +{} + +WmiSink::~WmiSink() +{} + +void +WmiSink::disconnect() +{ + m_callback = 0; +} + +STDMETHODIMP +WmiSink::QueryInterface(REFIID riid, LPVOID * ppv) +{ + *ppv = 0; + + if (IID_IUnknown==riid || IID_IWbemObjectSink == riid) + { + *ppv = (IWbemObjectSink *) this; + AddRef(); + return NOERROR; + } + + return E_NOINTERFACE; +} + + +ULONG +WmiSink::AddRef() +{ + return ++m_cRef; +} + +ULONG +WmiSink::Release() +{ + if (0 != --m_cRef) + return m_cRef; + + delete this; + return 0; +} + +// This method receives notification objects. +HRESULT +WmiSink::Indicate(long lObjectCount, IWbemClassObject** ppObjArray) +{ + // For each object in the array, extract the object and display the + // information in the object. + for (long i=0; iGet(L"InstanceName", 0, &vt, NULL, NULL); + ppObjArray[i]->Get(L"__Class", 0, &vtClass, NULL, NULL); + + if (!wcscmp (vtClass.bstrVal, L"MSNdis_StatusMediaDisconnect")) + { + if (m_callback) m_callback->onConnectionDown(vt.bstrVal); + } + else if (!wcscmp (vtClass.bstrVal, L"MSNdis_StatusMediaConnect")) + { + if (m_callback) m_callback->onConnectionUp(vt.bstrVal); + } + // notifications we aren't interested in right now: + // + //else if (!wcscmp (vtClass.bstrVal, L"MSNdis_NotifyAdapterRemoval")) + //{ + // bstrLog = (_bstr_t) vt.bstrVal; + // VariantClear (&vt); + // ppObjArray[i]->Get (L"DeviceName", 0, &vt, NULL, NULL); + // bstrLog += (_bstr_t) _T(": ") + (_bstr_t) vt.bstrVal + (_bstr_t) _T(" has been removed"); + // displayDlg.LogEvent (bstrLog); + //} + //else if (!wcscmp (vtClass.bstrVal, L"MSNdis_NotifyAdapterArrival")) + //{ + // bstrLog = (_bstr_t) vt.bstrVal; + // VariantClear (&vt); + // ppObjArray[i]->Get(L"DeviceName", 0, &vt, NULL, NULL); + // bstrLog += (_bstr_t) _T(": ") + (_bstr_t) vt.bstrVal + (_bstr_t) _T(" has been added"); + // displayDlg.LogEvent (bstrLog); + //} + //else if (!wcscmp (vtClass.bstrVal, L"MSNdis_StatusResetStart")) + //{ + // bstrLog = (_bstr_t) vt.bstrVal + (_bstr_t) _T(" has begun a reset"); + // displayDlg.LogEvent (bstrLog); + //} + //else if (!wcscmp (vtClass.bstrVal, L"MSNdis_StatusResetEnd")) + //{ + // bstrLog = (_bstr_t) vt.bstrVal + (_bstr_t) _T(" has finished a reset"); + // displayDlg.LogEvent (bstrLog); + //} + //else if (!wcscmp (vtClass.bstrVal, L"MSNdis_NotifyVcArrival")) + //{ + // bstrLog = (_bstr_t) _T("VC arrival: ") + (_bstr_t) vt.bstrVal; + // displayDlg.LogEvent (bstrLog); + //} + //else if (!wcscmp (vtClass.bstrVal, L"MSNdis_NotifyVcRemoval")) + //{ + // bstrLog = (_bstr_t) _T("VC removal: ") + (_bstr_t) vt.bstrVal; + // displayDlg.LogEvent (bstrLog); + //} + //else if (!wcscmp (vtClass.bstrVal, L"MSNdis_StatusMediaSpecificIndication")) + //{ + // ATLTRACE (_T("Media specific indication: %s\n"), (TCHAR *) (_bstr_t) vt.bstrVal); + // VariantClear (&vt); + // ppObjArray[i]->Get (L"NdisStatusMediaSpecificIndication", 0, &vt, NULL, NULL); + // LONG lLowerBound, lUpperBound, j; + // UCHAR ch; + // SafeArrayGetLBound (V_ARRAY (&vt), 1, &lLowerBound); + // SafeArrayGetUBound (V_ARRAY (&vt), 1, &lUpperBound); + // ATLTRACE (" "); + // for (j = lLowerBound; j<= lUpperBound; j++ ) + // { + // SafeArrayGetElement (V_ARRAY (&vt), &j, &ch); + // ATLTRACE (_T("%4i"), ch); + + // if (((j - lLowerBound) % 8 == 7) && (j <= lUpperBound)) + // ATLTRACE (_T("\n")); + // } + //} + //else if (!wcscmp (vtClass.bstrVal, L"MSNdis_StatusProtocolBind")) + //{ + // bstrLog = (_bstr_t) vt.bstrVal; + // VariantClear (&vt); + // ppObjArray[i]->Get (L"Transport", 0, &vt, NULL, NULL); + // bstrLog += (_bstr_t) _T(" is now bound to ") + (_bstr_t) vt.bstrVal; + // displayDlg.LogEvent (bstrLog); + //} + //else if (!wcscmp (vtClass.bstrVal, L"MSNdis_StatusProtocolBind")) + //{ + // bstrLog = (_bstr_t) vt.bstrVal; + // VariantClear (&vt); + // ppObjArray[i]->Get(L"Transport", 0, &vt, NULL, NULL); + // bstrLog += (_bstr_t) _T(" was unbound from ") + (_bstr_t) vt.bstrVal; + // displayDlg.LogEvent (bstrLog); + //} + //else if (!wcscmp (vtClass.bstrVal, L"MSNdis_StatusLinkSpeedChange")) + //{ + // IWbemClassObject* pWMIObj=NULL; + // bstrLog = (_bstr_t) _T("Link speed change ") + (_bstr_t) vt.bstrVal; + // VariantClear (&vt); + // ppObjArray[i]->Get (L"NdisStatusLinkSpeedChange", 0, &vt, NULL, NULL); + // if SUCCEEDED (vt.punkVal->QueryInterface (IID_IWbemClassObject, (void**)&pWMIObj)) + // { + // TCHAR szNum[50]; + // pWMIObj->Get (L"Inbound", 0, &vt2, NULL, NULL); + // _stprintf (szNum, _T(" Inbound = %u "), vt2.lVal); + // bstrLog += (_bstr_t) szNum; + // VariantClear (&vt2); + // pWMIObj->Get (L"Outbound", 0, &vt2, NULL, NULL); + // _stprintf (szNum, _T(" Outbound = %u "), vt2.lVal); + // bstrLog += (_bstr_t) szNum; + // VariantClear (&vt2); + // pWMIObj->Release (); + // pWMIObj = NULL; + // } + // displayDlg.LogEvent (bstrLog); + //} + + VariantClear (&vtClass); + VariantClear (&vt); + } + return WBEM_NO_ERROR; +} + + +// Misc. status codes sent by sink. +HRESULT +WmiSink::SetStatus(long lFlags, HRESULT hResult, BSTR strParam, IWbemClassObject __RPC_FAR *pObjParam) +{ + lFlags; + hResult; + strParam; + pObjParam; + return WBEM_NO_ERROR; +} diff --git a/thirdparty/liblastfm2/src/ws/win/WmiSink.h b/thirdparty/liblastfm2/src/ws/win/WmiSink.h new file mode 100644 index 000000000..1bce28fdd --- /dev/null +++ b/thirdparty/liblastfm2/src/ws/win/WmiSink.h @@ -0,0 +1,49 @@ +/* + Copyright 2009 Last.fm Ltd. + - Primarily authored by Max Howell, Jono Cole and Doug Mansell + + This file is part of liblastfm. + + liblastfm 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. + + liblastfm 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 liblastfm. If not, see . +*/ +#ifndef WMISINK_WIN_H +#define WMISINK_WIN_H + +#include "WbemCli.h" + +// Sink object for WMI NDIS notifications +class WmiSink : public IWbemObjectSink +{ + UINT m_cRef; + +public: + WmiSink(class NdisEvents *callback); + ~WmiSink(); + + // IUnknown members + STDMETHODIMP QueryInterface(REFIID, LPVOID *); + STDMETHODIMP_(ULONG) AddRef(void); + STDMETHODIMP_(ULONG) Release(void); + + // IWbemObjectSink + STDMETHODIMP Indicate(long, IWbemClassObject**); + STDMETHODIMP SetStatus(long, HRESULT, BSTR, IWbemClassObject *); + + void disconnect(); + +private: + class NdisEvents *m_callback; +}; + +#endif \ No newline at end of file diff --git a/thirdparty/liblastfm2/src/ws/ws.cpp b/thirdparty/liblastfm2/src/ws/ws.cpp new file mode 100644 index 000000000..89c6ece6b --- /dev/null +++ b/thirdparty/liblastfm2/src/ws/ws.cpp @@ -0,0 +1,268 @@ +/* + Copyright 2009 Last.fm Ltd. + - Primarily authored by Max Howell, Jono Cole and Doug Mansell + + This file is part of liblastfm. + + liblastfm 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. + + liblastfm 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 liblastfm. If not, see . +*/ +#include "ws.h" +#include "../core/misc.h" +#include "NetworkAccessManager.h" +#include +#include +#include +#include +#include +#include +static QNetworkAccessManager* nam = 0; + + +QString +lastfm::ws::host() +{ + QStringList const args = QCoreApplication::arguments(); + if (args.contains( "--debug")) + return "ws.staging.audioscrobbler.com"; + + int const n = args.indexOf( "--host" ); + if (n != -1 && args.count() > n+1) + return args[n+1]; + + return LASTFM_WS_HOSTNAME; +} + +static QUrl url() +{ + QUrl url; + url.setScheme( "http" ); + url.setHost( lastfm::ws::host() ); + url.setPath( "/2.0/" ); + return url; +} + +static QString iso639() +{ + return QLocale().name().left( 2 ).toLower(); +} + +void autograph( QMap& params ) +{ + params["api_key"] = lastfm::ws::ApiKey; + params["lang"] = iso639(); +} + +static void sign( QMap& params, bool sk = true ) +{ + autograph( params ); + // it's allowed for sk to be null if we this is an auth call for instance + if (sk && lastfm::ws::SessionKey.size()) + params["sk"] = lastfm::ws::SessionKey; + + QString s; + QMapIterator i( params ); + while (i.hasNext()) { + i.next(); + s += i.key() + i.value(); + } + s += lastfm::ws::SharedSecret; + + params["api_sig"] = lastfm::md5( s.toUtf8() ); +} + + +QNetworkReply* +lastfm::ws::get( QMap params ) +{ + sign( params ); + QUrl url = ::url(); + // Qt setQueryItems doesn't encode a bunch of stuff, so we do it manually + QMapIterator i( params ); + while (i.hasNext()) { + i.next(); + QByteArray const key = QUrl::toPercentEncoding( i.key() ); + QByteArray const value = QUrl::toPercentEncoding( i.value() ); + url.addEncodedQueryItem( key, value ); + } + + qDebug() << url; + + return nam()->get( QNetworkRequest(url) ); +} + + +QNetworkReply* +lastfm::ws::post( QMap params, bool sk ) +{ + sign( params, sk ); + QByteArray query; + QMapIterator i( params ); + while (i.hasNext()) { + i.next(); + query += QUrl::toPercentEncoding( i.key() ) + + '=' + + QUrl::toPercentEncoding( i.value() ) + + '&'; + } + + return nam()->post( QNetworkRequest(url()), query ); +} + + +QByteArray +lastfm::ws::parse( QNetworkReply* reply ) throw( ParseError ) +{ + try + { + QByteArray data = reply->readAll(); + + if (!data.size()) + throw MalformedResponse; + + QDomDocument xml; + xml.setContent( data ); + QDomElement lfm = xml.documentElement(); + + if (lfm.isNull()) + throw MalformedResponse; + + QString const status = lfm.attribute( "status" ); + QDomElement error = lfm.firstChildElement( "error" ); + uint const n = lfm.childNodes().count(); + + // no elements beyond the lfm is perfectably acceptable <-- wtf? + // if (n == 0) // nothing useful in the response + if (status == "failed" || (n == 1 && !error.isNull()) ) + throw error.isNull() + ? MalformedResponse + : Error( error.attribute( "code" ).toUInt() ); + + switch (reply->error()) + { + case QNetworkReply::RemoteHostClosedError: + case QNetworkReply::ConnectionRefusedError: + case QNetworkReply::TimeoutError: + case QNetworkReply::ContentAccessDenied: + case QNetworkReply::ContentOperationNotPermittedError: + case QNetworkReply::UnknownContentError: + case QNetworkReply::ProtocolInvalidOperationError: + case QNetworkReply::ProtocolFailure: + throw TryAgainLater; + + case QNetworkReply::NoError: + default: + break; + } + + //FIXME pretty wasteful to parse XML document twice.. + return data; + } + catch (Error e) + { + switch (e) + { + case OperationFailed: + case InvalidApiKey: + case InvalidSessionKey: + // NOTE will never be received during the LoginDialog stage + // since that happens before this slot is registered with + // QMetaObject in App::App(). Neat :) + QMetaObject::invokeMethod( qApp, "onWsError", Q_ARG( lastfm::ws::Error, e ) ); + default: + throw ParseError(e); + } + } + + // bit dodgy, but prolly for the best + reply->deleteLater(); +} + + +QNetworkAccessManager* +lastfm::nam() +{ + if (!::nam) ::nam = new NetworkAccessManager( qApp ); + return ::nam; +} + + +void +lastfm::setNetworkAccessManager( QNetworkAccessManager* nam ) +{ + delete ::nam; + ::nam = nam; + nam->setParent( qApp ); // ensure it isn't deleted out from under us +} + + +/** This useful function, fromHttpDate, comes from QNetworkHeadersPrivate + * in qnetworkrequest.cpp. Qt copyright and license apply. */ +static QDateTime QByteArrayToHttpDate(const QByteArray &value) +{ + // HTTP dates have three possible formats: + // RFC 1123/822 - ddd, dd MMM yyyy hh:mm:ss "GMT" + // RFC 850 - dddd, dd-MMM-yy hh:mm:ss "GMT" + // ANSI C's asctime - ddd MMM d hh:mm:ss yyyy + // We only handle them exactly. If they deviate, we bail out. + + int pos = value.indexOf(','); + QDateTime dt; + if (pos == -1) { + // no comma -> asctime(3) format + dt = QDateTime::fromString(QString::fromLatin1(value), Qt::TextDate); + } else { + // eat the weekday, the comma and the space following it + QString sansWeekday = QString::fromLatin1(value.constData() + pos + 2); + + QLocale c = QLocale::c(); + if (pos == 3) + // must be RFC 1123 date + dt = c.toDateTime(sansWeekday, QLatin1String("dd MMM yyyy hh:mm:ss 'GMT")); + else + // must be RFC 850 date + dt = c.toDateTime(sansWeekday, QLatin1String("dd-MMM-yy hh:mm:ss 'GMT'")); + } + + if (dt.isValid()) + dt.setTimeSpec(Qt::UTC); + return dt; +} + + +QDateTime +lastfm::ws::expires( QNetworkReply* reply ) +{ + return QByteArrayToHttpDate( reply->rawHeader( "Expires" ) ); +} + + +namespace lastfm +{ + namespace ws + { + QString SessionKey; + QString Username; + + /** we leave these unset as you can't use the webservices without them + * so lets make the programmer aware of it during testing by crashing */ + const char* SharedSecret; + const char* ApiKey; + + /** if this is found set to "" we conjure ourselves a suitable one */ + const char* UserAgent = 0; + } +} + + +QDebug operator<<( QDebug, lastfm::ws::Error ); diff --git a/thirdparty/liblastfm2/src/ws/ws.h b/thirdparty/liblastfm2/src/ws/ws.h new file mode 100644 index 000000000..b6f7cd922 --- /dev/null +++ b/thirdparty/liblastfm2/src/ws/ws.h @@ -0,0 +1,161 @@ +/* + Copyright 2009 Last.fm Ltd. + - Primarily authored by Max Howell, Jono Cole and Doug Mansell + + This file is part of liblastfm. + + liblastfm 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. + + liblastfm 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 liblastfm. If not, see . +*/ +#ifndef LASTFM_WS_H +#define LASTFM_WS_H + +#include +#include +#include +#include +#include + +#ifdef Q_CC_MSVC +// ms admits its lousy compiler doesn't care about throw declarations +#pragma warning( disable : 4290 ) +#endif + + +namespace lastfm +{ + /** if you don't set one, we create our own, our own is pretty good + * for instance, it auto detects proxy settings on windows and mac + * We take ownership of the NAM, do not delete it out from underneath us! + * So don't keep any other pointers to this around in case you accidently + * call delete on them :P */ + LASTFM_DLLEXPORT void setNetworkAccessManager( QNetworkAccessManager* nam ); + LASTFM_DLLEXPORT QNetworkAccessManager* nam(); + + namespace ws + { + /** both of these are provided when you register at http://last.fm/api */ + LASTFM_DLLEXPORT extern const char* SharedSecret; + LASTFM_DLLEXPORT extern const char* ApiKey; + + /** you need to set this for scrobbling to work (for now) + * Also the AuthenticatedUser class uses it */ + LASTFM_DLLEXPORT extern QString Username; + + /** Some webservices require authentication. See the following + * documentation: + * http://www.last.fm/api/authentication + * http://www.last.fm/api/desktopauth + * You have to authenticate and then assign to SessionKey, liblastfm does + * not do that for you. Also we do not store this. You should store this! + * You only need to authenticate once, and that key lasts forever! + */ + LASTFM_DLLEXPORT extern QString SessionKey; + + enum Error + { + NoError = 1, // because last.fm error numbers start at 2 + + /** numbers follow those at http://last.fm/api/ */ + InvalidService = 2, + InvalidMethod, + AuthenticationFailed, + InvalidFormat, + InvalidParameters, + InvalidResourceSpecified, + OperationFailed, + InvalidSessionKey, + InvalidApiKey, + ServiceOffline, + SubscribersOnly, + + Reserved13, + Reserved14, + Reserved15, + + /** Last.fm sucks. + * There may be an error in networkError(), or this may just be some + * internal error completing your request. + * Advise the user to try again in a _few_minutes_. + * For some cases, you may want to try again yourself, at this point + * in the API you will have to. Eventually we will discourage this and + * do it for you, as we don't want to strain Last.fm's servers + */ + TryAgainLater = 16, + + Reserved17, + Reserved18, + Reserved19, + + NotEnoughContent = 20, + NotEnoughMembers, + NotEnoughFans, + NotEnoughNeighbours, + + /** Last.fm fucked up, or something mangled the response on its way */ + MalformedResponse = 100, + + /** call QNetworkReply::error() as it's nothing to do with us */ + UnknownError + }; + + LASTFM_DLLEXPORT QString host(); + + /** the map needs a method entry, as per http://last.fm/api */ + LASTFM_DLLEXPORT QNetworkReply* get( QMap ); + /** generates api sig, includes api key, and posts, don't add the api + * key yourself as well--it'll break */ + LASTFM_DLLEXPORT QNetworkReply* post( QMap, bool sessionKey = true ); + + + class ParseError : public std::runtime_error + { + Error e; + public: + explicit ParseError(Error e) : std::runtime_error("lastfm::ws::Error"), e(e) + {} + Error enumValue() const { return e; } + }; + + /** Generally you don't use this, eg. if you called Artist::getInfo(), + * use the Artist::getInfo( QNetworkReply* ) function to get the + * results, you have to pass a QDomDocument because QDomElements stop + * existing when the parent DomDocument is deleted. + * + * The QByteArray is basically reply->readAll(), so all this function + * does is sanity check the response and throw if it is bad. + * + * Thus if you don't care about errors just do: reply->readAll() + * + * Not caring about errors is often fine with Qt as you just get null + * strings and that instead, and you can handle those as you go. + * + * The QByteArray is an XML document. You can parse it with QDom or + * use our much more convenient lastfm::XmlQuery. + */ + LASTFM_DLLEXPORT QByteArray parse( QNetworkReply* reply ) throw( ParseError ); + + /** returns the expiry date of this HTTP response */ + LASTFM_DLLEXPORT QDateTime expires( QNetworkReply* ); + } +} + + +inline QDebug operator<<( QDebug d, QNetworkReply::NetworkError e ) +{ + return d << lastfm::qMetaEnumString( e, "NetworkError" ); +} + +#define LASTFM_WS_HOSTNAME "ws.audioscrobbler.com" + +#endif diff --git a/thirdparty/liblastfm2/tests/TestTrack.h b/thirdparty/liblastfm2/tests/TestTrack.h new file mode 100644 index 000000000..c771a45e9 --- /dev/null +++ b/thirdparty/liblastfm2/tests/TestTrack.h @@ -0,0 +1,35 @@ +/* + This software is in the public domain, furnished "as is", without technical + support, and with no warranty, express or implied, as to its usefulness for + any purpose. +*/ +#include +#include +using lastfm::Track; + +class TestTrack : public QObject +{ + Q_OBJECT + + Track example() + { + lastfm::MutableTrack t; + t.setTitle( "Test Title" ); + t.setArtist( "Test Artist" ); + t.setAlbum( "Test Album" ); + return t; + } + +private slots: + void testClone() + { + Track original = example(); + Track copy = original; + + #define TEST( x ) QVERIFY( original.x == copy.x ) + TEST( title() ); + TEST( artist() ); + TEST( album() ); + #undef TEST + } +}; diff --git a/thirdparty/liblastfm2/tests/TestUrlBuilder.h b/thirdparty/liblastfm2/tests/TestUrlBuilder.h new file mode 100644 index 000000000..909d10e37 --- /dev/null +++ b/thirdparty/liblastfm2/tests/TestUrlBuilder.h @@ -0,0 +1,80 @@ +/* + This software is in the public domain, furnished "as is", without technical + support, and with no warranty, express or implied, as to its usefulness for + any purpose. +*/ +#include +#include +#include +#include +#include + +static inline int getResponseCode( const QUrl& url ) +{ + QNetworkAccessManager nam; + QNetworkReply* reply = nam.head( QNetworkRequest(url) ); + + QEventLoop loop; + loop.connect( reply, SIGNAL(finished()), SLOT(quit()) ); + loop.exec(); + + int const code = reply->attribute( QNetworkRequest::HttpStatusCodeAttribute ).toInt(); + + if (reply->error() != QNetworkReply::NoError) + qDebug() << url << lastfm::qMetaEnumString( reply->error(), "NetworkError" ) << code; + + return code; +} + + +class TestUrlBuilder : public QObject +{ + Q_OBJECT + +private slots: + void encode() /** @author */ + { + QFETCH( QString, input ); + QFETCH( QString, output ); + QCOMPARE( lastfm::UrlBuilder::encode( input ), output.toAscii() ); + } + + void encode_data() /** @author */ + { + QTest::addColumn("input"); + QTest::addColumn("output"); + + QTest::newRow( "ascii" ) << "Metallica" << "Metallica"; + QTest::newRow( "ascii alphanumeric" ) << "Apollo 440" << "Apollo+440"; + QTest::newRow( "ascii with symbols" ) << "some track [original version]" << "some+track+%5Boriginal+version%5D"; + QTest::newRow( "ascii with last.fm-special symbols" ) << "Survivalism [Revision #1]" << "Survivalism%2B%255BRevision%2B%25231%255D"; + } + + void no404() /** @author */ + { + QFETCH( QString, artist ); + QFETCH( QString, track ); + + QUrl url = lastfm::UrlBuilder( "music" ).slash( artist ).slash( "_" ).slash( track ).url(); + + QCOMPARE( getResponseCode( url ), 200 ); + } + + void no404_data() /** @author */ + { + QTest::addColumn("artist"); + QTest::addColumn("track"); + + #define NEW_ROW( x, y ) QTest::newRow( x " - " y ) << x << y; + NEW_ROW( "Air", "Radio #1" ); + NEW_ROW( "Pink Floyd", "Speak to Me / Breathe" ); + NEW_ROW( "Radiohead", "2 + 2 = 5" ); + NEW_ROW( "Above & Beyond", "World On Fire (Maor Levi Remix)" ); + #undef NEW_ROW + } + + void test404() /** @author */ + { + QCOMPARE( getResponseCode( QUrl("http://www.last.fm/404") ), 404 ); + } +}; diff --git a/thirdparty/liblastfm2/tests/main.cpp b/thirdparty/liblastfm2/tests/main.cpp new file mode 100644 index 000000000..5995bffa2 --- /dev/null +++ b/thirdparty/liblastfm2/tests/main.cpp @@ -0,0 +1,22 @@ +/* + This software is in the public domain, furnished "as is", without technical + support, and with no warranty, express or implied, as to its usefulness for + any purpose. +*/ +#include +#include +#include "TestTrack.h" +#include "TestUrlBuilder.h" + +int main( int argc, char** argv) +{ + QCoreApplication app( argc, argv ); + + #define TEST( Type ) { \ + Type o; \ + if (int r = QTest::qExec( &o, argc, argv ) != 0) return r; } + + TEST( TestTrack ); + TEST( TestUrlBuilder ); + return 0; +} diff --git a/thirdparty/liblastfm2/tests/tests.pro b/thirdparty/liblastfm2/tests/tests.pro new file mode 100644 index 000000000..da65f1cde --- /dev/null +++ b/thirdparty/liblastfm2/tests/tests.pro @@ -0,0 +1,4 @@ +QT = core testlib network xml +LIBS += -llastfm -L$$DESTDIR +SOURCES = main.cpp +HEADERS = TestTrack.h TestUrlBuilder.h \ No newline at end of file From 23d4a03acae53126cd46c0498ed42b4eb09e2b37 Mon Sep 17 00:00:00 2001 From: Christian Muehlhaeuser Date: Thu, 24 Mar 2011 23:54:28 +0000 Subject: [PATCH 113/329] * Prepare version 0.0.2. --- CMakeLists.txt | 2 +- admin/win/nsi/RELEASE_NOTES.txt | 2 +- admin/win/nsi/revision.txt | 2 +- admin/win/nsi/tomahawk.nsi | 2 +- 4 files changed, 4 insertions(+), 4 deletions(-) diff --git a/CMakeLists.txt b/CMakeLists.txt index 6552d24e2..d00a02eec 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -7,7 +7,7 @@ CMAKE_MINIMUM_REQUIRED( VERSION 2.8 ) SET( ORGANIZATION_NAME "Tomahawk" ) SET( ORGANIZATION_DOMAIN "tomahawk-player.org" ) SET( APPLICATION_NAME "Tomahawk" ) -SET( VERSION "0.0.1" ) +SET( VERSION "0.0.2" ) # set paths diff --git a/admin/win/nsi/RELEASE_NOTES.txt b/admin/win/nsi/RELEASE_NOTES.txt index fd3e8f620..adbeaae55 100755 --- a/admin/win/nsi/RELEASE_NOTES.txt +++ b/admin/win/nsi/RELEASE_NOTES.txt @@ -1 +1 @@ -TO DO \ No newline at end of file +See http://github.com/tomahawk-player/tomahawk/blob/stable/ChangeLog diff --git a/admin/win/nsi/revision.txt b/admin/win/nsi/revision.txt index bd753ccc4..90be1cdd8 100644 --- a/admin/win/nsi/revision.txt +++ b/admin/win/nsi/revision.txt @@ -1 +1 @@ -94 \ No newline at end of file +95 \ No newline at end of file diff --git a/admin/win/nsi/tomahawk.nsi b/admin/win/nsi/tomahawk.nsi index d2681ef8d..4bdb57fa4 100755 --- a/admin/win/nsi/tomahawk.nsi +++ b/admin/win/nsi/tomahawk.nsi @@ -35,7 +35,7 @@ !define VER_MAJOR "0" !define VER_MINOR "0" -!define VER_BUILD "1" +!define VER_BUILD "2" !define VERSION "${VER_MAJOR}.${VER_MINOR}.${VER_BUILD}" From 32ed0c748b754070da5ecec7aa54ec40bc9bd025 Mon Sep 17 00:00:00 2001 From: Christian Muehlhaeuser Date: Fri, 25 Mar 2011 00:08:28 +0000 Subject: [PATCH 114/329] * Updated sparkle templates. --- admin/mac/sparkle-beta.rss | 12 ++++++------ admin/mac/sparkle.rss | 8 ++++---- admin/win/nsi/revision.txt | 2 +- admin/win/sparklewin-beta.rss | 10 +++++----- admin/win/sparklewin.rss | 6 +++--- 5 files changed, 19 insertions(+), 19 deletions(-) diff --git a/admin/mac/sparkle-beta.rss b/admin/mac/sparkle-beta.rss index 0a38d7ee5..e49379f98 100755 --- a/admin/mac/sparkle-beta.rss +++ b/admin/mac/sparkle-beta.rss @@ -2,16 +2,16 @@ Tomahawk Player Changelog - http://download.tomahawk-player.org/sparkle - Most recent changes with links to updates. + http://www.gettomahawk.com + Tomahawk Player Beta en - Version 0.0.1 (Tomahawk Player - It Lives!) + Version 0.0.1 (Tomahawk Player Beta - It Lives?) - https://github.com/tomahawk-player/tomahawk/raw/master/ChangeLog + https://github.com/tomahawk-player/tomahawk/raw/stable/ChangeLog - Fri, 04 Mar 2011 16:05:15 -0500 - + Fri, 25 Mar 2011 00:00:01 +0100 + diff --git a/admin/mac/sparkle.rss b/admin/mac/sparkle.rss index 391b34a02..9fa00ed8e 100755 --- a/admin/mac/sparkle.rss +++ b/admin/mac/sparkle.rss @@ -2,15 +2,15 @@ Tomahawk Player Changelog - http://download.tomahawk-player.org/sparkle - Most recent changes with links to updates. + http://www.gettomahawk.com + Tomahawk Player en Version 0.0.1 (Tomahawk Player - It Lives!) - https://github.com/tomahawk-player/tomahawk/raw/master/ChangeLog + https://github.com/tomahawk-player/tomahawk/raw/0.0.1/ChangeLog - Fri, 04 Mar 2011 16:05:15 -0500 + Fri, 25 Mar 2011 00:00:01 +0100 diff --git a/admin/win/nsi/revision.txt b/admin/win/nsi/revision.txt index 90be1cdd8..56749c830 100644 --- a/admin/win/nsi/revision.txt +++ b/admin/win/nsi/revision.txt @@ -1 +1 @@ -95 \ No newline at end of file +96 \ No newline at end of file diff --git a/admin/win/sparklewin-beta.rss b/admin/win/sparklewin-beta.rss index 9d1f276cc..14d33d797 100644 --- a/admin/win/sparklewin-beta.rss +++ b/admin/win/sparklewin-beta.rss @@ -3,15 +3,15 @@ Tomahawk Player http://www.gettomahawk.com - Tomahawk Player + Tomahawk Player Beta en - Version 0.0.1 (Tomahawk Player - It Lives!) + Version 0.0.1 (Tomahawk Player Beta - It Lives?) - https://github.com/tomahawk-player/tomahawk/blob/master/ChangeLog + https://github.com/tomahawk-player/tomahawk/raw/stable/ChangeLog - Fri, 04 Mar 2011 16:05:15 -0500 - + Fri, 25 Mar 2011 00:00:01 +0100 + diff --git a/admin/win/sparklewin.rss b/admin/win/sparklewin.rss index cec8b11c6..aa9f8f4fa 100644 --- a/admin/win/sparklewin.rss +++ b/admin/win/sparklewin.rss @@ -8,10 +8,10 @@ Version 0.0.1 (Tomahawk Player - It Lives!) - https://github.com/tomahawk-player/tomahawk/blob/master/ChangeLog + https://github.com/tomahawk-player/tomahawk/raw/0.0.1/ChangeLog - Fri, 04 Mar 2011 16:05:15 -0500 - + Fri, 25 Mar 2011 00:00:01 +0100 + From 4f77de3bbc97fe88b324aecf1ef596c14c2495ab Mon Sep 17 00:00:00 2001 From: Christian Muehlhaeuser Date: Fri, 25 Mar 2011 07:08:34 +0100 Subject: [PATCH 115/329] * Changed domme's email address. --- AUTHORS | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/AUTHORS b/AUTHORS index c210243d3..c07550f06 100644 --- a/AUTHORS +++ b/AUTHORS @@ -5,7 +5,7 @@ Tomahawk is primarily authored by: Contributors include: * Leo Franchi -* Dominik Schmidt +* Dominik Schmidt * Jeff Mitchell * J Herskowitz * Alejandro Wainzinger From 811c7b8754bd60df9d80fef8371a99145976071c Mon Sep 17 00:00:00 2001 From: Jeff Mitchell Date: Thu, 31 Mar 2011 12:18:49 -0400 Subject: [PATCH 116/329] Make debug output a little more helpful --- src/sip/twitter/twitter.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/sip/twitter/twitter.cpp b/src/sip/twitter/twitter.cpp index 655dd832b..e3390608a 100644 --- a/src/sip/twitter/twitter.cpp +++ b/src/sip/twitter/twitter.cpp @@ -599,7 +599,7 @@ TwitterPlugin::makeConnection( const QString &screenName, const QHash< QString, qDebug() << Q_FUNC_INFO; if ( m_attemptedConnects.contains( screenName ) && m_attemptedConnects[screenName] ) { - qDebug() << "Already attempted to connect to this peer with no change in their status, not trying again for now"; + qDebug() << "Already attempted to connect to " << screenName << " with no change in their status, not trying again for now"; return; } if ( !peerData.contains( "host" ) || !peerData.contains( "port" ) || !peerData.contains( "pkey" ) || !peerData.contains( "node" ) ) From 85be5653e560e35e7aea8553af489df4014caa2d Mon Sep 17 00:00:00 2001 From: Jeff Mitchell Date: Thu, 31 Mar 2011 15:12:08 -0400 Subject: [PATCH 117/329] Add more debugging and remove the don't-try-to-reconnect behavior --- src/libtomahawk/network/servent.cpp | 3 +++ src/sip/twitter/twitter.cpp | 10 +--------- src/sip/twitter/twitter.h | 1 - 3 files changed, 4 insertions(+), 10 deletions(-) diff --git a/src/libtomahawk/network/servent.cpp b/src/libtomahawk/network/servent.cpp index 0c0d8285c..a602d176c 100644 --- a/src/libtomahawk/network/servent.cpp +++ b/src/libtomahawk/network/servent.cpp @@ -727,8 +727,11 @@ Servent::isIPWhitelisted( QHostAddress ip ) bool Servent::connectedToSession( const QString& session ) { + qDebug() << Q_FUNC_INFO; + qDebug() << "Checking against " << session; foreach( ControlConnection* cc, m_controlconnections ) { + qDebug() << "Checking session " << cc->id(); if( cc->id() == session ) return true; } diff --git a/src/sip/twitter/twitter.cpp b/src/sip/twitter/twitter.cpp index e3390608a..1a5f91163 100644 --- a/src/sip/twitter/twitter.cpp +++ b/src/sip/twitter/twitter.cpp @@ -178,7 +178,6 @@ TwitterPlugin::disconnectPlugin() delete m_twitterAuth.data(); m_cachedPeers.empty(); - m_attemptedConnects.empty(); m_isOnline = false; } @@ -571,7 +570,6 @@ TwitterPlugin::registerOffer( const QString &screenName, const QHash< QString, Q { m_cachedPeers[screenName] = QVariant::fromValue< QHash< QString, QVariant > >( _peerData ); TomahawkSettings::instance()->setTwitterCachedPeers( m_cachedPeers ); - m_attemptedConnects[screenName] = false; } if ( m_isOnline && _peerData.contains( "host" ) && _peerData.contains( "port" ) && _peerData.contains( "pkey" ) ) @@ -597,14 +595,9 @@ void TwitterPlugin::makeConnection( const QString &screenName, const QHash< QString, QVariant > &peerData ) { qDebug() << Q_FUNC_INFO; - if ( m_attemptedConnects.contains( screenName ) && m_attemptedConnects[screenName] ) - { - qDebug() << "Already attempted to connect to " << screenName << " with no change in their status, not trying again for now"; - return; - } if ( !peerData.contains( "host" ) || !peerData.contains( "port" ) || !peerData.contains( "pkey" ) || !peerData.contains( "node" ) ) { - qDebug() << "TwitterPlugin could not find host and/or port and/or pkey for peer " << screenName; + qDebug() << "TwitterPlugin could not find host and/or port and/or pkey and/or node for peer " << screenName; return; } QString friendlyName = QString( '@' + screenName ); @@ -614,7 +607,6 @@ TwitterPlugin::makeConnection( const QString &screenName, const QHash< QString, peerData["pkey"].toString(), friendlyName, peerData["node"].toString() ); - m_attemptedConnects[screenName] = true; } void diff --git a/src/sip/twitter/twitter.h b/src/sip/twitter/twitter.h index 4e8a98a31..c0da00d7f 100644 --- a/src/sip/twitter/twitter.h +++ b/src/sip/twitter/twitter.h @@ -108,7 +108,6 @@ private: qint64 m_cachedMentionsSinceId; qint64 m_cachedDirectMessagesSinceId; QHash< QString, QVariant > m_cachedPeers; - QHash< QString, bool > m_attemptedConnects; QSet m_keyCache; bool m_finishedFriends; bool m_finishedMentions; From 48f69c3180f735cae6ffda778bfb3eb536bbf7dd Mon Sep 17 00:00:00 2001 From: Christian Muehlhaeuser Date: Fri, 1 Apr 2011 11:52:15 +0200 Subject: [PATCH 118/329] * Disabled launching Tomahawk from within the Windows installer. It caused starting Tomahawk with admin privileges. --- admin/win/nsi/tomahawk.nsi | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/admin/win/nsi/tomahawk.nsi b/admin/win/nsi/tomahawk.nsi index c2d2a9bd1..e9ba96112 100644 --- a/admin/win/nsi/tomahawk.nsi +++ b/admin/win/nsi/tomahawk.nsi @@ -9,7 +9,7 @@ !define OPTION_SECTION_SC_DESKTOP !define OPTION_SECTION_SC_QUICK_LAUNCH !define OPTION_FINISHPAGE -!define OPTION_FINISHPAGE_LAUNCHER +;!define OPTION_FINISHPAGE_LAUNCHER !define OPTION_FINISHPAGE_RELEASE_NOTES ;----------------------------------------------------------------------------- From 70ddc32e4204354525d0e516a8f7cf013c2484cc Mon Sep 17 00:00:00 2001 From: Christian Muehlhaeuser Date: Sat, 2 Apr 2011 10:24:20 +0200 Subject: [PATCH 119/329] * Only keep track of the 50 most recent tracks in WelcomeWidget. Don't auto-resolve playback-logs any longer. --- .../database/databasecommand_logplayback.cpp | 3 ++- src/libtomahawk/playlist/playlistmodel.cpp | 10 +++++--- src/libtomahawk/playlist/playlistmodel.h | 1 + src/libtomahawk/playlist/trackmodel.cpp | 14 +++++++++++ src/libtomahawk/playlist/trackmodel.h | 2 ++ src/libtomahawk/widgets/welcomewidget.cpp | 24 +++++++++++++++++-- src/libtomahawk/widgets/welcomewidget.h | 3 +++ 7 files changed, 51 insertions(+), 6 deletions(-) diff --git a/src/libtomahawk/database/databasecommand_logplayback.cpp b/src/libtomahawk/database/databasecommand_logplayback.cpp index 9254ea979..6443b2216 100644 --- a/src/libtomahawk/database/databasecommand_logplayback.cpp +++ b/src/libtomahawk/database/databasecommand_logplayback.cpp @@ -44,7 +44,8 @@ DatabaseCommand_LogPlayback::postCommitHook() connect( this, SIGNAL( trackPlayed( Tomahawk::query_ptr ) ), source().data(), SLOT( onPlaybackFinished( Tomahawk::query_ptr ) ), Qt::QueuedConnection ); - Tomahawk::query_ptr q = Tomahawk::Query::get( m_artist, m_track, QString(), uuid() ); + // do not auto resolve this track + Tomahawk::query_ptr q = Tomahawk::Query::get( m_artist, m_track, QString() ); if ( m_action == Finished ) { diff --git a/src/libtomahawk/playlist/playlistmodel.cpp b/src/libtomahawk/playlist/playlistmodel.cpp index 8e4d24ae6..2ab23bf51 100644 --- a/src/libtomahawk/playlist/playlistmodel.cpp +++ b/src/libtomahawk/playlist/playlistmodel.cpp @@ -396,12 +396,16 @@ PlaylistModel::playlistEntries() const } +void +PlaylistModel::remove( unsigned int row, bool moreToCome ) +{ + removeIndex( index( row, 0, QModelIndex() ), moreToCome ); +} + + void PlaylistModel::removeIndex( const QModelIndex& index, bool moreToCome ) { - if ( isReadOnly() ) - return; - TrackModel::removeIndex( index ); if ( !moreToCome && !m_playlist.isNull() ) diff --git a/src/libtomahawk/playlist/playlistmodel.h b/src/libtomahawk/playlist/playlistmodel.h index e69bf59e1..bc743a18a 100644 --- a/src/libtomahawk/playlist/playlistmodel.h +++ b/src/libtomahawk/playlist/playlistmodel.h @@ -62,6 +62,7 @@ public: void insert( unsigned int row, const Tomahawk::query_ptr& query ); + void remove( unsigned int row, bool moreToCome = false ); virtual void removeIndex( const QModelIndex& index, bool moreToCome = false ); signals: diff --git a/src/libtomahawk/playlist/trackmodel.cpp b/src/libtomahawk/playlist/trackmodel.cpp index eceadd401..e3fd2083a 100644 --- a/src/libtomahawk/playlist/trackmodel.cpp +++ b/src/libtomahawk/playlist/trackmodel.cpp @@ -26,6 +26,7 @@ #include "utils/tomahawkutils.h" #include "album.h" +#include "pipeline.h" using namespace Tomahawk; @@ -370,3 +371,16 @@ TrackModel::onPlaybackStopped() oldEntry->setIsPlaying( false ); } } + + +void +TrackModel::ensureResolved() +{ + for( int i = 0; i < rowCount( QModelIndex() ); i++ ) + { + query_ptr query = itemFromIndex( index( i, 0, QModelIndex() ) )->query(); + + if ( !query->numResults() ) + Pipeline::instance()->resolve( query ); + } +} diff --git a/src/libtomahawk/playlist/trackmodel.h b/src/libtomahawk/playlist/trackmodel.h index d9cec03d8..b3a5e0c24 100644 --- a/src/libtomahawk/playlist/trackmodel.h +++ b/src/libtomahawk/playlist/trackmodel.h @@ -76,6 +76,8 @@ public: virtual PlaylistInterface::RepeatMode repeatMode() const { return PlaylistInterface::NoRepeat; } virtual bool shuffled() const { return false; } + virtual void ensureResolved(); + virtual void append( const Tomahawk::query_ptr& query ) = 0; PlItem* itemFromIndex( const QModelIndex& index ) const; diff --git a/src/libtomahawk/widgets/welcomewidget.cpp b/src/libtomahawk/widgets/welcomewidget.cpp index cc4460895..d7ad62938 100644 --- a/src/libtomahawk/widgets/welcomewidget.cpp +++ b/src/libtomahawk/widgets/welcomewidget.cpp @@ -31,7 +31,9 @@ #include -#define FILTER_TIMEOUT 280 +#define HISTORY_TRACK_ITEMS 50 +#define HISTORY_PLAYLIST_ITEMS 10 +#define HISTORY_RESOLVING_TIMEOUT 2500 WelcomeWidget::WelcomeWidget( QWidget* parent ) @@ -46,7 +48,10 @@ WelcomeWidget::WelcomeWidget( QWidget* parent ) m_tracksModel = new PlaylistModel( ui->tracksView ); ui->tracksView->setModel( m_tracksModel ); - m_tracksModel->loadHistory( Tomahawk::source_ptr() ); + m_tracksModel->loadHistory( Tomahawk::source_ptr(), HISTORY_TRACK_ITEMS ); + + m_timer = new QTimer( this ); + connect( m_timer, SIGNAL( timeout() ), SLOT( checkQueries() ) ); connect( SourceList::instance(), SIGNAL( sourceAdded( Tomahawk::source_ptr ) ), SLOT( onSourceAdded( Tomahawk::source_ptr ) ) ); @@ -96,10 +101,25 @@ WelcomeWidget::onSourceAdded( const Tomahawk::source_ptr& source ) } +void +WelcomeWidget::checkQueries() +{ + m_timer->stop(); + m_tracksModel->ensureResolved(); +} + + void WelcomeWidget::onPlaybackFinished( const Tomahawk::query_ptr& query ) { m_tracksModel->insert( 0, query ); + + if ( m_tracksModel->trackCount() > HISTORY_TRACK_ITEMS ) + m_tracksModel->remove( HISTORY_TRACK_ITEMS ); + + if ( m_timer->isActive() ) + m_timer->stop(); + m_timer->start( HISTORY_RESOLVING_TIMEOUT ); } diff --git a/src/libtomahawk/widgets/welcomewidget.h b/src/libtomahawk/widgets/welcomewidget.h index 9ec4e249c..036e3d5ca 100644 --- a/src/libtomahawk/widgets/welcomewidget.h +++ b/src/libtomahawk/widgets/welcomewidget.h @@ -124,10 +124,13 @@ private slots: void onPlaylistActivated( QListWidgetItem* item ); void onPlaybackFinished( const Tomahawk::query_ptr& query ); + void checkQueries(); + private: Ui::WelcomeWidget *ui; PlaylistModel* m_tracksModel; + QTimer* m_timer; }; #endif // WELCOMEWIDGET_H From 5cde25cefed51a6a5cec13303617724764dfbca3 Mon Sep 17 00:00:00 2001 From: Christian Muehlhaeuser Date: Sat, 2 Apr 2011 10:24:48 +0200 Subject: [PATCH 120/329] * Properly thread QtScriptResolver. --- src/resolvers/qtscriptresolver.cpp | 126 +++++++++++++++++++++-------- src/resolvers/qtscriptresolver.h | 39 +++++++-- 2 files changed, 125 insertions(+), 40 deletions(-) diff --git a/src/resolvers/qtscriptresolver.cpp b/src/resolvers/qtscriptresolver.cpp index d4c0cc5f5..a19147b14 100644 --- a/src/resolvers/qtscriptresolver.cpp +++ b/src/resolvers/qtscriptresolver.cpp @@ -27,19 +27,86 @@ QtScriptResolver::QtScriptResolver( const QString& scriptPath ) : Tomahawk::ExternalResolver( scriptPath ) - , m_engine( new ScriptEngine( this ) ) - , m_thread( new QThread( this ) ) , m_ready( false ) , m_stopped( false ) { qDebug() << Q_FUNC_INFO << scriptPath; + m_thread = new ScriptThread( scriptPath, this ); + connect( m_thread, SIGNAL( engineFound( QString, unsigned int, unsigned int, unsigned int ) ), + SLOT( onEngineFound( QString, unsigned int, unsigned int, unsigned int ) ) ); + m_thread->start(); - QFile scriptFile( scriptPath ); + connect( this, SIGNAL( destroyed( QObject* ) ), m_thread, SLOT( deleteLater() ) ); +} + + +QtScriptResolver::~QtScriptResolver() +{ + Tomahawk::Pipeline::instance()->removeResolver( this ); + delete m_thread; +} + + +void +QtScriptResolver::resolve( const Tomahawk::query_ptr& query ) +{ + m_thread->resolve( query ); +} + + +void +QtScriptResolver::onEngineFound( const QString& name, unsigned int weight, unsigned int timeout, unsigned int preference ) +{ + m_name = name; + m_weight = weight; + m_timeout = timeout; + m_preference = preference; + + qDebug() << "QTSCRIPT" << filePath() << "READY," << endl + << "name" << m_name << endl + << "weight" << m_weight << endl + << "timeout" << m_timeout << endl + << "preference" << m_preference; + + m_ready = true; + Tomahawk::Pipeline::instance()->addResolver( this ); +} + + +ScriptThread::ScriptThread( const QString& scriptPath, QtScriptResolver* parent ) + : QThread() + , m_parent( parent ) + , m_scriptPath( scriptPath ) +{ + moveToThread( this ); +} + + +void +ScriptThread::resolve( const Tomahawk::query_ptr& query ) +{ + m_engine->resolve( query ); +} + + +void +ScriptThread::run() +{ + QTimer::singleShot( 0, this, SLOT( initEngine() ) ); + exec(); +} + + +void +ScriptThread::initEngine() +{ + m_engine = new ScriptEngine( m_parent, this ); + QFile scriptFile( m_scriptPath ); if ( !scriptFile.open( QIODevice::ReadOnly ) ) { - qDebug() << Q_FUNC_INFO << "Failed loading JavaScript resolver:" << scriptPath; + qDebug() << Q_FUNC_INFO << "Failed loading JavaScript resolver:" << m_scriptPath; deleteLater(); return; } @@ -48,43 +115,32 @@ QtScriptResolver::QtScriptResolver( const QString& scriptPath ) m_engine->mainFrame()->evaluateJavaScript( scriptFile.readAll() ); scriptFile.close(); + QString name; + unsigned int weight, preference, timeout; QVariantMap m = m_engine->mainFrame()->evaluateJavaScript( "getSettings();" ).toMap(); - m_name = m.value( "name" ).toString(); - m_weight = m.value( "weight", 0 ).toUInt(); - m_timeout = m.value( "timeout", 25 ).toUInt() * 1000; - m_preference = m.value( "preference", 0 ).toUInt(); + name = m.value( "name" ).toString(); + weight = m.value( "weight", 0 ).toUInt(); + timeout = m.value( "timeout", 25 ).toUInt() * 1000; + preference = m.value( "preference", 0 ).toUInt(); - qDebug() << "QTSCRIPT" << filePath() << "READY," << endl - << "name" << m_name << endl - << "weight" << m_weight << endl - << "timeout" << m_timeout << endl - << "preference" << m_preference; - - m_engine->moveToThread( m_thread ); - m_ready = true; - Tomahawk::Pipeline::instance()->addResolver( this ); - - connect( this, SIGNAL( destroyed( QObject* ) ), m_thread, SLOT( deleteLater() ) ); -} - - -QtScriptResolver::~QtScriptResolver() -{ - Tomahawk::Pipeline::instance()->removeResolver( this ); - delete m_engine; -} - - -void -QtScriptResolver::resolve( const Tomahawk::query_ptr& query ) -{ - QMetaObject::invokeMethod( m_engine, "resolve", Qt::QueuedConnection, Q_ARG( Tomahawk::query_ptr, query ) ); + qDebug() << Q_FUNC_INFO << name << weight << timeout << preference; + emit engineFound( name, weight, timeout, preference ); } void ScriptEngine::resolve( const Tomahawk::query_ptr& query ) { + if ( QThread::currentThread() != thread() ) + { +// qDebug() << "Reinvoking in correct thread:" << Q_FUNC_INFO; + QMetaObject::invokeMethod( this, "resolve", + Qt::QueuedConnection, + Q_ARG(Tomahawk::query_ptr, query) + ); + return; + } + qDebug() << Q_FUNC_INFO << query->toString(); QString eval = QString( "resolve( '%1', '%2', '%3', '%4' );" ) .arg( query->id().replace( "'", "\\'" ) ) @@ -113,9 +169,9 @@ ScriptEngine::resolve( const Tomahawk::query_ptr& query ) rp->setBitrate( m.value( "bitrate" ).toUInt() ); rp->setUrl( m.value( "url" ).toString() ); rp->setSize( m.value( "size" ).toUInt() ); - rp->setScore( m.value( "score" ).toFloat() * ( (float)m_parent->weight() / 100.0 ) ); + rp->setScore( m.value( "score" ).toFloat() * ( (float)m_resolver->weight() / 100.0 ) ); rp->setRID( uuid() ); - rp->setFriendlySource( m_parent->name() ); + rp->setFriendlySource( m_resolver->name() ); if ( m.contains( "year" ) ) { diff --git a/src/resolvers/qtscriptresolver.h b/src/resolvers/qtscriptresolver.h index 05b55cc71..7c69fe103 100644 --- a/src/resolvers/qtscriptresolver.h +++ b/src/resolvers/qtscriptresolver.h @@ -26,9 +26,11 @@ #include #include #include +#include #include #include +class ScriptThread; class QtScriptResolver; class ScriptEngine : public QWebPage @@ -36,9 +38,10 @@ class ScriptEngine : public QWebPage Q_OBJECT public: - explicit ScriptEngine( QtScriptResolver* parent ) + explicit ScriptEngine( QtScriptResolver* resolver, ScriptThread* parent ) : QWebPage( (QObject*)parent ) , m_parent( parent ) + , m_resolver( resolver ) {} public slots: @@ -54,9 +57,35 @@ protected: { qDebug() << "JAVASCRIPT ERROR:" << message << lineNumber << sourceID; } private: - QtScriptResolver* m_parent; + ScriptThread* m_parent; + QtScriptResolver* m_resolver; }; + +class ScriptThread : public QThread +{ +Q_OBJECT + +public: + ScriptThread( const QString& scriptPath, QtScriptResolver* parent ); + + void run(); + + virtual void resolve( const Tomahawk::query_ptr& query ); + +signals: + void engineFound( const QString& name, unsigned int weight, unsigned int timeout, unsigned int preference ); + +private slots: + void initEngine(); + +private: + ScriptEngine* m_engine; + QtScriptResolver* m_parent; + QString m_scriptPath; +}; + + class QtScriptResolver : public Tomahawk::ExternalResolver { Q_OBJECT @@ -76,12 +105,12 @@ public slots: signals: void finished(); - + private slots: + void onEngineFound( const QString& name, unsigned int weight, unsigned int timeout, unsigned int preference ); private: - ScriptEngine* m_engine; - QThread* m_thread; + ScriptThread* m_thread; QString m_name; unsigned int m_weight, m_preference, m_timeout; From 727d8c83efc073daab1c7a89383a3fa465524162 Mon Sep 17 00:00:00 2001 From: Christian Muehlhaeuser Date: Sat, 2 Apr 2011 11:39:49 +0200 Subject: [PATCH 121/329] * Updated Changelog. --- ChangeLog | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/ChangeLog b/ChangeLog index c3a791448..b25481516 100644 --- a/ChangeLog +++ b/ChangeLog @@ -1,7 +1,9 @@ Version 0.0.3: - * Fix crashes in Twitter authentication. For reals now. + * Fix crashes in Twitter authentication. * Properly honor the chosen port number if a static host and port are marked as preferred. + * Don't automatically try to resolve all incoming playback logs. This + speeds up importing sources a lot. Version 0.0.2: * Don't reconnect to Jabber if the settings dialog is closed successfully From a080323e2ef677326448fda2577a236ea6e3e512 Mon Sep 17 00:00:00 2001 From: Leo Franchi Date: Sun, 3 Apr 2011 00:14:24 -0400 Subject: [PATCH 122/329] switch to using KDSingleApplicationGuard instead of QtUniqueApp as it's broken on windows. KDSingleApplicationGuard is from KDTools and this is the GPL-licensed version. Conflicts: src/main.cpp --- include/tomahawk/tomahawkapp.h | 5 +- src/headlesscheck.h | 8 +- src/libtomahawk/CMakeLists.txt | 12 +- .../kdlockedsharedmemorypointer.cpp | 475 +++++++++++++ .../kdlockedsharedmemorypointer.h | 115 ++++ .../kdsharedmemorylocker.cpp | 40 ++ .../kdsharedmemorylocker.h | 36 + .../kdsingleapplicationguard.cpp | 622 ++++++++++++++++++ .../kdsingleapplicationguard.h | 74 +++ .../kdtoolsglobal.cpp | 32 + .../kdsingleapplicationguard/kdtoolsglobal.h | 113 ++++ .../kdsingleapplicationguard/license-gpl | 349 ++++++++++ .../kdsingleapplicationguard/pimpl_ptr.cpp | 203 ++++++ .../kdsingleapplicationguard/pimpl_ptr.h | 44 ++ src/libtomahawk/qtsingleapp/qtlocalpeer.cpp | 199 ------ src/libtomahawk/qtsingleapp/qtlocalpeer.h | 72 -- src/libtomahawk/qtsingleapp/qtlockedfile.cpp | 192 ------ src/libtomahawk/qtsingleapp/qtlockedfile.h | 96 --- .../qtsingleapp/qtlockedfile_unix.cpp | 114 ---- .../qtsingleapp/qtlockedfile_win.cpp | 208 ------ .../qtsingleapp/qtsingleapplication.cpp | 344 ---------- .../qtsingleapp/qtsingleapplication.h | 84 --- .../qtsingleapp/qtsinglecoreapplication.cpp | 148 ----- .../qtsingleapp/qtsinglecoreapplication.h | 66 -- src/main.cpp | 16 +- src/tomahawkapp.cpp | 18 +- thirdparty/jreen | 2 +- 27 files changed, 2133 insertions(+), 1554 deletions(-) create mode 100644 src/libtomahawk/kdsingleapplicationguard/kdlockedsharedmemorypointer.cpp create mode 100644 src/libtomahawk/kdsingleapplicationguard/kdlockedsharedmemorypointer.h create mode 100644 src/libtomahawk/kdsingleapplicationguard/kdsharedmemorylocker.cpp create mode 100644 src/libtomahawk/kdsingleapplicationguard/kdsharedmemorylocker.h create mode 100644 src/libtomahawk/kdsingleapplicationguard/kdsingleapplicationguard.cpp create mode 100644 src/libtomahawk/kdsingleapplicationguard/kdsingleapplicationguard.h create mode 100644 src/libtomahawk/kdsingleapplicationguard/kdtoolsglobal.cpp create mode 100644 src/libtomahawk/kdsingleapplicationguard/kdtoolsglobal.h create mode 100644 src/libtomahawk/kdsingleapplicationguard/license-gpl create mode 100644 src/libtomahawk/kdsingleapplicationguard/pimpl_ptr.cpp create mode 100644 src/libtomahawk/kdsingleapplicationguard/pimpl_ptr.h delete mode 100644 src/libtomahawk/qtsingleapp/qtlocalpeer.cpp delete mode 100644 src/libtomahawk/qtsingleapp/qtlocalpeer.h delete mode 100644 src/libtomahawk/qtsingleapp/qtlockedfile.cpp delete mode 100644 src/libtomahawk/qtsingleapp/qtlockedfile.h delete mode 100644 src/libtomahawk/qtsingleapp/qtlockedfile_unix.cpp delete mode 100644 src/libtomahawk/qtsingleapp/qtlockedfile_win.cpp delete mode 100644 src/libtomahawk/qtsingleapp/qtsingleapplication.cpp delete mode 100644 src/libtomahawk/qtsingleapp/qtsingleapplication.h delete mode 100644 src/libtomahawk/qtsingleapp/qtsinglecoreapplication.cpp delete mode 100644 src/libtomahawk/qtsingleapp/qtsinglecoreapplication.h diff --git a/include/tomahawk/tomahawkapp.h b/include/tomahawk/tomahawkapp.h index bf83f6e86..7775a6528 100644 --- a/include/tomahawk/tomahawkapp.h +++ b/include/tomahawk/tomahawkapp.h @@ -40,6 +40,7 @@ #include "network/servent.h" #include "utils/tomahawkutils.h" +#include "kdsingleapplicationguard/kdsingleapplicationguard.h" class AudioEngine; class Database; @@ -98,9 +99,11 @@ public: // because QApplication::arguments() is expensive bool scrubFriendlyName() const { return m_scrubFriendlyName; } +public slots: + void instanceStarted( KDSingleApplicationGuard::Instance ); + private slots: void setupSIP(); - void messageReceived( const QString& ); private: void initLocalCollection(); diff --git a/src/headlesscheck.h b/src/headlesscheck.h index 189f1127e..f82fcea33 100644 --- a/src/headlesscheck.h +++ b/src/headlesscheck.h @@ -21,14 +21,14 @@ #ifdef ENABLE_HEADLESS -#define TOMAHAWK_APPLICATION QtSingleCoreApplication +#define TOMAHAWK_APPLICATION QCoreApplication #define TOMAHAWK_HEADLESS -#include "qtsingleapp/qtsingleapplication.h" +#include > #else -#define TOMAHAWK_APPLICATION QtSingleApplication -#include "qtsingleapp/qtsingleapplication.h" +#define TOMAHAWK_APPLICATION QApplication +#include #include "tomahawkwindow.h" #endif diff --git a/src/libtomahawk/CMakeLists.txt b/src/libtomahawk/CMakeLists.txt index f45c391a3..e398e89a7 100644 --- a/src/libtomahawk/CMakeLists.txt +++ b/src/libtomahawk/CMakeLists.txt @@ -145,8 +145,10 @@ set( libSources widgets/overlaywidget.cpp widgets/infowidgets/sourceinfowidget.cpp - qtsingleapp/qtlocalpeer.cpp - qtsingleapp/qtsingleapplication.cpp + kdsingleapplicationguard/kdsingleapplicationguard.cpp + kdsingleapplicationguard/kdsharedmemorylocker.cpp + kdsingleapplicationguard/kdtoolsglobal.cpp + kdsingleapplicationguard/kdlockedsharedmemorypointer.cpp ) set( libHeaders @@ -287,8 +289,10 @@ set( libHeaders widgets/overlaywidget.h widgets/infowidgets/sourceinfowidget.h - qtsingleapp/qtlocalpeer.h - qtsingleapp/qtsingleapplication.h + kdsingleapplicationguard/kdsingleapplicationguard.h + kdsingleapplicationguard/kdsharedmemorylocker.h + kdsingleapplicationguard/kdtoolsglobal.h + kdsingleapplicationguard/kdlockedsharedmemorypointer.h ) set( libHeaders_NoMOC diff --git a/src/libtomahawk/kdsingleapplicationguard/kdlockedsharedmemorypointer.cpp b/src/libtomahawk/kdsingleapplicationguard/kdlockedsharedmemorypointer.cpp new file mode 100644 index 000000000..e1fe10a4d --- /dev/null +++ b/src/libtomahawk/kdsingleapplicationguard/kdlockedsharedmemorypointer.cpp @@ -0,0 +1,475 @@ +#include "kdlockedsharedmemorypointer.h" + +#if QT_VERSION >= 0x040400 || defined( DOXYGEN_RUN ) +#ifndef QT_NO_SHAREDMEMORY + +namespace kdtools +{ +} +using namespace kdtools; + +KDLockedSharedMemoryPointerBase::KDLockedSharedMemoryPointerBase( QSharedMemory * m ) + : locker( m ), + mem( m ) +{ + +} + +KDLockedSharedMemoryPointerBase::KDLockedSharedMemoryPointerBase( QSharedMemory & m ) + : locker( &m ), + mem( &m ) +{ + +} + +KDLockedSharedMemoryPointerBase::~KDLockedSharedMemoryPointerBase() {} + +void * KDLockedSharedMemoryPointerBase::get() { + return mem ? mem->data() : 0 ; +} + +const void * KDLockedSharedMemoryPointerBase::get() const { + return mem ? mem->data() : 0 ; +} + +size_t KDLockedSharedMemoryPointerBase::byteSize() const { + return mem->size(); +} + +/*! + \class KDLockedSharedMemoryPointer + \ingroup core raii smartptr + \brief Locking pointer for Qt shared memory segments + \since_c 2.1 + + (The exception safety of this class has not been evaluated yet.) + + KDLockedSharedMemoryPointer is a smart immutable pointer, which gives convenient and safe access to a QSharedMemory data segment. + The content of a KDLockedSharedMemoryPointer cannot be changed during it's lifetime. + + You can use this class like a normal pointer to the shared memory segment and be sure it's locked while accessing it. + \note You can only put simple types/structs/classes into it. structs and classes shall not contain any other pointers. See the + documentation of QSharedMemory for details. +*/ + +/*! + \fn KDLockedSharedMemoryPointer::KDLockedSharedMemoryPointer( QSharedMemory * mem ) + + Constructor. Constructs a KDLockedSharedMemory pointer which points to the data segment of \a mem. + The constructor locks \a mem. If the memory segment is already locked by another process, this constructor + blocks until the lock is released. + + \post data() == mem->data() and the memory segment has been locked +*/ + +/*! + \fn KDLockedSharedMemoryPointer::KDLockedSharedMemoryPointer( QSharedMemory & mem ) + + \overload + + \post data() == mem.data() and the memory segment has been locked +*/ + +/*! + \fn KDLockedSharedMemoryPointer::~KDLockedSharedMemoryPointer() + + Destructor. Unlocks the shared memory segment. + + \post The shared memory segment has been unlocked +*/ + +/*! + \fn T * KDLockedSharedMemoryPointer::get() + + \returns a pointer to the contained object. +*/ + +/*! + \fn const T * KDLockedSharedMemoryPointer::get() const + + \returns a const pointer to the contained object + \overload +*/ + +/*! + \fn T * KDLockedSharedMemoryPointer::data() + + Equivalent to get(), provided for consistency with Qt naming conventions. +*/ + +/*! + \fn const T * KDLockedSharedMemoryPointer::data() const + + \overload +*/ + +/*! + \fn T & KDLockedSharedMemoryPointer::operator*() + + Dereference operator. Returns \link get() *get()\endlink. +*/ + +/*! + \fn const T & KDLockedSharedMemoryPointer::operator*() const + + Dereference operator. Returns \link get() *get()\endlink. + \overload +*/ + +/*! + \fn T * KDLockedSharedMemoryPointer::operator->() + + Member-by-pointer operator. Returns get(). +*/ + +/*! + \fn const T * KDLockedSharedMemoryPointer::operator->() const + + Member-by-pointer operator. Returns get(). + \overload +*/ + +/*! + \class KDLockedSharedMemoryArray + \ingroup core raii smartptr + \brief Locking array pointer to Qt shared memory segments + \since_c 2.1 + + (The exception safety of this class has not been evaluated yet.) + + KDLockedSharedMemoryArray is a smart immutable pointer, which gives convenient and safe access to array data stored in a QSharedMemory + data segment. + The content of a KDLockedSharedMemoryArray cannot be changed during it's lifetime. + + You can use this class like a normal pointer to the shared memory segment and be sure it's locked while accessing it. + \note You can only put arrays of simple types/structs/classes into it. structs and classes shall not contain any other pointers. See the + documentation of QSharedMemory for details. + + \sa KDLockedSharedMemoryPointer +*/ + +/*! + \fn KDLockedSharedMemoryArray::KDLockedSharedMemoryArray( QSharedMemory* mem ) + Constructor. Constructs a KDLockedSharedMemoryArray which points to the data segment of \a mem. The constructor locks \a mem. If the memory + segment is already locked by another process, this constructor blocks until the lock is release. + + \post get() == mem->data() and the memory segment has been locked +*/ + +/*! + \fn KDLockedSharedMemoryArray::KDLockedSharedMemoryArray( QSharedMemory& mem ) + \overload + + \post get() == mem->data() and the memory segment has been locked +*/ + + +/*! + \typedef KDLockedSharedMemoryArray::size_type + Typedef for std::size_t. Provided for STL compatibility. +*/ + +/*! + \typedef KDLockedSharedMemoryArray::difference_type + Typedef for std::ptrdiff_t. Provided for STL compatibility. +*/ + +/*! + \typedef KDLockedSharedMemoryArray::iterator + Typedef for T*. Provided for STL compatibility. + \since_t 2.2 +*/ + +/*! + \typedef KDLockedSharedMemoryArray::const_iterator + Typedef for const T*. Provided for STL compatibility. + \since_t 2.2 +*/ + +/*! + \typedef KDLockedSharedMemoryArray::reverse_iterator + Typedef for std::reverse_iterator< \link KDLockedSharedMemoryArray::iterator iterator\endlink >. Provided for STL compatibility. + \since_t 2.2 +*/ + +/*! + \typedef KDLockedSharedMemoryArray::const_reverse_iterator + Typedef for std::reverse_iterator< \link KDLockedSharedMemoryArray::const_iterator const_iterator\endlink >. Provided for STL compatibility. + \since_t 2.2 +*/ + +/*! + \fn KDLockedSharedMemoryArray::iterator KDLockedSharedMemoryArray::begin() + Returns an \link KDLockedSharedMemoryArray::iterator iterator\endlink pointing to the first item of the array. + \since_f 2.2 +*/ + +/*! + \fn KDLockedSharedMemoryArray::const_iterator KDLockedSharedMemoryArray::begin() const + \overload + \since_f 2.2 +*/ + +/*! + \fn KDLockedSharedMemoryArray::iterator KDLockedSharedMemoryArray::end() + Returns an \link KDLockedSharedMemoryArray::iterator iterator\endlink pointing to the item after the last item of the array. + \since_f 2.2 +*/ + +/*! + \fn KDLockedSharedMemoryArray::const_iterator KDLockedSharedMemoryArray::end() const + \overload + \since_f 2.2 +*/ + +/*! + \fn KDLockedSharedMemoryArray::reverse_iterator KDLockedSharedMemoryArray::rbegin() + Returns an \link KDLockedSharedMemoryArray::reverse_iterator reverse_iterator\endlink pointing to the item after the last item of the array. + \since_f 2.2 +*/ + +/*! + \fn KDLockedSharedMemoryArray::const_reverse_iterator KDLockedSharedMemoryArray::rbegin() const + \overload + \since_f 2.2 +*/ + +/*! + \fn KDLockedSharedMemoryArray::reverse_iterator KDLockedSharedMemoryArray::rend() + Returns an \link KDLockedSharedMemoryArray::reverse_iterator reverse_iterator\endlink pointing to the first item of the array. + \since_f 2.2 +*/ + +/*! + \fn KDLockedSharedMemoryArray::const_reverse_iterator KDLockedSharedMemoryArray::rend() const + \overload + \since_f 2.2 +*/ + +/*! + \fn KDLockedSharedMemoryArray::size_type KDLockedSharedMemoryArray::size() const + Returns the size of this array. The size is calculated from the storage size of T and + the size of the shared memory segment. + \since_f 2.2 +*/ + +/*! + \fn T& KDLockedSharedMemoryArray::operator[]( difference_type n ) + Array access operator. Returns a reference to the item at index position \a n. +*/ + +/*! + \fn const T& KDLockedSharedMemoryArray::operator[]( difference_type n ) const + \overload +*/ + +/*! + \fn T& KDLockedSharedMemoryArray::front() + Returns a reference to the first item in the array. This is the same as operator[](0). +*/ + +/*! + \fn const T& KDLockedSharedMemoryArray::front() const + \overload +*/ + +/*! + \fn T& KDLockedSharedMemoryArray::back() + Returns a reference to the last item in the array. This is the same as operator[](size()-1). + \since_f 2.2 +*/ + +/*! + \fn const T& KDLockedSharedMemoryArray::back() const + \overload + \since_f 2.2 +*/ + + +#ifdef eKDTOOLSCORE_UNITTESTS + +#include + +#include +#include + +namespace +{ + struct TestStruct + { + TestStruct( uint nn = 0 ) + : n( nn ), + f( 0.0 ), + c( '\0' ), + b( false ) + { + } + uint n; + double f; + char c; + bool b; + }; + + bool operator==( const TestStruct& lhs, const TestStruct& rhs ) + { + return lhs.n == rhs.n && lhs.f == rhs.f && lhs.c == rhs.c && lhs.b == rhs.b; + } + + class TestThread : public QThread + { + public: + TestThread( const QString& key ) + : mem( key ) + { + mem.attach(); + } + + void run() + { + while( true ) + { + msleep( 100 ); + kdtools::KDLockedSharedMemoryPointer< TestStruct > p( &mem ); + if( !p->b ) + continue; + + p->n = 5; + p->f = 3.14; + p->c = 'A'; + p->b = false; + return; + } + } + + QSharedMemory mem; + }; + + bool isConst( TestStruct* ) + { + return false; + } + + bool isConst( const TestStruct* ) + { + return true; + } +} + + +KDAB_UNITTEST_SIMPLE( KDLockedSharedMemoryPointer, "kdcoretools" ) { + + const QString key = QUuid::createUuid(); + QSharedMemory mem( key ); + const bool created = mem.create( sizeof( TestStruct ) ); + assertTrue( created ); + if ( !created ) + return; // don't execute tests if shm coulnd't be created + + // On Windows, shared mem is only available in increments of page + // size (4k), so don't fail if the segment is larger: + const unsigned long mem_size = mem.size(); + assertGreaterOrEqual( mem_size, sizeof( TestStruct ) ); + + { + kdtools::KDLockedSharedMemoryPointer< TestStruct > p( &mem ); + assertTrue( p ); + *p = TestStruct(); + assertEqual( p->n, 0u ); + assertEqual( p->f, 0.0 ); + assertEqual( p->c, '\0' ); + assertFalse( p->b ); + } + + { + TestThread thread( key ); + assertEqual( thread.mem.key().toStdString(), key.toStdString() ); + assertEqual( static_cast< unsigned long >( thread.mem.size() ), mem_size ); + thread.start(); + + assertTrue( thread.isRunning() ); + thread.wait( 2000 ); + assertTrue( thread.isRunning() ); + + { + kdtools::KDLockedSharedMemoryPointer< TestStruct > p( &mem ); + p->b = true; + } + + thread.wait( 2000 ); + assertFalse( thread.isRunning() ); + } + + { + kdtools::KDLockedSharedMemoryPointer< TestStruct > p( &mem ); + assertEqual( p->n, 5u ); + assertEqual( p->f, 3.14 ); + assertEqual( p->c, 'A' ); + assertFalse( p->b ); + } + + { + kdtools::KDLockedSharedMemoryPointer< TestStruct > p( mem ); + assertEqual( mem.data(), p.get() ); + assertEqual( p.get(), p.operator->() ); + assertEqual( p.get(), &(*p) ); + assertEqual( p.get(), p.data() ); + assertFalse( isConst( p.get() ) ); + } + + { + const kdtools::KDLockedSharedMemoryPointer< TestStruct > p( &mem ); + assertEqual( mem.data(), p.get() ); + assertEqual( p.get(), p.operator->() ); + assertEqual( p.get(), &(*p) ); + assertEqual( p.get(), p.data() ); + assertTrue( isConst( p.get() ) ); + } + + { + QSharedMemory mem2( key + key ); + const bool created2 = mem2.create( 16 * sizeof( TestStruct ) ); + assertTrue( created2 ); + if ( !created2 ) + return; // don't execute tests if shm coulnd't be created + + kdtools::KDLockedSharedMemoryArray a( mem2 ); + assertTrue( a ); + assertEqual( a.get(), mem2.data() ); + assertEqual( &a[0], a.get() ); + + a[1] = a[0]; + assertTrue( a[0] == a[1] ); + + TestStruct ts; + ts.n = 5; + ts.f = 3.14; + a[0] = ts; + assertFalse( a[0] == a[1] ); + assertEqual( a.front().n, ts.n ); + assertEqual( a[0].f, ts.f ); + a[0].n = 10; + assertEqual( a.front().n, 10u ); + ts = a[0]; + assertEqual( ts.n, 10u ); + + std::vector< TestStruct > v; + for( uint i = 0; i < a.size(); ++i ) + v.push_back( TestStruct( i ) ); + + std::copy( v.begin(), v.end(), a.begin() ); + for( uint i = 0; i < a.size(); ++i ) + assertEqual( a[ i ].n, i ); + assertEqual( a.front().n, 0u ); + assertEqual( a.back().n, a.size() - 1 ); + + std::copy( v.begin(), v.end(), a.rbegin() ); + for( uint i = 0; i < a.size(); ++i ) + assertEqual( a[ i ].n, a.size() - 1 - i ); + assertEqual( a.front().n, a.size() - 1 ); + assertEqual( a.back().n, 0u ); + } + +} +#endif // KDTOOLSCORE_UNITTESTS +#endif // QT_NO_SHAREDMEMORY +#endif // QT_VERSION >= 0x040400 || defined( DOXYGEN_RUN ) diff --git a/src/libtomahawk/kdsingleapplicationguard/kdlockedsharedmemorypointer.h b/src/libtomahawk/kdsingleapplicationguard/kdlockedsharedmemorypointer.h new file mode 100644 index 000000000..df0ea4998 --- /dev/null +++ b/src/libtomahawk/kdsingleapplicationguard/kdlockedsharedmemorypointer.h @@ -0,0 +1,115 @@ +#ifndef __KDTOOLS__CORE__KDLOCKEDSHAREDMEMORYPOINTER_H__ +#define __KDTOOLS__CORE__KDLOCKEDSHAREDMEMORYPOINTER_H__ + +#include + +#if QT_VERSION >= 0x040400 || defined( DOXYGEN_RUN ) +#ifndef QT_NO_SHAREDMEMORY + +#include "kdsharedmemorylocker.h" +#include + +#include + +#ifndef DOXYGEN_RUN +namespace kdtools { +#endif + +class KDLockedSharedMemoryPointerBase { +protected: + explicit KDLockedSharedMemoryPointerBase( QSharedMemory * mem ); + explicit KDLockedSharedMemoryPointerBase( QSharedMemory & mem ); + ~KDLockedSharedMemoryPointerBase(); + + // PENDING(marc) do we really want const propagation here? I + // usually declare all my RAII objects const... + void * get(); + const void * get() const; + + KDAB_IMPLEMENT_SAFE_BOOL_OPERATOR( get() ) + + size_t byteSize() const; + +private: + KDSharedMemoryLocker locker; + QSharedMemory * const mem; +}; + +template< typename T> +class MAKEINCLUDES_EXPORT KDLockedSharedMemoryPointer : KDLockedSharedMemoryPointerBase { + KDAB_DISABLE_COPY( KDLockedSharedMemoryPointer ); +public: + explicit KDLockedSharedMemoryPointer( QSharedMemory * m ) + : KDLockedSharedMemoryPointerBase( m ) {} + explicit KDLockedSharedMemoryPointer( QSharedMemory & m ) + : KDLockedSharedMemoryPointerBase( m ) {} + + T * get() { return static_cast( KDLockedSharedMemoryPointerBase::get() ); } + const T * get() const { return static_cast( KDLockedSharedMemoryPointerBase::get() ); } + + T * data() { return static_cast( get() ); } + const T * data() const { return static_cast( get() ); } + + T & operator*() { assert( get() ); return *get(); } + const T & operator*() const { assert( get() ); return *get(); } + + T * operator->() { return get(); } + const T * operator->() const { return get(); } + + KDAB_USING_SAFE_BOOL_OPERATOR( KDLockedSharedMemoryPointerBase ) +}; + +template +class MAKEINCLUDES_EXPORT KDLockedSharedMemoryArray : KDLockedSharedMemoryPointerBase { + KDAB_DISABLE_COPY( KDLockedSharedMemoryArray ); +public: + explicit KDLockedSharedMemoryArray( QSharedMemory * m ) + : KDLockedSharedMemoryPointerBase( m ) {} + explicit KDLockedSharedMemoryArray( QSharedMemory & m ) + : KDLockedSharedMemoryPointerBase( m ) {} + + typedef std::size_t size_type; + typedef std::ptrdiff_t difference_type; + typedef T* iterator; + typedef const T* const_iterator; + typedef std::reverse_iterator< const_iterator > const_reverse_iterator; + typedef std::reverse_iterator< iterator > reverse_iterator; + + iterator begin() { return get(); } + const_iterator begin() const { return get(); } + + iterator end() { return begin() + size(); } + const_iterator end() const { return begin() + size(); } + + reverse_iterator rbegin() { return reverse_iterator( end() ); } + const_reverse_iterator rbegin() const { return reverse_iterator( end() ); } + + reverse_iterator rend() { return reverse_iterator( begin() ); } + const_reverse_iterator rend() const { return const_reverse_iterator( begin() ); } + + size_type size() const { return byteSize() / sizeof( T ); } + + T * get() { return static_cast( KDLockedSharedMemoryPointerBase::get() ); } + const T * get() const { return static_cast( KDLockedSharedMemoryPointerBase::get() ); } + + T & operator[]( difference_type n ) { assert( get() ); return *(get()+n); } + const T & operator[]( difference_type n ) const { assert( get() ); return *(get()+n); } + + T & front() { assert( get() ); return *get(); } + const T & front() const { assert( get() ); return *get(); } + + T & back() { assert( get() ); return *( get() + size() - 1 ); } + const T & back() const { assert( get() ); return *( get() + size() - 1 ); } + + KDAB_USING_SAFE_BOOL_OPERATOR( KDLockedSharedMemoryPointerBase ) +}; + +#ifndef DOXYGEN_RUN +} +#endif + +#endif /* QT_NO_SHAREDMEMORY */ + +#endif /* QT_VERSION >= 0x040400 || defined( DOXYGEN_RUN ) */ + +#endif /* __KDTOOLS__CORE__KDLOCKEDSHAREDMEMORYPOINTER_H__ */ diff --git a/src/libtomahawk/kdsingleapplicationguard/kdsharedmemorylocker.cpp b/src/libtomahawk/kdsingleapplicationguard/kdsharedmemorylocker.cpp new file mode 100644 index 000000000..0c99b8fff --- /dev/null +++ b/src/libtomahawk/kdsingleapplicationguard/kdsharedmemorylocker.cpp @@ -0,0 +1,40 @@ +#include "kdsharedmemorylocker.h" + +#if QT_VERSION >= 0x040400 || defined( DOXYGEN_RUN ) + +#include + +using namespace kdtools; + +/*! + \class KDSharedMemoryLocker + \ingroup raii core + \brief Exception-safe and convenient wrapper around QSharedMemory::lock() +*/ + +/** + * Constructor. Locks the shared memory segment \a mem. + * If another process has locking the segment, this constructor blocks + * until the lock is released. The memory segments needs to be properly created or attached. + */ +KDSharedMemoryLocker::KDSharedMemoryLocker( QSharedMemory* mem ) + : mem( mem ) +{ + mem->lock(); +} + +/** + * Destructor. Unlocks the shared memory segment associated with this + * KDSharedMemoryLocker. + */ +KDSharedMemoryLocker::~KDSharedMemoryLocker() +{ + mem->unlock(); +} + +#ifdef KDAB_EVAL +#include KDAB_EVAL +static const EvalDialogChecker evalChecker( "KD Tools", false ); +#endif + +#endif diff --git a/src/libtomahawk/kdsingleapplicationguard/kdsharedmemorylocker.h b/src/libtomahawk/kdsingleapplicationguard/kdsharedmemorylocker.h new file mode 100644 index 000000000..7ae83e771 --- /dev/null +++ b/src/libtomahawk/kdsingleapplicationguard/kdsharedmemorylocker.h @@ -0,0 +1,36 @@ +#ifndef __KDTOOLS__CORE__KDSHAREDMEMORYLOCKER_H +#define __KDTOOLS__CORE__KDSHAREDMEMORYLOCKER_H + +#include "kdtoolsglobal.h" + +#if QT_VERSION < 0x040400 && !defined( DOXYGEN_RUN ) +#ifdef Q_CC_GNU +#warning "Can't use KDTools KDSharedMemoryLocker with Qt versions prior to 4.4" +#endif +#else + +class QSharedMemory; + +#ifndef DOXYGEN_RUN +namespace kdtools +{ +#endif + +class KDTOOLSCORE_EXPORT KDSharedMemoryLocker +{ + Q_DISABLE_COPY( KDSharedMemoryLocker ) +public: + KDSharedMemoryLocker( QSharedMemory* mem ); + ~KDSharedMemoryLocker(); + +private: + QSharedMemory* const mem; +}; + +#ifndef DOXYGEN_RUN +} +#endif + +#endif + +#endif diff --git a/src/libtomahawk/kdsingleapplicationguard/kdsingleapplicationguard.cpp b/src/libtomahawk/kdsingleapplicationguard/kdsingleapplicationguard.cpp new file mode 100644 index 000000000..63893dbde --- /dev/null +++ b/src/libtomahawk/kdsingleapplicationguard/kdsingleapplicationguard.cpp @@ -0,0 +1,622 @@ +#include "kdsingleapplicationguard.h" + +#ifndef KDSINGLEAPPLICATIONGUARD_NUMBER_OF_PROCESSES +#define KDSINGLEAPPLICATIONGUARD_NUMBER_OF_PROCESSES 128 +#endif + +#ifndef KDSINGLEAPPLICATIONGUARD_MAX_COMMAND_LINE +#define KDSINGLEAPPLICATIONGUARD_MAX_COMMAND_LINE 1024 +#endif + + + +KDSingleApplicationGuard::Instance::Instance( const QStringList& args, qint64 p ) + : arguments( args ), + pid( p ) +{ +} + +#if QT_VERSION < 0x040400 + +class KDSingleApplicationGuard::Private +{ +}; + +KDSingleApplicationGuard::KDSingleApplicationGuard( QCoreApplication*, Policy ) +{ + qWarning( "KD Tools was compiled with a Qt version prior to 4.4. SingleApplicationGuard won't work." ); +} + +KDSingleApplicationGuard::~KDSingleApplicationGuard() +{ +} + +void KDSingleApplicationGuard::shutdownOtherInstances() +{ +} + +void KDSingleApplicationGuard::killOtherInstances() +{ +} + +void KDSingleApplicationGuard::timerEvent( QTimerEvent* ) +{ +} +#else + +#include +#include + +#include "kdsharedmemorylocker.h" +#include "kdlockedsharedmemorypointer.h" + +#include +#include +#include + +#ifndef Q_WS_WIN +#include +#endif + +using namespace kdtools; + +/*! + \class KDSingleApplicationGuard KDSingleApplicationGuard + \brief A guard to protect an application from having several instances. + + KDSingleApplicationGuard can be used to make sure only one instance of an + application is running at the same time. + + \note As KDSingleApplicationGuard uses QSharedMemory Qt 4.4 or later is required + */ + +/*! + \fn void KDSingleApplicationGuard::instanceStarted() + This signal is emitted by the primary instance when ever one other + instance was started. + */ + +/*! + \fn void KDSingleApplicationGuard::instanceExited() + This signal is emitted by the primary instance when ever one other + instance was exited. + */ + +/*! + \fn void KDSingleApplicationGuard::becamePrimaryInstance() + This signal is emitted, when the current running application gets the new + primary application. The old primary application has quit. + */ + +enum Command +{ + NoCommand = 0x00, + ExitedInstance = 0x01, + NewInstance = 0x02, + FreeInstance = 0x04, + ShutDownCommand = 0x08, + KillCommand = 0x10, + BecomePrimaryCommand = 0x20 +}; + +Q_DECLARE_FLAGS( Commands, Command ) +Q_DECLARE_OPERATORS_FOR_FLAGS( Commands ) + +struct ProcessInfo +{ + explicit ProcessInfo( Command c = FreeInstance, const QStringList& arguments = QStringList(), qint64 p = -1 ) + : command( c ), + pid( p ) + { + std::fill_n( commandline, KDSINGLEAPPLICATIONGUARD_MAX_COMMAND_LINE, '\0' ); + + int argpos = 0; + for( QStringList::const_iterator it = arguments.begin(); it != arguments.end(); ++it ) + { + const QByteArray arg = it->toLatin1(); + const int count = qMin( KDSINGLEAPPLICATIONGUARD_MAX_COMMAND_LINE - argpos, arg.count() ); + std::copy( arg.begin(), arg.begin() + count, commandline + argpos ); + argpos += arg.count() + 1; // makes sure there's a \0 between every parameter + } + } + + QStringList arguments() const + { + QStringList result; + + QByteArray arg; + for( int i = 0; i < KDSINGLEAPPLICATIONGUARD_MAX_COMMAND_LINE; ++i ) + { + if( commandline[ i ] == '\0' && !arg.isEmpty() ) + { + result.push_back( QString::fromLatin1( arg ) ); + arg.clear(); + } + else if( !commandline[ i ] == '\0' ) + { + arg.push_back( commandline[ i ] ); + } + } + + return result; + } + + Commands command; + char commandline[ KDSINGLEAPPLICATIONGUARD_MAX_COMMAND_LINE ]; + qint64 pid; +}; + +bool operator==( const ProcessInfo& lhs, const ProcessInfo& rhs ) +{ + return lhs.command == rhs.command && + ::memcmp( lhs.commandline, rhs.commandline, KDSINGLEAPPLICATIONGUARD_MAX_COMMAND_LINE ) == 0; +} + +bool operator!=( const ProcessInfo& lhs, const ProcessInfo& rhs ) +{ + return !operator==( lhs, rhs ); +} + +/*! + This struct contains information about the managed process system. + \internal + */ +struct InstanceRegister +{ + InstanceRegister( KDSingleApplicationGuard::Policy policy = KDSingleApplicationGuard::NoPolicy ) + : policy( policy ) + { + std::fill_n( info, KDSINGLEAPPLICATIONGUARD_NUMBER_OF_PROCESSES, ProcessInfo() ); + ::memcpy( magicCookie, "kdsingleapp", 12 ); + } + + /*! + Returns wheter this register was properly initialized by the first instance. + */ + bool isValid() const + { + return ::strcmp( magicCookie, "kdsingleapp" ) == 0; + } + + ProcessInfo info[ KDSINGLEAPPLICATIONGUARD_NUMBER_OF_PROCESSES ]; + KDSingleApplicationGuard::Policy policy; + char magicCookie[ 12 ]; +}; + +bool operator==( const InstanceRegister& lhs, const InstanceRegister& rhs ) +{ + if( lhs.policy != rhs.policy ) + return false; + + for( int i = 0; i < KDSINGLEAPPLICATIONGUARD_NUMBER_OF_PROCESSES; ++i ) + if( lhs.info[ i ] != rhs.info[ i ] ) + return false; + + return true; +} + +/*! + \internal + */ +class KDSingleApplicationGuard::Private +{ +public: + Private( KDSingleApplicationGuard* qq ) + : q( qq ), + id( -1 ) + { + if( primaryInstance == 0 ) + primaryInstance = q; + } + + ~Private() + { + if( primaryInstance == q ) + primaryInstance = 0; + } + + void shutdownInstance() + { + KDLockedSharedMemoryPointer< InstanceRegister > instances( &q->d->mem ); + instances->info[ q->d->id ].command = ExitedInstance; + + if( q->isPrimaryInstance() ) + { + // ohh... we need a new primary instance... + for( int i = 1; i < KDSINGLEAPPLICATIONGUARD_NUMBER_OF_PROCESSES; ++i ) + { + if( ( instances->info[ i ].command & ( FreeInstance | ExitedInstance | ShutDownCommand | KillCommand ) ) == 0 ) + { + instances->info[ i ].command |= BecomePrimaryCommand; + return; + } + } + // none found? then my species is dead :-( + } + } + + static KDSingleApplicationGuard* primaryInstance; + +private: + KDSingleApplicationGuard* const q; + +public: + Policy policy; + QSharedMemory mem; + int id; +}; + +KDSingleApplicationGuard* KDSingleApplicationGuard::Private::primaryInstance = 0; + +#ifndef Q_WS_WIN +void SIGINT_handler( int sig ) +{ + if( sig == SIGINT && KDSingleApplicationGuard::Private::primaryInstance != 0 ) + KDSingleApplicationGuard::Private::primaryInstance->d->shutdownInstance(); + ::exit( 1 ); +} +#endif + +/*! + Creates a new KDSingleApplicationGuard guarding \a parent from mulitply instances. + If \a policy is AutoKillOtherInstances (the default), all instances, which try to start, + are killed automatically and instanceStarted() is emitted. + If \a policy is NoPolicy, the other instance will run and instanceStarted() is emitted. + */ +KDSingleApplicationGuard::KDSingleApplicationGuard( QCoreApplication* parent, Policy policy ) + : QObject( parent ), + d( new Private( this ) ) +{ + const QString name = parent->applicationName(); + Q_ASSERT_X( !name.isEmpty(), "KDSingleApplicationGuard::KDSingleApplicationGuard", "applicationName must not be emty" ); + d->mem.setKey( name ); + + // if another instance crashed, the shared memory segment is still there on Unix + // the following lines trigger deletion in that case +#ifndef Q_WS_WIN + d->mem.attach(); + d->mem.detach(); +#endif + + d->policy = policy; + + const bool created = d->mem.create( sizeof( InstanceRegister ) ); + if( !created ) + { + if( !d->mem.attach() ) + { + qWarning( "KDSingleApplicationGuard: Could neither create nor attach to shared memory segment." ); + return; + } + + // lets wait till the other instance initialized the register + bool initialized = false; + while( !initialized ) + { + const KDLockedSharedMemoryPointer< InstanceRegister > instances( &d->mem ); + initialized = instances->isValid(); + } + } + + + KDLockedSharedMemoryPointer< InstanceRegister > instances( &d->mem ); + + if( !created ) + { + // we're _not_ the first instance + // but the + bool killOurSelf = false; + + // find a new slot... + d->id = std::find( instances->info, instances->info + KDSINGLEAPPLICATIONGUARD_NUMBER_OF_PROCESSES, ProcessInfo() ) - instances->info; + ProcessInfo& info = instances->info[ d->id ]; + info = ProcessInfo( NewInstance, parent->arguments(), QCoreApplication::applicationPid() ); + killOurSelf = instances->policy == AutoKillOtherInstances; + d->policy = instances->policy; + + // but the signal that we tried to start was sent to the primary application + if( killOurSelf ) + { + info.command |= ExitedInstance; + exit( 1 ); + } + } + else + { + // ok.... we are the first instance + InstanceRegister reg( policy ); // create a new list + d->id = 0; // our id = 0 + // and we've no command + reg.info[ 0 ] = ProcessInfo( NoCommand, parent->arguments(), QCoreApplication::applicationPid() ); + *instances = reg; // push this is the process list into shared memory + } + +#ifndef Q_WS_WIN + ::signal( SIGINT, SIGINT_handler ); +#endif + + // now listen for commands + startTimer( 250 ); +} + +/*! + Destroys this SingleApplicationGuard. + If this instance has been the primary instance and no other instance is existing anymore, + the application is shut down completely. Otherwise the destructor selects another instance to + be the primary instances. + */ +KDSingleApplicationGuard::~KDSingleApplicationGuard() +{ + if( d->id == -1 ) + return; + + d->shutdownInstance(); +} + +/*! + \property KDSingleApplicationGuard::primaryInstance + Determines wheter this instance is the primary instance. + The primary instance is the first instance which was started or an instance which + got selected by KDSingleApplicationGuard's destructor, when the primary instance was + shut down. + + Get this property's value using %isPrimaryInstance(), and monitor changes to it + using becamePrimaryInstance(). + */ +bool KDSingleApplicationGuard::isPrimaryInstance() const +{ + return d->id == 0; +} + +/*! + \property KDSingleApplicationGuard::Policy + Specifies the policy KDSingleApplicationGuard is using when new instances are started. + This can only be set in the primary instance. + + Get this property's value using %policy(), set it using %setPolicy(), and monitor changes + to it using policyChanged(). + */ +KDSingleApplicationGuard::Policy KDSingleApplicationGuard::policy() const +{ + return d->policy; +} + +void KDSingleApplicationGuard::setPolicy( Policy policy ) +{ + Q_ASSERT( isPrimaryInstance() ); + if( d->policy == policy ) + return; + + d->policy = policy; + emit policyChanged(); + KDLockedSharedMemoryPointer< InstanceRegister > instances( &d->mem ); + instances->policy = policy; +} + +/*! + Returns a list of all currently running instances. + */ +QList< KDSingleApplicationGuard::Instance > KDSingleApplicationGuard::instances() const +{ + QList< Instance > result; + const KDLockedSharedMemoryPointer< InstanceRegister > instances( const_cast< QSharedMemory* >( &d->mem ) ); + for( int i = 0; i < KDSINGLEAPPLICATIONGUARD_NUMBER_OF_PROCESSES; ++i ) + { + const ProcessInfo& info = instances->info[ i ]; + if( ( info.command & ( FreeInstance | ExitedInstance ) ) == 0 ) + result.push_back( Instance( info.arguments(), info.pid ) ); + } + return result; +} + +/*! + Shuts down all other instances. This can only be called from the + the primary instance. + Shut down is done gracefully via QCoreApplication::quit(). + */ +void KDSingleApplicationGuard::shutdownOtherInstances() +{ + Q_ASSERT( isPrimaryInstance() ); + KDLockedSharedMemoryPointer< InstanceRegister > instances( &d->mem ); + for( int i = 1; i < KDSINGLEAPPLICATIONGUARD_NUMBER_OF_PROCESSES; ++i ) + { + if( ( instances->info[ i ].command & ( FreeInstance | ExitedInstance ) ) == 0 ) + instances->info[ i ].command = ShutDownCommand; + } +} + +/*! + Kills all other instances. This can only be called from the + the primary instance. + Killing is done via exit(1) + */ +void KDSingleApplicationGuard::killOtherInstances() +{ + Q_ASSERT( isPrimaryInstance() ); + KDLockedSharedMemoryPointer< InstanceRegister > instances( &d->mem ); + for( int i = 1; i < KDSINGLEAPPLICATIONGUARD_NUMBER_OF_PROCESSES; ++i ) + { + if( ( instances->info[ i ].command & ( FreeInstance | ExitedInstance ) ) == 0 ) + instances->info[ i ].command = KillCommand; + } +} + +/*! + \reimp + */ +void KDSingleApplicationGuard::timerEvent( QTimerEvent* event ) +{ + Q_UNUSED( event ) + + if( isPrimaryInstance() ) + { + // only the primary instance will get notified about new instances + QList< Instance > exitedInstances; + QList< Instance > startedInstances; + + { + KDLockedSharedMemoryPointer< InstanceRegister > instances( &d->mem ); + + for( int i = 1; i < KDSINGLEAPPLICATIONGUARD_NUMBER_OF_PROCESSES; ++i ) + { + ProcessInfo& info = instances->info[ i ]; + if( info.command & NewInstance ) + { + startedInstances.push_back( Instance( info.arguments(), info.pid ) ); + info.command &= ~NewInstance; // clear NewInstance flag + } + else if( info.command & ExitedInstance ) + { + exitedInstances.push_back( Instance( info.arguments(), info.pid ) ); + info.command = FreeInstance; // set FreeInstance flag + } + } + } + + // one signal for every new instance - _after_ the memory segment was unlocked again + for( QList< Instance >::const_iterator it = startedInstances.begin(); it != startedInstances.end(); ++it ) + emit instanceStarted( *it ); + for( QList< Instance >::const_iterator it = exitedInstances.begin(); it != exitedInstances.end(); ++it ) + emit instanceExited( *it ); + } + else + { + // do we have a command? + bool killOurSelf = false; + bool shutDownOurSelf = false; + bool policyDidChange = false; + + { + KDLockedSharedMemoryPointer< InstanceRegister > instances( &d->mem ); + + policyDidChange = instances->policy != d->policy; + d->policy = instances->policy; + + if( instances->info[ d->id ].command & BecomePrimaryCommand ) + { + // we became primary! + instances->info[ 0 ] = instances->info[ d->id ]; + instances->info[ d->id ] = ProcessInfo(); // change our id to 0 and declare the old slot as free + d->id = 0; + emit becamePrimaryInstance(); + } + + killOurSelf = instances->info[ d->id ].command & KillCommand; // check for kill command + shutDownOurSelf = instances->info[ d->id ].command & ShutDownCommand; // check for shut down command + instances->info[ d->id ].command &= ~( KillCommand | ShutDownCommand | BecomePrimaryCommand ); // reset both flags + if( killOurSelf ) + { + instances->info[ d->id ].command |= ExitedInstance; // upon kill, we have to set the ExitedInstance flag + d->id = -1; // becauso our d'tor won't be called anymore + } + } + + if( killOurSelf ) // kill our self takes precedence + exit( 1 ); + else if( shutDownOurSelf ) + qApp->quit(); + else if( policyDidChange ) + emit policyChanged(); + } +} + +#ifdef KDTOOLSCORE_UNITTESTS + +#include + +#include + +#include +#include +#include + +Q_DECLARE_METATYPE( KDSingleApplicationGuard::Instance ); + +static void wait( int msec ) +{ + QTime t; + t.start(); + while( t.elapsed() < msec ) + { + qApp->processEvents( QEventLoop::WaitForMoreEvents, msec - t.elapsed() ); + } +} + +static std::ostream& operator<<( std::ostream& stream, const QStringList& list ) +{ + stream << "QStringList("; + for( QStringList::const_iterator it = list.begin(); it != list.end(); ++it ) + { + stream << " " << it->toLocal8Bit().data(); + if( it + 1 != list.end() ) + stream << ","; + } + stream << " )"; + return stream; +} + + +KDAB_UNITTEST_SIMPLE( KDSingleApplicationGuard, "kdcoretools" ) { + + // set it to an unique name + qApp->setApplicationName( QUuid::createUuid().toString() ); + + qRegisterMetaType< KDSingleApplicationGuard::Instance >(); + + KDSingleApplicationGuard* guard3 = 0; + QSignalSpy* spy3 = 0; + + { + KDSingleApplicationGuard guard1( qApp ); + assertEqual( guard1.policy(), KDSingleApplicationGuard::AutoKillOtherInstances ); + assertEqual( guard1.instances().count(), 1 ); + assertTrue( guard1.isPrimaryInstance() ); + + guard1.setPolicy( KDSingleApplicationGuard::NoPolicy ); + assertEqual( guard1.policy(), KDSingleApplicationGuard::NoPolicy ); + + QSignalSpy spy1( &guard1, SIGNAL( instanceStarted( KDSingleApplicationGuard::Instance ) ) ); + + KDSingleApplicationGuard guard2( qApp ); + assertEqual( guard1.instances().count(), 2 ); + assertEqual( guard2.instances().count(), 2 ); + assertEqual( guard2.policy(), KDSingleApplicationGuard::NoPolicy ); + assertFalse( guard2.isPrimaryInstance() ); + + wait( 1000 ); + + assertEqual( spy1.count(), 1 ); + guard3 = new KDSingleApplicationGuard( qApp ); + spy3 = new QSignalSpy( guard3, SIGNAL( becamePrimaryInstance() ) ); + assertFalse( guard3->isPrimaryInstance() ); + } + + wait( 1000 ); + assertEqual( spy3->count(), 1 ); + assertEqual( guard3->instances().count(), 1 ); + assertTrue( guard3->isPrimaryInstance() ); + + assertEqual( guard3->instances().first().arguments, qApp->arguments() ); + + QSignalSpy spyStarted( guard3, SIGNAL( instanceStarted( KDSingleApplicationGuard::Instance ) ) ); + QSignalSpy spyExited( guard3, SIGNAL( instanceExited( KDSingleApplicationGuard::Instance ) ) ); + + { + KDSingleApplicationGuard guard1( qApp ); + KDSingleApplicationGuard guard2( qApp ); + + wait( 1000 ); + + assertEqual( spyStarted.count(), 2 ); + } + + wait( 1000 ); + assertEqual( spyExited.count(), 2 ); + + delete spy3; + delete guard3; + } + +#endif // KDTOOLSCORE_UNITTESTS + +#endif // QT_VERSION < 0x040400 diff --git a/src/libtomahawk/kdsingleapplicationguard/kdsingleapplicationguard.h b/src/libtomahawk/kdsingleapplicationguard/kdsingleapplicationguard.h new file mode 100644 index 000000000..f649ef836 --- /dev/null +++ b/src/libtomahawk/kdsingleapplicationguard/kdsingleapplicationguard.h @@ -0,0 +1,74 @@ +#ifndef __KDTOOLSCORE_KDSINGLEAPPLICATIONGUARD_H__ +#define __KDTOOLSCORE_KDSINGLEAPPLICATIONGUARD_H__ + +#include +#include + +#include "pimpl_ptr.h" + +class QCoreApplication; + +#ifndef Q_WS_WIN +void SIGINT_handler( int sig ); +#endif + +class KDTOOLSCORE_EXPORT KDSingleApplicationGuard : public QObject +{ + Q_OBJECT +#ifndef Q_WS_WIN + friend void ::SIGINT_handler( int ); +#endif + +public: + enum Policy + { + NoPolicy = 0, + AutoKillOtherInstances = 1 + }; + + Q_PROPERTY( bool primaryInstance READ isPrimaryInstance NOTIFY becamePrimaryInstance ) + Q_PROPERTY( Policy policy READ policy WRITE setPolicy NOTIFY policyChanged ) + + explicit KDSingleApplicationGuard( QCoreApplication* parent, Policy policy = AutoKillOtherInstances ); + ~KDSingleApplicationGuard(); + + bool isPrimaryInstance() const; + + Policy policy() const; + void setPolicy( Policy policy ); + + struct Instance + { + Instance( const QStringList& arguments = QStringList(), qint64 pid = -1 ); + + QStringList arguments; + qint64 pid; + }; + + QList< Instance > instances() const; + +Q_SIGNALS: + void instanceStarted( KDSingleApplicationGuard::Instance instance ); + void instanceExited( KDSingleApplicationGuard::Instance instance ); + void becamePrimaryInstance(); + void policyChanged(); + +public Q_SLOTS: + void shutdownOtherInstances(); + void killOtherInstances(); + +protected: + void timerEvent( QTimerEvent* event ); + +private: + class Private; + kdtools::pimpl_ptr< Private > d; +}; + +#if QT_VERSION < 0x040400 +#ifdef Q_CC_GNU +#warning "Can't use KDSingleApplicationGuard with Qt versions prior to 4.4" +#endif +#endif + +#endif diff --git a/src/libtomahawk/kdsingleapplicationguard/kdtoolsglobal.cpp b/src/libtomahawk/kdsingleapplicationguard/kdtoolsglobal.cpp new file mode 100644 index 000000000..5997fe64c --- /dev/null +++ b/src/libtomahawk/kdsingleapplicationguard/kdtoolsglobal.cpp @@ -0,0 +1,32 @@ +#include "kdtoolsglobal.h" + +#include + +#include + +namespace { + struct Version { + unsigned char v[3]; + }; + + static inline bool operator<( const Version & lhs, const Version & rhs ) { + return std::lexicographical_compare( lhs.v, lhs.v + 3, rhs.v, rhs.v + 3 ); + } + static inline bool operator==( const Version & lhs, const Version & rhs ) { + return std::equal( lhs.v, lhs.v + 3, rhs.v ); + } + KDTOOLS_MAKE_RELATION_OPERATORS( Version, static inline ) +} + +static Version kdParseQtVersion( const char * const version ) { + if ( !version || qstrlen( version ) < 5 || version[1] != '.' || version[3] != '.' || version[5] != 0 && version[5] != '.' && version[5] != '-' ) + return Version(); // parse error + const Version result = { { version[0] - '0', version[2] - '0', version[4] - '0' } }; + return result; +} + +bool _kdCheckQtVersion_impl( int major, int minor, int patchlevel ) { + static const Version actual = kdParseQtVersion( qVersion() ); // do this only once each run... + const Version requested = { { major, minor, patchlevel } }; + return actual >= requested; +} diff --git a/src/libtomahawk/kdsingleapplicationguard/kdtoolsglobal.h b/src/libtomahawk/kdsingleapplicationguard/kdtoolsglobal.h new file mode 100644 index 000000000..4e8d0d673 --- /dev/null +++ b/src/libtomahawk/kdsingleapplicationguard/kdtoolsglobal.h @@ -0,0 +1,113 @@ +#ifndef __KDTOOLS_KDTOOLSGLOBAL_H__ +#define __KDTOOLS_KDTOOLSGLOBAL_H__ + +#include + +#define KDAB_DISABLE_COPY( x ) private: x( const x & ); x & operator=( const x & ) + +#ifdef KDTOOLS_SHARED +# ifdef BUILD_SHARED_KDTOOLSCORE +# define KDTOOLSCORE_EXPORT Q_DECL_EXPORT +# else +# define KDTOOLSCORE_EXPORT Q_DECL_IMPORT +# endif +# ifdef BUILD_SHARED_KDTOOLSGUI +# define KDTOOLSGUI_EXPORT Q_DECL_EXPORT +# else +# define KDTOOLSGUI_EXPORT Q_DECL_IMPORT +# endif +# ifdef BUILD_SHARED_KDTOOLSXML +# define KDTOOLSXML_EXPORT Q_DECL_EXPORT +# else +# define KDTOOLSXML_EXPORT Q_DECL_IMPORT +# endif +# ifdef BUILD_SHARED_KDUPDATER +# define KDTOOLS_UPDATER_EXPORT Q_DECL_EXPORT +# else +# define KDTOOLS_UPDATER_EXPORT Q_DECL_IMPORT +# endif +#else // KDTOOLS_SHARED +# define KDTOOLSCORE_EXPORT +# define KDTOOLSGUI_EXPORT +# define KDTOOLSXML_EXPORT +# define KDTOOLS_UPDATER_EXPORT +#endif // KDTOOLS_SHARED + +#define MAKEINCLUDES_EXPORT + +#define DOXYGEN_PROPERTY( x ) +#ifdef DOXYGEN_RUN +# define KDAB_IMPLEMENT_SAFE_BOOL_OPERATOR( func ) operator unspecified_bool_type() const { return func; } +# define KDAB_USING_SAFE_BOOL_OPERATOR( Class ) operator unspecified_bool_type() const; +#else +# define KDAB_IMPLEMENT_SAFE_BOOL_OPERATOR( func ) \ + private: struct __safe_bool_dummy__ { void nonnull() {} }; \ + typedef void ( __safe_bool_dummy__::*unspecified_bool_type )(); \ + public: \ + operator unspecified_bool_type() const { \ + return ( func ) ? &__safe_bool_dummy__::nonnull : 0 ; \ + } +#define KDAB_USING_SAFE_BOOL_OPERATOR( Class ) \ + using Class::operator Class::unspecified_bool_type; +#endif + +#define KDTOOLS_MAKE_RELATION_OPERATORS( Class, linkage ) \ + linkage bool operator>( const Class & lhs, const Class & rhs ) { \ + return operator<( rhs, lhs ); \ + } \ + linkage bool operator!=( const Class & lhs, const Class & rhs ) { \ + return !operator==( lhs, rhs ); \ + } \ + linkage bool operator<=( const Class & lhs, const Class & rhs ) { \ + return !operator>( lhs, rhs ); \ + } \ + linkage bool operator>=( const Class & lhs, const Class & rhs ) { \ + return !operator<( lhs, rhs ); \ + } + +template +inline T & __kdtools__dereference_for_methodcall( T & o ) { + return o; +} + +template +inline T & __kdtools__dereference_for_methodcall( T * o ) { + return *o; +} + +#define KDAB_SET_OBJECT_NAME( x ) __kdtools__dereference_for_methodcall( x ).setObjectName( QLatin1String( #x ) ) + +KDTOOLSCORE_EXPORT bool _kdCheckQtVersion_impl( int major, int minor=0, int patchlevel=0 ); +static inline bool kdCheckQtVersion( unsigned int major, unsigned int minor=0, unsigned int patchlevel=0 ) { + return (major<<16|minor<<8|patchlevel) <= static_cast(QT_VERSION) + || _kdCheckQtVersion_impl( major, minor, patchlevel ); +} + +#define KDTOOLS_DECLARE_PRIVATE_BASE( Class ) \ +protected: \ + class Private; \ + Private * d_func() { return _d; } \ + const Private * d_func() const { return _d; } \ + Class( Private * _d_, bool b ) : _d( _d_ ) { init(b); } \ +private: \ + void init(bool); \ +private: \ + Private * _d + +#define KDTOOLS_DECLARE_PRIVATE_DERIVED( Class, Base ) \ +protected: \ + class Private; \ + Private * d_func() { \ + return reinterpret_cast( Base::d_func() ); \ + } \ + const Private * d_func() const { \ + return reinterpret_cast( Base::d_func() ); \ + } \ + Class( Private * _d_, bool b ) \ + : Base( reinterpret_cast(_d_), b ) { init(b); } \ +private: \ + void init(bool) + + +#endif /* __KDTOOLS_KDTOOLSGLOBAL_H__ */ + diff --git a/src/libtomahawk/kdsingleapplicationguard/license-gpl b/src/libtomahawk/kdsingleapplicationguard/license-gpl new file mode 100644 index 000000000..332ed973c --- /dev/null +++ b/src/libtomahawk/kdsingleapplicationguard/license-gpl @@ -0,0 +1,349 @@ + + The KD Tools Library is Copyright (C) 2001-2003 Klarälvdalens Datakonsult AB. + + You may use, distribute and copy the KD Tools Library under the terms of + GNU General Public License version 2, which is displayed below. + +------------------------------------------------------------------------- + + GNU GENERAL PUBLIC LICENSE + Version 2, June 1991 + + Copyright (C) 1989, 1991 Free Software Foundation, Inc. + 675 Mass Ave, Cambridge, MA 02139, USA + Everyone is permitted to copy and distribute verbatim copies + of this license document, but changing it is not allowed. + + Preamble + + The licenses for most software are designed to take away your +freedom to share and change it. By contrast, the GNU General Public +License is intended to guarantee your freedom to share and change free +software--to make sure the software is free for all its users. This +General Public License applies to most of the Free Software +Foundation's software and to any other program whose authors commit to +using it. (Some other Free Software Foundation software is covered by +the GNU Library General Public License instead.) You can apply it to +your programs, too. + + When we speak of free software, we are referring to freedom, not +price. Our General Public Licenses are designed to make sure that you +have the freedom to distribute copies of free software (and charge for +this service if you wish), that you receive source code or can get it +if you want it, that you can change the software or use pieces of it +in new free programs; and that you know you can do these things. + + To protect your rights, we need to make restrictions that forbid +anyone to deny you these rights or to ask you to surrender the rights. +These restrictions translate to certain responsibilities for you if you +distribute copies of the software, or if you modify it. + + For example, if you distribute copies of such a program, whether +gratis or for a fee, you must give the recipients all the rights that +you have. You must make sure that they, too, receive or can get the +source code. And you must show them these terms so they know their +rights. + + We protect your rights with two steps: (1) copyright the software, and +(2) offer you this license which gives you legal permission to copy, +distribute and/or modify the software. + + Also, for each author's protection and ours, we want to make certain +that everyone understands that there is no warranty for this free +software. If the software is modified by someone else and passed on, we +want its recipients to know that what they have is not the original, so +that any problems introduced by others will not reflect on the original +authors' reputations. + + Finally, any free program is threatened constantly by software +patents. We wish to avoid the danger that redistributors of a free +program will individually obtain patent licenses, in effect making the +program proprietary. To prevent this, we have made it clear that any +patent must be licensed for everyone's free use or not licensed at all. + + The precise terms and conditions for copying, distribution and +modification follow. + + GNU GENERAL PUBLIC LICENSE + TERMS AND CONDITIONS FOR COPYING, DISTRIBUTION AND MODIFICATION + + 0. This License applies to any program or other work which contains +a notice placed by the copyright holder saying it may be distributed +under the terms of this General Public License. The "Program", below, +refers to any such program or work, and a "work based on the Program" +means either the Program or any derivative work under copyright law: +that is to say, a work containing the Program or a portion of it, +either verbatim or with modifications and/or translated into another +language. (Hereinafter, translation is included without limitation in +the term "modification".) Each licensee is addressed as "you". + +Activities other than copying, distribution and modification are not +covered by this License; they are outside its scope. The act of +running the Program is not restricted, and the output from the Program +is covered only if its contents constitute a work based on the +Program (independent of having been made by running the Program). +Whether that is true depends on what the Program does. + + 1. You may copy and distribute verbatim copies of the Program's +source code as you receive it, in any medium, provided that you +conspicuously and appropriately publish on each copy an appropriate +copyright notice and disclaimer of warranty; keep intact all the +notices that refer to this License and to the absence of any warranty; +and give any other recipients of the Program a copy of this License +along with the Program. + +You may charge a fee for the physical act of transferring a copy, and +you may at your option offer warranty protection in exchange for a fee. + + 2. You may modify your copy or copies of the Program or any portion +of it, thus forming a work based on the Program, and copy and +distribute such modifications or work under the terms of Section 1 +above, provided that you also meet all of these conditions: + + a) You must cause the modified files to carry prominent notices + stating that you changed the files and the date of any change. + + b) You must cause any work that you distribute or publish, that in + whole or in part contains or is derived from the Program or any + part thereof, to be licensed as a whole at no charge to all third + parties under the terms of this License. + + c) If the modified program normally reads commands interactively + when run, you must cause it, when started running for such + interactive use in the most ordinary way, to print or display an + announcement including an appropriate copyright notice and a + notice that there is no warranty (or else, saying that you provide + a warranty) and that users may redistribute the program under + these conditions, and telling the user how to view a copy of this + License. (Exception: if the Program itself is interactive but + does not normally print such an announcement, your work based on + the Program is not required to print an announcement.) + +These requirements apply to the modified work as a whole. If +identifiable sections of that work are not derived from the Program, +and can be reasonably considered independent and separate works in +themselves, then this License, and its terms, do not apply to those +sections when you distribute them as separate works. But when you +distribute the same sections as part of a whole which is a work based +on the Program, the distribution of the whole must be on the terms of +this License, whose permissions for other licensees extend to the +entire whole, and thus to each and every part regardless of who wrote it. + +Thus, it is not the intent of this section to claim rights or contest +your rights to work written entirely by you; rather, the intent is to +exercise the right to control the distribution of derivative or +collective works based on the Program. + +In addition, mere aggregation of another work not based on the Program +with the Program (or with a work based on the Program) on a volume of +a storage or distribution medium does not bring the other work under +the scope of this License. + + 3. You may copy and distribute the Program (or a work based on it, +under Section 2) in object code or executable form under the terms of +Sections 1 and 2 above provided that you also do one of the following: + + a) Accompany it with the complete corresponding machine-readable + source code, which must be distributed under the terms of Sections + 1 and 2 above on a medium customarily used for software interchange; or, + + b) Accompany it with a written offer, valid for at least three + years, to give any third party, for a charge no more than your + cost of physically performing source distribution, a complete + machine-readable copy of the corresponding source code, to be + distributed under the terms of Sections 1 and 2 above on a medium + customarily used for software interchange; or, + + c) Accompany it with the information you received as to the offer + to distribute corresponding source code. (This alternative is + allowed only for noncommercial distribution and only if you + received the program in object code or executable form with such + an offer, in accord with Subsection b above.) + +The source code for a work means the preferred form of the work for +making modifications to it. For an executable work, complete source +code means all the source code for all modules it contains, plus any +associated interface definition files, plus the scripts used to +control compilation and installation of the executable. However, as a +special exception, the source code distributed need not include +anything that is normally distributed (in either source or binary +form) with the major components (compiler, kernel, and so on) of the +operating system on which the executable runs, unless that component +itself accompanies the executable. + +If distribution of executable or object code is made by offering +access to copy from a designated place, then offering equivalent +access to copy the source code from the same place counts as +distribution of the source code, even though third parties are not +compelled to copy the source along with the object code. + + 4. You may not copy, modify, sublicense, or distribute the Program +except as expressly provided under this License. Any attempt +otherwise to copy, modify, sublicense or distribute the Program is +void, and will automatically terminate your rights under this License. +However, parties who have received copies, or rights, from you under +this License will not have their licenses terminated so long as such +parties remain in full compliance. + + 5. You are not required to accept this License, since you have not +signed it. However, nothing else grants you permission to modify or +distribute the Program or its derivative works. These actions are +prohibited by law if you do not accept this License. Therefore, by +modifying or distributing the Program (or any work based on the +Program), you indicate your acceptance of this License to do so, and +all its terms and conditions for copying, distributing or modifying +the Program or works based on it. + + 6. Each time you redistribute the Program (or any work based on the +Program), the recipient automatically receives a license from the +original licensor to copy, distribute or modify the Program subject to +these terms and conditions. You may not impose any further +restrictions on the recipients' exercise of the rights granted herein. +You are not responsible for enforcing compliance by third parties to +this License. + + 7. If, as a consequence of a court judgment or allegation of patent +infringement or for any other reason (not limited to patent issues), +conditions are imposed on you (whether by court order, agreement or +otherwise) that contradict the conditions of this License, they do not +excuse you from the conditions of this License. If you cannot +distribute so as to satisfy simultaneously your obligations under this +License and any other pertinent obligations, then as a consequence you +may not distribute the Program at all. For example, if a patent +license would not permit royalty-free redistribution of the Program by +all those who receive copies directly or indirectly through you, then +the only way you could satisfy both it and this License would be to +refrain entirely from distribution of the Program. + +If any portion of this section is held invalid or unenforceable under +any particular circumstance, the balance of the section is intended to +apply and the section as a whole is intended to apply in other +circumstances. + +It is not the purpose of this section to induce you to infringe any +patents or other property right claims or to contest validity of any +such claims; this section has the sole purpose of protecting the +integrity of the free software distribution system, which is +implemented by public license practices. Many people have made +generous contributions to the wide range of software distributed +through that system in reliance on consistent application of that +system; it is up to the author/donor to decide if he or she is willing +to distribute software through any other system and a licensee cannot +impose that choice. + +This section is intended to make thoroughly clear what is believed to +be a consequence of the rest of this License. + + 8. If the distribution and/or use of the Program is restricted in +certain countries either by patents or by copyrighted interfaces, the +original copyright holder who places the Program under this License +may add an explicit geographical distribution limitation excluding +those countries, so that distribution is permitted only in or among +countries not thus excluded. In such case, this License incorporates +the limitation as if written in the body of this License. + + 9. The Free Software Foundation may publish revised and/or new versions +of the General Public License from time to time. Such new versions will +be similar in spirit to the present version, but may differ in detail to +address new problems or concerns. + +Each version is given a distinguishing version number. If the Program +specifies a version number of this License which applies to it and "any +later version", you have the option of following the terms and conditions +either of that version or of any later version published by the Free +Software Foundation. If the Program does not specify a version number of +this License, you may choose any version ever published by the Free Software +Foundation. + + 10. If you wish to incorporate parts of the Program into other free +programs whose distribution conditions are different, write to the author +to ask for permission. For software which is copyrighted by the Free +Software Foundation, write to the Free Software Foundation; we sometimes +make exceptions for this. Our decision will be guided by the two goals +of preserving the free status of all derivatives of our free software and +of promoting the sharing and reuse of software generally. + + NO WARRANTY + + 11. BECAUSE THE PROGRAM IS LICENSED FREE OF CHARGE, THERE IS NO WARRANTY +FOR THE PROGRAM, TO THE EXTENT PERMITTED BY APPLICABLE LAW. EXCEPT WHEN +OTHERWISE STATED IN WRITING THE COPYRIGHT HOLDERS AND/OR OTHER PARTIES +PROVIDE THE PROGRAM "AS IS" WITHOUT WARRANTY OF ANY KIND, EITHER EXPRESSED +OR IMPLIED, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF +MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE. THE ENTIRE RISK AS +TO THE QUALITY AND PERFORMANCE OF THE PROGRAM IS WITH YOU. SHOULD THE +PROGRAM PROVE DEFECTIVE, YOU ASSUME THE COST OF ALL NECESSARY SERVICING, +REPAIR OR CORRECTION. + + 12. IN NO EVENT UNLESS REQUIRED BY APPLICABLE LAW OR AGREED TO IN WRITING +WILL ANY COPYRIGHT HOLDER, OR ANY OTHER PARTY WHO MAY MODIFY AND/OR +REDISTRIBUTE THE PROGRAM AS PERMITTED ABOVE, BE LIABLE TO YOU FOR DAMAGES, +INCLUDING ANY GENERAL, SPECIAL, INCIDENTAL OR CONSEQUENTIAL DAMAGES ARISING +OUT OF THE USE OR INABILITY TO USE THE PROGRAM (INCLUDING BUT NOT LIMITED +TO LOSS OF DATA OR DATA BEING RENDERED INACCURATE OR LOSSES SUSTAINED BY +YOU OR THIRD PARTIES OR A FAILURE OF THE PROGRAM TO OPERATE WITH ANY OTHER +PROGRAMS), EVEN IF SUCH HOLDER OR OTHER PARTY HAS BEEN ADVISED OF THE +POSSIBILITY OF SUCH DAMAGES. + + END OF TERMS AND CONDITIONS + + Appendix: How to Apply These Terms to Your New Programs + + If you develop a new program, and you want it to be of the greatest +possible use to the public, the best way to achieve this is to make it +free software which everyone can redistribute and change under these terms. + + To do so, attach the following notices to the program. It is safest +to attach them to the start of each source file to most effectively +convey the exclusion of warranty; and each file should have at least +the "copyright" line and a pointer to where the full notice is found. + + + Copyright (C) 19yy + + 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., 675 Mass Ave, Cambridge, MA 02139, USA. + +Also add information on how to contact you by electronic and paper mail. + +If the program is interactive, make it output a short notice like this +when it starts in an interactive mode: + + Gnomovision version 69, Copyright (C) 19yy name of author + Gnomovision comes with ABSOLUTELY NO WARRANTY; for details type `show w'. + This is free software, and you are welcome to redistribute it + under certain conditions; type `show c' for details. + +The hypothetical commands `show w' and `show c' should show the appropriate +parts of the General Public License. Of course, the commands you use may +be called something other than `show w' and `show c'; they could even be +mouse-clicks or menu items--whatever suits your program. + +You should also get your employer (if you work as a programmer) or your +school, if any, to sign a "copyright disclaimer" for the program, if +necessary. Here is a sample; alter the names: + + Yoyodyne, Inc., hereby disclaims all copyright interest in the program + `Gnomovision' (which makes passes at compilers) written by James Hacker. + + , 1 April 1989 + Ty Coon, President of Vice + +This General Public License does not permit incorporating your program into +proprietary programs. If your program is a subroutine library, you may +consider it more useful to permit linking proprietary applications with the +library. If this is what you want to do, use the GNU Library General +Public License instead of this License. + +------------------------------------------------------------------------- diff --git a/src/libtomahawk/kdsingleapplicationguard/pimpl_ptr.cpp b/src/libtomahawk/kdsingleapplicationguard/pimpl_ptr.cpp new file mode 100644 index 000000000..3045ebc2a --- /dev/null +++ b/src/libtomahawk/kdsingleapplicationguard/pimpl_ptr.cpp @@ -0,0 +1,203 @@ +#include "pimpl_ptr.h" + +/*! + \class pimpl_ptr: + \ingroup core smartptr + \brief Owning pointer for private implementations + \since_c 2.1 + + (The exception safety of this class has not been evaluated yet.) + + pimpl_ptr is a smart immutable pointer, which owns the contained object. Unlike other smart pointers, + it creates a standard constructed object when instanciated via the + \link pimpl_ptr() standard constructor\endlink. + Additionally, pimpl_ptr respects constness of the pointer object and returns \c const \c T* for + a const pimpl_ptr object. + + The content of a pimpl_ptr cannot be changed during it's lifetime. + + \section general-use General Use + + The general use case of pimpl_ptr is the "Pimpl Idiom", i.e. hiding the private implementation of a class + from the user's compiler which see \c MyClass as + + \code + class MyClass + { + public: + MyClass(); + ~MyClass(); + + // public class API + int value() const; + + private: + class Private; // defined later + kdtools::pimpl_ptr< Private > d; + }; + \endcode + + but not the private parts of it. These can only be seen (and accessed) by the code knowing \c MyClass::Private: + + \code + class MyClass::Private + { + public: + int value; + }; + + MyClass::MyClass() + { + // d was automatically filled with new Private + d->value = 42; + } + + MyClass::~MyClass() + { + // the content of d gets deleted automatically + } + + int MyClass::value() const + { + // access the private part: + // since MyClass::value() is const, the returned pointee is const, too + return d->value; + } + \endcode + +*/ + +/*! + \fn pimpl_ptr::pimpl_ptr() + + Default constructor. Constructs a pimpl_tr that contains (owns) a standard constructed + instance of \c T. + + \post \c *this owns a new object. +*/ + +/*! + \fn pimpl_ptr::pimpl_ptr( T * t ) + + Constructor. Constructs a pimpl_ptr that contains (owns) \a t. + + \post get() == obj +*/ + +/*! + \fn pimpl_ptr::~pimpl_ptr() + + Destructor. + + \post The object previously owned by \c *this has been deleted. +*/ + +/*! + \fn const T * pimpl_ptr::get() const + + \returns a const pointer to the contained (owned) object. + \overload +*/ + +/*! + \fn T * pimpl_ptr::get() + + \returns a pointer to the contained (owned) object. +*/ + +/*! + \fn const T & pimpl_ptr::operator*() const + + Dereference operator. Returns \link get() *get()\endlink. + \overload +*/ + +/*! + \fn T & pimpl_ptr::operator*() + + Dereference operator. Returns \link get() *get()\endlink. +*/ + +/*! + \fn const T * pimpl_ptr::operator->() const + + Member-by-pointer operator. Returns get(). + \overload +*/ + +/*! + \fn T * pimpl_ptr::operator->() + + Member-by-pointer operator. Returns get(). +*/ + +#ifdef KDTOOLSCORE_UNITTESTS + +#include + +#include +#include + +namespace +{ + struct ConstTester + { + bool isConst() + { + return false; + } + + bool isConst() const + { + return true; + } + }; +} + +KDAB_UNITTEST_SIMPLE( pimpl_ptr, "kdcoretools" ) { + + { + kdtools::pimpl_ptr< QObject > p; + assertNotNull( p.get() ); + assertNull( p->parent() ); + } + + + { + QPointer< QObject > o; + { + kdtools::pimpl_ptr< QObject > qobject( new QObject ); + o = qobject.get(); + assertEqual( o, qobject.operator->() ); + assertEqual( o, &(qobject.operator*()) ); + } + assertNull( o ); + } + + { + const kdtools::pimpl_ptr< QObject > qobject( new QObject ); + const QObject* o = qobject.get(); + assertEqual( o, qobject.operator->() ); + assertEqual( o, &(qobject.operator*()) ); + } + + { + kdtools::pimpl_ptr< QObject > o1; + assertTrue( o1 ); + kdtools::pimpl_ptr< QObject > o2( 0 ); + assertFalse( o2 ); + } + + { + const kdtools::pimpl_ptr< ConstTester > o1; + kdtools::pimpl_ptr< ConstTester > o2; + assertTrue( o1->isConst() ); + assertFalse( o2->isConst() ); + assertTrue( (*o1).isConst() ); + assertFalse( (*o2).isConst() ); + assertTrue( o1.get()->isConst() ); + assertFalse( o2.get()->isConst() ); + } +} + +#endif // KDTOOLSCORE_UNITTESTS diff --git a/src/libtomahawk/kdsingleapplicationguard/pimpl_ptr.h b/src/libtomahawk/kdsingleapplicationguard/pimpl_ptr.h new file mode 100644 index 000000000..7b7f36839 --- /dev/null +++ b/src/libtomahawk/kdsingleapplicationguard/pimpl_ptr.h @@ -0,0 +1,44 @@ +#ifndef __KDTOOLSCORE__PIMPL_PTR_H__ +#define __KDTOOLSCORE__PIMPL_PTR_H__ + +#include "kdtoolsglobal.h" + +#ifndef DOXYGEN_RUN +namespace kdtools { +#endif + + template + class pimpl_ptr { + KDAB_DISABLE_COPY( pimpl_ptr ); + T * d; + public: + pimpl_ptr() : d( new T ) {} + explicit pimpl_ptr( T * t ) : d( t ) {} + ~pimpl_ptr() { delete d; d = 0; } + + T * get() { return d; } + const T * get() const { return d; } + + T * operator->() { return get(); } + const T * operator->() const { return get(); } + + T & operator*() { return *get(); } + const T & operator*() const { return *get(); } + + KDAB_IMPLEMENT_SAFE_BOOL_OPERATOR( get() ) + }; + + // these are not implemented, so's we can catch their use at + // link-time. Leaving them undeclared would open up a comparison + // via operator unspecified-bool-type(). + template + void operator==( const pimpl_ptr &, const pimpl_ptr & ); + template + void operator!=( const pimpl_ptr &, const pimpl_ptr & ); + +#ifndef DOXYGEN_RUN +} // namespace kdtools +#endif + +#endif /* __KDTOOLSCORE__PIMPL_PTR_H__ */ + diff --git a/src/libtomahawk/qtsingleapp/qtlocalpeer.cpp b/src/libtomahawk/qtsingleapp/qtlocalpeer.cpp deleted file mode 100644 index 382d182dc..000000000 --- a/src/libtomahawk/qtsingleapp/qtlocalpeer.cpp +++ /dev/null @@ -1,199 +0,0 @@ -/**************************************************************************** -** -** Copyright (C) 2010 Nokia Corporation and/or its subsidiary(-ies). -** All rights reserved. -** -** Contact: Nokia Corporation (qt-info@nokia.com) -** -** This file is part of a Qt Solutions component. -** -** You may use this file under the terms of the BSD license as follows: -** -** "Redistribution and use in source and binary forms, with or without -** modification, are permitted provided that the following conditions are -** met: -** * Redistributions of source code must retain the above copyright -** notice, this list of conditions and the following disclaimer. -** * Redistributions in binary form must reproduce the above copyright -** notice, this list of conditions and the following disclaimer in -** the documentation and/or other materials provided with the -** distribution. -** * Neither the name of Nokia Corporation and its Subsidiary(-ies) nor -** the names of its contributors may be used to endorse or promote -** products derived from this software without specific prior written -** permission. -** -** THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS -** "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT -** LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR -** A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT -** OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, -** SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT -** LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, -** DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY -** THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT -** (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE -** OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE." -** -****************************************************************************/ - - -#include "qtlocalpeer.h" -#include -#include - -#if defined(Q_OS_WIN) -#include -#include -typedef BOOL(WINAPI*PProcessIdToSessionId)(DWORD,DWORD*); -static PProcessIdToSessionId pProcessIdToSessionId = 0; -#endif -#if defined(Q_OS_UNIX) -#include -#endif - -namespace QtLP_Private { -#include "qtlockedfile.cpp" -#if defined(Q_OS_WIN) -#include "qtlockedfile_win.cpp" -#else -#include "qtlockedfile_unix.cpp" -#endif -} - -const char* QtLocalPeer::ack = "ack"; - -QtLocalPeer::QtLocalPeer(QObject* parent, const QString &appId) - : QObject(parent), id(appId) -{ - QString prefix = id; - if (id.isEmpty()) { - id = QCoreApplication::applicationFilePath(); -#if defined(Q_OS_WIN) - id = id.toLower(); -#endif - prefix = id.section(QLatin1Char('/'), -1); - } - prefix.remove(QRegExp("[^a-zA-Z]")); - prefix.truncate(6); - - QByteArray idc = id.toUtf8(); - quint16 idNum = qChecksum(idc.constData(), idc.size()); - socketName = QLatin1String("qtsingleapp-") + prefix - + QLatin1Char('-') + QString::number(idNum, 16); - -#if defined(Q_OS_WIN) - if (!pProcessIdToSessionId) { - QLibrary lib("kernel32"); - pProcessIdToSessionId = (PProcessIdToSessionId)lib.resolve("ProcessIdToSessionId"); - } - if (pProcessIdToSessionId) { - DWORD sessionId = 0; - pProcessIdToSessionId(GetCurrentProcessId(), &sessionId); - socketName += QLatin1Char('-') + QString::number(sessionId, 16); - } -#else - socketName += QLatin1Char('-') + QString::number(::getuid(), 16); -#endif - - server = new QLocalServer(this); - QString lockName = QDir(QDir::tempPath()).absolutePath() - + QLatin1Char('/') + socketName - + QLatin1String("-lockfile"); - lockFile.setFileName(lockName); - lockFile.open(QIODevice::ReadWrite); -} - - - -bool QtLocalPeer::isClient() -{ - if (lockFile.isLocked()) - return false; - - if (!lockFile.lock(QtLP_Private::QtLockedFile::WriteLock, false)) - return true; - - bool res = server->listen(socketName); -#if defined(Q_OS_UNIX) && (QT_VERSION >= QT_VERSION_CHECK(4,5,0)) - // ### Workaround - if (!res && server->serverError() == QAbstractSocket::AddressInUseError) { - QFile::remove(QDir::cleanPath(QDir::tempPath())+QLatin1Char('/')+socketName); - res = server->listen(socketName); - } -#endif - if (!res) - qWarning("QtSingleCoreApplication: listen on local socket failed, %s", qPrintable(server->errorString())); - QObject::connect(server, SIGNAL(newConnection()), SLOT(receiveConnection())); - return false; -} - - -bool QtLocalPeer::sendMessage(const QString &message, int timeout) -{ - if (!isClient()) - return false; - - QLocalSocket socket; - bool connOk = false; - for(int i = 0; i < 2; i++) { - // Try twice, in case the other instance is just starting up - socket.connectToServer(socketName); - connOk = socket.waitForConnected(timeout/2); - if (connOk || i) - break; - int ms = 250; -#if defined(Q_OS_WIN) - Sleep(DWORD(ms)); -#else - struct timespec ts = { ms / 1000, (ms % 1000) * 1000 * 1000 }; - nanosleep(&ts, NULL); -#endif - } - if (!connOk) - return false; - - QByteArray uMsg(message.toUtf8()); - QDataStream ds(&socket); - ds.writeBytes(uMsg.constData(), uMsg.size()); - bool res = socket.waitForBytesWritten(timeout); - if (res) { - res &= socket.waitForReadyRead(timeout); // wait for ack - if (res) - res &= (socket.read(qstrlen(ack)) == ack); - } - return res; -} - - -void QtLocalPeer::receiveConnection() -{ - QLocalSocket* socket = server->nextPendingConnection(); - if (!socket) - return; - - while (socket->bytesAvailable() < (int)sizeof(quint32)) - socket->waitForReadyRead(); - QDataStream ds(socket); - QByteArray uMsg; - quint32 remaining; - ds >> remaining; - uMsg.resize(remaining); - int got = 0; - char* uMsgBuf = uMsg.data(); - do { - got = ds.readRawData(uMsgBuf, remaining); - remaining -= got; - uMsgBuf += got; - } while (remaining && got >= 0 && socket->waitForReadyRead(2000)); - if (got < 0) { - qWarning("QtLocalPeer: Message reception failed %s", socket->errorString().toLatin1().constData()); - delete socket; - return; - } - QString message(QString::fromUtf8(uMsg)); - socket->write(ack, qstrlen(ack)); - socket->waitForBytesWritten(1000); - delete socket; - emit messageReceived(message); //### (might take a long time to return) -} diff --git a/src/libtomahawk/qtsingleapp/qtlocalpeer.h b/src/libtomahawk/qtsingleapp/qtlocalpeer.h deleted file mode 100644 index 869af2ac2..000000000 --- a/src/libtomahawk/qtsingleapp/qtlocalpeer.h +++ /dev/null @@ -1,72 +0,0 @@ -/**************************************************************************** -** -** Copyright (C) 2010 Nokia Corporation and/or its subsidiary(-ies). -** All rights reserved. -** -** Contact: Nokia Corporation (qt-info@nokia.com) -** -** This file is part of a Qt Solutions component. -** -** You may use this file under the terms of the BSD license as follows: -** -** "Redistribution and use in source and binary forms, with or without -** modification, are permitted provided that the following conditions are -** met: -** * Redistributions of source code must retain the above copyright -** notice, this list of conditions and the following disclaimer. -** * Redistributions in binary form must reproduce the above copyright -** notice, this list of conditions and the following disclaimer in -** the documentation and/or other materials provided with the -** distribution. -** * Neither the name of Nokia Corporation and its Subsidiary(-ies) nor -** the names of its contributors may be used to endorse or promote -** products derived from this software without specific prior written -** permission. -** -** THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS -** "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT -** LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR -** A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT -** OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, -** SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT -** LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, -** DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY -** THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT -** (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE -** OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE." -** -****************************************************************************/ - - -#include -#include -#include - -#include "qtlockedfile.h" - -class QtLocalPeer : public QObject -{ - Q_OBJECT - -public: - QtLocalPeer(QObject *parent = 0, const QString &appId = QString()); - bool isClient(); - bool sendMessage(const QString &message, int timeout); - QString applicationId() const - { return id; } - -Q_SIGNALS: - void messageReceived(const QString &message); - -protected Q_SLOTS: - void receiveConnection(); - -protected: - QString id; - QString socketName; - QLocalServer* server; - QtLP_Private::QtLockedFile lockFile; - -private: - static const char* ack; -}; diff --git a/src/libtomahawk/qtsingleapp/qtlockedfile.cpp b/src/libtomahawk/qtsingleapp/qtlockedfile.cpp deleted file mode 100644 index 3e73ba652..000000000 --- a/src/libtomahawk/qtsingleapp/qtlockedfile.cpp +++ /dev/null @@ -1,192 +0,0 @@ -/**************************************************************************** -** -** Copyright (C) 2010 Nokia Corporation and/or its subsidiary(-ies). -** All rights reserved. -** -** Contact: Nokia Corporation (qt-info@nokia.com) -** -** This file is part of a Qt Solutions component. -** -** You may use this file under the terms of the BSD license as follows: -** -** "Redistribution and use in source and binary forms, with or without -** modification, are permitted provided that the following conditions are -** met: -** * Redistributions of source code must retain the above copyright -** notice, this list of conditions and the following disclaimer. -** * Redistributions in binary form must reproduce the above copyright -** notice, this list of conditions and the following disclaimer in -** the documentation and/or other materials provided with the -** distribution. -** * Neither the name of Nokia Corporation and its Subsidiary(-ies) nor -** the names of its contributors may be used to endorse or promote -** products derived from this software without specific prior written -** permission. -** -** THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS -** "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT -** LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR -** A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT -** OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, -** SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT -** LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, -** DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY -** THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT -** (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE -** OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE." -** -****************************************************************************/ - -#include "qtlockedfile.h" - -/*! - \class QtLockedFile - - \brief The QtLockedFile class extends QFile with advisory locking - functions. - - A file may be locked in read or write mode. Multiple instances of - \e QtLockedFile, created in multiple processes running on the same - machine, may have a file locked in read mode. Exactly one instance - may have it locked in write mode. A read and a write lock cannot - exist simultaneously on the same file. - - The file locks are advisory. This means that nothing prevents - another process from manipulating a locked file using QFile or - file system functions offered by the OS. Serialization is only - guaranteed if all processes that access the file use - QLockedFile. Also, while holding a lock on a file, a process - must not open the same file again (through any API), or locks - can be unexpectedly lost. - - The lock provided by an instance of \e QtLockedFile is released - whenever the program terminates. This is true even when the - program crashes and no destructors are called. -*/ - -/*! \enum QtLockedFile::LockMode - - This enum describes the available lock modes. - - \value ReadLock A read lock. - \value WriteLock A write lock. - \value NoLock Neither a read lock nor a write lock. -*/ - -/*! - Constructs an unlocked \e QtLockedFile object. This constructor - behaves in the same way as \e QFile::QFile(). - - \sa QFile::QFile() -*/ -QtLockedFile::QtLockedFile() - : QFile() -{ -#ifdef Q_OS_WIN - wmutex = 0; - rmutex = 0; -#endif - m_lock_mode = NoLock; -} - -/*! - Constructs an unlocked QtLockedFile object with file \a name. This - constructor behaves in the same way as \e QFile::QFile(const - QString&). - - \sa QFile::QFile() -*/ -QtLockedFile::QtLockedFile(const QString &name) - : QFile(name) -{ -#ifdef Q_OS_WIN - wmutex = 0; - rmutex = 0; -#endif - m_lock_mode = NoLock; -} - -/*! - Opens the file in OpenMode \a mode. - - This is identical to QFile::open(), with the one exception that the - Truncate mode flag is disallowed. Truncation would conflict with the - advisory file locking, since the file would be modified before the - write lock is obtained. If truncation is required, use resize(0) - after obtaining the write lock. - - Returns true if successful; otherwise false. - - \sa QFile::open(), QFile::resize() -*/ -bool QtLockedFile::open(OpenMode mode) -{ - if (mode & QIODevice::Truncate) { - qWarning("QtLockedFile::open(): Truncate mode not allowed."); - return false; - } - return QFile::open(mode); -} - -/*! - Returns \e true if this object has a in read or write lock; - otherwise returns \e false. - - \sa lockMode() -*/ -bool QtLockedFile::isLocked() const -{ - return m_lock_mode != NoLock; -} - -/*! - Returns the type of lock currently held by this object, or \e - QtLockedFile::NoLock. - - \sa isLocked() -*/ -QtLockedFile::LockMode QtLockedFile::lockMode() const -{ - return m_lock_mode; -} - -/*! - \fn bool QtLockedFile::lock(LockMode mode, bool block = true) - - Obtains a lock of type \a mode. The file must be opened before it - can be locked. - - If \a block is true, this function will block until the lock is - aquired. If \a block is false, this function returns \e false - immediately if the lock cannot be aquired. - - If this object already has a lock of type \a mode, this function - returns \e true immediately. If this object has a lock of a - different type than \a mode, the lock is first released and then a - new lock is obtained. - - This function returns \e true if, after it executes, the file is - locked by this object, and \e false otherwise. - - \sa unlock(), isLocked(), lockMode() -*/ - -/*! - \fn bool QtLockedFile::unlock() - - Releases a lock. - - If the object has no lock, this function returns immediately. - - This function returns \e true if, after it executes, the file is - not locked by this object, and \e false otherwise. - - \sa lock(), isLocked(), lockMode() -*/ - -/*! - \fn QtLockedFile::~QtLockedFile() - - Destroys the \e QtLockedFile object. If any locks were held, they - are released. -*/ diff --git a/src/libtomahawk/qtsingleapp/qtlockedfile.h b/src/libtomahawk/qtsingleapp/qtlockedfile.h deleted file mode 100644 index 07a42bffb..000000000 --- a/src/libtomahawk/qtsingleapp/qtlockedfile.h +++ /dev/null @@ -1,96 +0,0 @@ -/**************************************************************************** -** -** Copyright (C) 2010 Nokia Corporation and/or its subsidiary(-ies). -** All rights reserved. -** -** Contact: Nokia Corporation (qt-info@nokia.com) -** -** This file is part of a Qt Solutions component. -** -** You may use this file under the terms of the BSD license as follows: -** -** "Redistribution and use in source and binary forms, with or without -** modification, are permitted provided that the following conditions are -** met: -** * Redistributions of source code must retain the above copyright -** notice, this list of conditions and the following disclaimer. -** * Redistributions in binary form must reproduce the above copyright -** notice, this list of conditions and the following disclaimer in -** the documentation and/or other materials provided with the -** distribution. -** * Neither the name of Nokia Corporation and its Subsidiary(-ies) nor -** the names of its contributors may be used to endorse or promote -** products derived from this software without specific prior written -** permission. -** -** THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS -** "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT -** LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR -** A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT -** OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, -** SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT -** LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, -** DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY -** THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT -** (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE -** OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE." -** -****************************************************************************/ - -#ifndef QTLOCKEDFILE_H -#define QTLOCKEDFILE_H - -#include -#ifdef Q_OS_WIN -#include -#endif - -#if defined(Q_WS_WIN) -# if !defined(QT_QTLOCKEDFILE_EXPORT) && !defined(QT_QTLOCKEDFILE_IMPORT) -# define QT_QTLOCKEDFILE_EXPORT -# elif defined(QT_QTLOCKEDFILE_IMPORT) -# if defined(QT_QTLOCKEDFILE_EXPORT) -# undef QT_QTLOCKEDFILE_EXPORT -# endif -# define QT_QTLOCKEDFILE_EXPORT __declspec(dllimport) -# elif defined(QT_QTLOCKEDFILE_EXPORT) -# undef QT_QTLOCKEDFILE_EXPORT -# define QT_QTLOCKEDFILE_EXPORT __declspec(dllexport) -# endif -#else -# define QT_QTLOCKEDFILE_EXPORT -#endif - -namespace QtLP_Private { - -class QT_QTLOCKEDFILE_EXPORT QtLockedFile : public QFile -{ -public: - enum LockMode { NoLock = 0, ReadLock, WriteLock }; - - QtLockedFile(); - QtLockedFile(const QString &name); - ~QtLockedFile(); - - bool open(OpenMode mode); - - bool lock(LockMode mode, bool block = true); - bool unlock(); - bool isLocked() const; - LockMode lockMode() const; - -private: -#ifdef Q_OS_WIN - Qt::HANDLE wmutex; - Qt::HANDLE rmutex; - QVector rmutexes; - QString mutexname; - - Qt::HANDLE getMutexHandle(int idx, bool doCreate); - bool waitMutex(Qt::HANDLE mutex, bool doBlock); - -#endif - LockMode m_lock_mode; -}; -} -#endif diff --git a/src/libtomahawk/qtsingleapp/qtlockedfile_unix.cpp b/src/libtomahawk/qtsingleapp/qtlockedfile_unix.cpp deleted file mode 100644 index 715c7d9b1..000000000 --- a/src/libtomahawk/qtsingleapp/qtlockedfile_unix.cpp +++ /dev/null @@ -1,114 +0,0 @@ -/**************************************************************************** -** -** Copyright (C) 2010 Nokia Corporation and/or its subsidiary(-ies). -** All rights reserved. -** -** Contact: Nokia Corporation (qt-info@nokia.com) -** -** This file is part of a Qt Solutions component. -** -** You may use this file under the terms of the BSD license as follows: -** -** "Redistribution and use in source and binary forms, with or without -** modification, are permitted provided that the following conditions are -** met: -** * Redistributions of source code must retain the above copyright -** notice, this list of conditions and the following disclaimer. -** * Redistributions in binary form must reproduce the above copyright -** notice, this list of conditions and the following disclaimer in -** the documentation and/or other materials provided with the -** distribution. -** * Neither the name of Nokia Corporation and its Subsidiary(-ies) nor -** the names of its contributors may be used to endorse or promote -** products derived from this software without specific prior written -** permission. -** -** THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS -** "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT -** LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR -** A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT -** OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, -** SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT -** LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, -** DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY -** THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT -** (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE -** OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE." -** -****************************************************************************/ - -#include -#include -#include -#include - -#include "qtlockedfile.h" - -bool QtLockedFile::lock(LockMode mode, bool block) -{ - if (!isOpen()) { - qWarning("QtLockedFile::lock(): file is not opened"); - return false; - } - - if (mode == NoLock) - return unlock(); - - if (mode == m_lock_mode) - return true; - - if (m_lock_mode != NoLock) - unlock(); - - struct flock fl; - fl.l_whence = SEEK_SET; - fl.l_start = 0; - fl.l_len = 0; - fl.l_type = (mode == ReadLock) ? F_RDLCK : F_WRLCK; - int cmd = block ? F_SETLKW : F_SETLK; - int ret = fcntl(handle(), cmd, &fl); - - if (ret == -1) { - if (errno != EINTR && errno != EAGAIN) - qWarning("QtLockedFile::lock(): fcntl: %s", strerror(errno)); - return false; - } - - - m_lock_mode = mode; - return true; -} - - -bool QtLockedFile::unlock() -{ - if (!isOpen()) { - qWarning("QtLockedFile::unlock(): file is not opened"); - return false; - } - - if (!isLocked()) - return true; - - struct flock fl; - fl.l_whence = SEEK_SET; - fl.l_start = 0; - fl.l_len = 0; - fl.l_type = F_UNLCK; - int ret = fcntl(handle(), F_SETLKW, &fl); - - if (ret == -1) { - qWarning("QtLockedFile::lock(): fcntl: %s", strerror(errno)); - return false; - } - - m_lock_mode = NoLock; - return true; -} - -QtLockedFile::~QtLockedFile() -{ - if (isOpen()) - unlock(); -} - diff --git a/src/libtomahawk/qtsingleapp/qtlockedfile_win.cpp b/src/libtomahawk/qtsingleapp/qtlockedfile_win.cpp deleted file mode 100644 index 8090470cd..000000000 --- a/src/libtomahawk/qtsingleapp/qtlockedfile_win.cpp +++ /dev/null @@ -1,208 +0,0 @@ -/**************************************************************************** -** -** Copyright (C) 2010 Nokia Corporation and/or its subsidiary(-ies). -** All rights reserved. -** -** Contact: Nokia Corporation (qt-info@nokia.com) -** -** This file is part of a Qt Solutions component. -** -** You may use this file under the terms of the BSD license as follows: -** -** "Redistribution and use in source and binary forms, with or without -** modification, are permitted provided that the following conditions are -** met: -** * Redistributions of source code must retain the above copyright -** notice, this list of conditions and the following disclaimer. -** * Redistributions in binary form must reproduce the above copyright -** notice, this list of conditions and the following disclaimer in -** the documentation and/or other materials provided with the -** distribution. -** * Neither the name of Nokia Corporation and its Subsidiary(-ies) nor -** the names of its contributors may be used to endorse or promote -** products derived from this software without specific prior written -** permission. -** -** THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS -** "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT -** LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR -** A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT -** OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, -** SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT -** LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, -** DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY -** THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT -** (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE -** OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE." -** -****************************************************************************/ - -#include "qtlockedfile.h" -#include -#include - -#define MUTEX_PREFIX "QtLockedFile mutex " -// Maximum number of concurrent read locks. Must not be greater than MAXIMUM_WAIT_OBJECTS -#define MAX_READERS MAXIMUM_WAIT_OBJECTS - -#define TCHAR WCHAR - -Qt::HANDLE QtLockedFile::getMutexHandle(int idx, bool doCreate) -{ - if (mutexname.isEmpty()) { - QFileInfo fi(*this); - mutexname = QString::fromLatin1(MUTEX_PREFIX) - + fi.absoluteFilePath().toLower(); - } - QString mname(mutexname); - if (idx >= 0) - mname += QString::number(idx); - - Qt::HANDLE mutex; - if (doCreate) { - QT_WA( { mutex = CreateMutexW(NULL, FALSE, (TCHAR*)mname.utf16()); }, - { mutex = CreateMutexA(NULL, FALSE, mname.toLocal8Bit().constData()); } ); - if (!mutex) { - qErrnoWarning("QtLockedFile::lock(): CreateMutex failed"); - return 0; - } - } - else { - QT_WA( { mutex = OpenMutexW(SYNCHRONIZE | MUTEX_MODIFY_STATE, FALSE, (TCHAR*)mname.utf16()); }, - { mutex = OpenMutexA(SYNCHRONIZE | MUTEX_MODIFY_STATE, FALSE, mname.toLocal8Bit().constData()); } ); - if (!mutex) { - if (GetLastError() != ERROR_FILE_NOT_FOUND) - qErrnoWarning("QtLockedFile::lock(): OpenMutex failed"); - return 0; - } - } - return mutex; -} - -bool QtLockedFile::waitMutex(Qt::HANDLE mutex, bool doBlock) -{ - Q_ASSERT(mutex); - DWORD res = WaitForSingleObject(mutex, doBlock ? INFINITE : 0); - switch (res) { - case WAIT_OBJECT_0: - case WAIT_ABANDONED: - return true; - break; - case WAIT_TIMEOUT: - break; - default: - qErrnoWarning("QtLockedFile::lock(): WaitForSingleObject failed"); - } - return false; -} - - - -bool QtLockedFile::lock(LockMode mode, bool block) -{ - if (!isOpen()) { - qWarning("QtLockedFile::lock(): file is not opened"); - return false; - } - - if (mode == NoLock) - return unlock(); - - if (mode == m_lock_mode) - return true; - - if (m_lock_mode != NoLock) - unlock(); - - if (!wmutex && !(wmutex = getMutexHandle(-1, true))) - return false; - - if (!waitMutex(wmutex, block)) - return false; - - if (mode == ReadLock) { - int idx = 0; - for (; idx < MAX_READERS; idx++) { - rmutex = getMutexHandle(idx, false); - if (!rmutex || waitMutex(rmutex, false)) - break; - CloseHandle(rmutex); - } - bool ok = true; - if (idx >= MAX_READERS) { - qWarning("QtLockedFile::lock(): too many readers"); - rmutex = 0; - ok = false; - } - else if (!rmutex) { - rmutex = getMutexHandle(idx, true); - if (!rmutex || !waitMutex(rmutex, false)) - ok = false; - } - if (!ok && rmutex) { - CloseHandle(rmutex); - rmutex = 0; - } - ReleaseMutex(wmutex); - if (!ok) - return false; - } - else { - Q_ASSERT(rmutexes.isEmpty()); - for (int i = 0; i < MAX_READERS; i++) { - Qt::HANDLE mutex = getMutexHandle(i, false); - if (mutex) - rmutexes.append(mutex); - } - if (rmutexes.size()) { - DWORD res = WaitForMultipleObjects(rmutexes.size(), rmutexes.constData(), - TRUE, block ? INFINITE : 0); - if (res != WAIT_OBJECT_0 && res != WAIT_ABANDONED) { - if (res != WAIT_TIMEOUT) - qErrnoWarning("QtLockedFile::lock(): WaitForMultipleObjects failed"); - m_lock_mode = WriteLock; // trick unlock() to clean up - semiyucky - unlock(); - return false; - } - } - } - - m_lock_mode = mode; - return true; -} - -bool QtLockedFile::unlock() -{ - if (!isOpen()) { - qWarning("QtLockedFile::unlock(): file is not opened"); - return false; - } - - if (!isLocked()) - return true; - - if (m_lock_mode == ReadLock) { - ReleaseMutex(rmutex); - CloseHandle(rmutex); - rmutex = 0; - } - else { - foreach(Qt::HANDLE mutex, rmutexes) { - ReleaseMutex(mutex); - CloseHandle(mutex); - } - rmutexes.clear(); - ReleaseMutex(wmutex); - } - - m_lock_mode = QtLockedFile::NoLock; - return true; -} - -QtLockedFile::~QtLockedFile() -{ - if (isOpen()) - unlock(); - if (wmutex) - CloseHandle(wmutex); -} diff --git a/src/libtomahawk/qtsingleapp/qtsingleapplication.cpp b/src/libtomahawk/qtsingleapp/qtsingleapplication.cpp deleted file mode 100644 index 5a8f1b035..000000000 --- a/src/libtomahawk/qtsingleapp/qtsingleapplication.cpp +++ /dev/null @@ -1,344 +0,0 @@ -/**************************************************************************** -** -** Copyright (C) 2010 Nokia Corporation and/or its subsidiary(-ies). -** All rights reserved. -** -** Contact: Nokia Corporation (qt-info@nokia.com) -** -** This file is part of a Qt Solutions component. -** -** You may use this file under the terms of the BSD license as follows: -** -** "Redistribution and use in source and binary forms, with or without -** modification, are permitted provided that the following conditions are -** met: -** * Redistributions of source code must retain the above copyright -** notice, this list of conditions and the following disclaimer. -** * Redistributions in binary form must reproduce the above copyright -** notice, this list of conditions and the following disclaimer in -** the documentation and/or other materials provided with the -** distribution. -** * Neither the name of Nokia Corporation and its Subsidiary(-ies) nor -** the names of its contributors may be used to endorse or promote -** products derived from this software without specific prior written -** permission. -** -** THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS -** "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT -** LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR -** A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT -** OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, -** SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT -** LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, -** DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY -** THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT -** (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE -** OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE." -** -****************************************************************************/ - - -#include "qtsingleapplication.h" -#include "qtlocalpeer.h" -#include - - -/*! - \class QtSingleApplication qtsingleapplication.h - \brief The QtSingleApplication class provides an API to detect and - communicate with running instances of an application. - - This class allows you to create applications where only one - instance should be running at a time. I.e., if the user tries to - launch another instance, the already running instance will be - activated instead. Another usecase is a client-server system, - where the first started instance will assume the role of server, - and the later instances will act as clients of that server. - - By default, the full path of the executable file is used to - determine whether two processes are instances of the same - application. You can also provide an explicit identifier string - that will be compared instead. - - The application should create the QtSingleApplication object early - in the startup phase, and call isRunning() to find out if another - instance of this application is already running. If isRunning() - returns false, it means that no other instance is running, and - this instance has assumed the role as the running instance. In - this case, the application should continue with the initialization - of the application user interface before entering the event loop - with exec(), as normal. - - The messageReceived() signal will be emitted when the running - application receives messages from another instance of the same - application. When a message is received it might be helpful to the - user to raise the application so that it becomes visible. To - facilitate this, QtSingleApplication provides the - setActivationWindow() function and the activateWindow() slot. - - If isRunning() returns true, another instance is already - running. It may be alerted to the fact that another instance has - started by using the sendMessage() function. Also data such as - startup parameters (e.g. the name of the file the user wanted this - new instance to open) can be passed to the running instance with - this function. Then, the application should terminate (or enter - client mode). - - If isRunning() returns true, but sendMessage() fails, that is an - indication that the running instance is frozen. - - Here's an example that shows how to convert an existing - application to use QtSingleApplication. It is very simple and does - not make use of all QtSingleApplication's functionality (see the - examples for that). - - \code - // Original - int main(int argc, char **argv) - { - QApplication app(argc, argv); - - MyMainWidget mmw; - mmw.show(); - return app.exec(); - } - - // Single instance - int main(int argc, char **argv) - { - QtSingleApplication app(argc, argv); - - if (app.isRunning()) - return !app.sendMessage(someDataString); - - MyMainWidget mmw; - app.setActivationWindow(&mmw); - mmw.show(); - return app.exec(); - } - \endcode - - Once this QtSingleApplication instance is destroyed (normally when - the process exits or crashes), when the user next attempts to run the - application this instance will not, of course, be encountered. The - next instance to call isRunning() or sendMessage() will assume the - role as the new running instance. - - For console (non-GUI) applications, QtSingleCoreApplication may be - used instead of this class, to avoid the dependency on the QtGui - library. - - \sa QtSingleCoreApplication -*/ - - -void QtSingleApplication::sysInit(const QString &appId) -{ - actWin = 0; - peer = new QtLocalPeer(this, appId); - connect(peer, SIGNAL(messageReceived(const QString&)), SIGNAL(messageReceived(const QString&))); -} - - -/*! - Creates a QtSingleApplication object. The application identifier - will be QCoreApplication::applicationFilePath(). \a argc, \a - argv, and \a GUIenabled are passed on to the QAppliation constructor. - - If you are creating a console application (i.e. setting \a - GUIenabled to false), you may consider using - QtSingleCoreApplication instead. -*/ - -QtSingleApplication::QtSingleApplication(int &argc, char **argv, bool GUIenabled) - : QApplication(argc, argv, GUIenabled) -{ - sysInit(); -} - - -/*! - Creates a QtSingleApplication object with the application - identifier \a appId. \a argc and \a argv are passed on to the - QAppliation constructor. -*/ - -QtSingleApplication::QtSingleApplication(const QString &appId, int &argc, char **argv) - : QApplication(argc, argv) -{ - sysInit(appId); -} - - -/*! - Creates a QtSingleApplication object. The application identifier - will be QCoreApplication::applicationFilePath(). \a argc, \a - argv, and \a type are passed on to the QAppliation constructor. -*/ -QtSingleApplication::QtSingleApplication(int &argc, char **argv, Type type) - : QApplication(argc, argv, type) -{ - sysInit(); -} - - -#if defined(Q_WS_X11) -/*! - Special constructor for X11, ref. the documentation of - QApplication's corresponding constructor. The application identifier - will be QCoreApplication::applicationFilePath(). \a dpy, \a visual, - and \a cmap are passed on to the QApplication constructor. -*/ -QtSingleApplication::QtSingleApplication(Display* dpy, Qt::HANDLE visual, Qt::HANDLE cmap) - : QApplication(dpy, visual, cmap) -{ - sysInit(); -} - -/*! - Special constructor for X11, ref. the documentation of - QApplication's corresponding constructor. The application identifier - will be QCoreApplication::applicationFilePath(). \a dpy, \a argc, \a - argv, \a visual, and \a cmap are passed on to the QApplication - constructor. -*/ -QtSingleApplication::QtSingleApplication(Display *dpy, int &argc, char **argv, Qt::HANDLE visual, Qt::HANDLE cmap) - : QApplication(dpy, argc, argv, visual, cmap) -{ - sysInit(); -} - -/*! - Special constructor for X11, ref. the documentation of - QApplication's corresponding constructor. The application identifier - will be \a appId. \a dpy, \a argc, \a - argv, \a visual, and \a cmap are passed on to the QApplication - constructor. -*/ -QtSingleApplication::QtSingleApplication(Display* dpy, const QString &appId, int argc, char **argv, Qt::HANDLE visual, Qt::HANDLE cmap) - : QApplication(dpy, argc, argv, visual, cmap) -{ - sysInit(appId); -} -#endif - - -/*! - Returns true if another instance of this application is running; - otherwise false. - - This function does not find instances of this application that are - being run by a different user (on Windows: that are running in - another session). - - \sa sendMessage() -*/ - -bool QtSingleApplication::isRunning() -{ - return peer->isClient(); -} - - -/*! - Tries to send the text \a message to the currently running - instance. The QtSingleApplication object in the running instance - will emit the messageReceived() signal when it receives the - message. - - This function returns true if the message has been sent to, and - processed by, the current instance. If there is no instance - currently running, or if the running instance fails to process the - message within \a timeout milliseconds, this function return false. - - \sa isRunning(), messageReceived() -*/ -bool QtSingleApplication::sendMessage(const QString &message, int timeout) -{ - return peer->sendMessage(message, timeout); -} - - -/*! - Returns the application identifier. Two processes with the same - identifier will be regarded as instances of the same application. -*/ -QString QtSingleApplication::id() const -{ - return peer->applicationId(); -} - - -/*! - Sets the activation window of this application to \a aw. The - activation window is the widget that will be activated by - activateWindow(). This is typically the application's main window. - - If \a activateOnMessage is true (the default), the window will be - activated automatically every time a message is received, just prior - to the messageReceived() signal being emitted. - - \sa activateWindow(), messageReceived() -*/ - -void QtSingleApplication::setActivationWindow(QWidget* aw, bool activateOnMessage) -{ - actWin = aw; - if (activateOnMessage) - connect(peer, SIGNAL(messageReceived(const QString&)), this, SLOT(activateWindow())); - else - disconnect(peer, SIGNAL(messageReceived(const QString&)), this, SLOT(activateWindow())); -} - - -/*! - Returns the applications activation window if one has been set by - calling setActivationWindow(), otherwise returns 0. - - \sa setActivationWindow() -*/ -QWidget* QtSingleApplication::activationWindow() const -{ - return actWin; -} - - -/*! - De-minimizes, raises, and activates this application's activation window. - This function does nothing if no activation window has been set. - - This is a convenience function to show the user that this - application instance has been activated when he has tried to start - another instance. - - This function should typically be called in response to the - messageReceived() signal. By default, that will happen - automatically, if an activation window has been set. - - \sa setActivationWindow(), messageReceived(), initialize() -*/ -void QtSingleApplication::activateWindow() -{ - if (actWin) { - actWin->setWindowState(actWin->windowState() & ~Qt::WindowMinimized); - actWin->raise(); - actWin->activateWindow(); - } -} - - -/*! - \fn void QtSingleApplication::messageReceived(const QString& message) - - This signal is emitted when the current instance receives a \a - message from another instance of this application. - - \sa sendMessage(), setActivationWindow(), activateWindow() -*/ - - -/*! - \fn void QtSingleApplication::initialize(bool dummy = true) - - \obsolete -*/ diff --git a/src/libtomahawk/qtsingleapp/qtsingleapplication.h b/src/libtomahawk/qtsingleapp/qtsingleapplication.h deleted file mode 100644 index c696d60ce..000000000 --- a/src/libtomahawk/qtsingleapp/qtsingleapplication.h +++ /dev/null @@ -1,84 +0,0 @@ -/**************************************************************************** -** -** Copyright (C) 2010 Nokia Corporation and/or its subsidiary(-ies). -** All rights reserved. -** -** Contact: Nokia Corporation (qt-info@nokia.com) -** -** This file is part of a Qt Solutions component. -** -** You may use this file under the terms of the BSD license as follows: -** -** "Redistribution and use in source and binary forms, with or without -** modification, are permitted provided that the following conditions are -** met: -** * Redistributions of source code must retain the above copyright -** notice, this list of conditions and the following disclaimer. -** * Redistributions in binary form must reproduce the above copyright -** notice, this list of conditions and the following disclaimer in -** the documentation and/or other materials provided with the -** distribution. -** * Neither the name of Nokia Corporation and its Subsidiary(-ies) nor -** the names of its contributors may be used to endorse or promote -** products derived from this software without specific prior written -** permission. -** -** THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS -** "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT -** LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR -** A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT -** OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, -** SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT -** LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, -** DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY -** THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT -** (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE -** OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE." -** -****************************************************************************/ - - -#include - -#include "dllmacro.h" - -class QtLocalPeer; - -class DLLEXPORT QtSingleApplication : public QApplication -{ - Q_OBJECT - -public: - QtSingleApplication(int &argc, char **argv, bool GUIenabled = true); - QtSingleApplication(const QString &id, int &argc, char **argv); - QtSingleApplication(int &argc, char **argv, Type type); -#if defined(Q_WS_X11) - QtSingleApplication(Display* dpy, Qt::HANDLE visual = 0, Qt::HANDLE colormap = 0); - QtSingleApplication(Display *dpy, int &argc, char **argv, Qt::HANDLE visual = 0, Qt::HANDLE cmap= 0); - QtSingleApplication(Display* dpy, const QString &appId, int argc, char **argv, Qt::HANDLE visual = 0, Qt::HANDLE colormap = 0); -#endif - - bool isRunning(); - QString id() const; - - void setActivationWindow(QWidget* aw, bool activateOnMessage = true); - QWidget* activationWindow() const; - - // Obsolete: - void initialize(bool dummy = true) - { isRunning(); Q_UNUSED(dummy) } - -public Q_SLOTS: - bool sendMessage(const QString &message, int timeout = 5000); - void activateWindow(); - - -Q_SIGNALS: - void messageReceived(const QString &message); - - -private: - void sysInit(const QString &appId = QString()); - QtLocalPeer *peer; - QWidget *actWin; -}; diff --git a/src/libtomahawk/qtsingleapp/qtsinglecoreapplication.cpp b/src/libtomahawk/qtsingleapp/qtsinglecoreapplication.cpp deleted file mode 100644 index cf607710e..000000000 --- a/src/libtomahawk/qtsingleapp/qtsinglecoreapplication.cpp +++ /dev/null @@ -1,148 +0,0 @@ -/**************************************************************************** -** -** Copyright (C) 2010 Nokia Corporation and/or its subsidiary(-ies). -** All rights reserved. -** -** Contact: Nokia Corporation (qt-info@nokia.com) -** -** This file is part of a Qt Solutions component. -** -** You may use this file under the terms of the BSD license as follows: -** -** "Redistribution and use in source and binary forms, with or without -** modification, are permitted provided that the following conditions are -** met: -** * Redistributions of source code must retain the above copyright -** notice, this list of conditions and the following disclaimer. -** * Redistributions in binary form must reproduce the above copyright -** notice, this list of conditions and the following disclaimer in -** the documentation and/or other materials provided with the -** distribution. -** * Neither the name of Nokia Corporation and its Subsidiary(-ies) nor -** the names of its contributors may be used to endorse or promote -** products derived from this software without specific prior written -** permission. -** -** THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS -** "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT -** LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR -** A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT -** OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, -** SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT -** LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, -** DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY -** THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT -** (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE -** OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE." -** -****************************************************************************/ - - -#include "qtsinglecoreapplication.h" -#include "qtlocalpeer.h" - -/*! - \class QtSingleCoreApplication qtsinglecoreapplication.h - \brief A variant of the QtSingleApplication class for non-GUI applications. - - This class is a variant of QtSingleApplication suited for use in - console (non-GUI) applications. It is an extension of - QCoreApplication (instead of QApplication). It does not require - the QtGui library. - - The API and usage is identical to QtSingleApplication, except that - functions relating to the "activation window" are not present, for - obvious reasons. Please refer to the QtSingleApplication - documentation for explanation of the usage. - - A QtSingleCoreApplication instance can communicate to a - QtSingleApplication instance if they share the same application - id. Hence, this class can be used to create a light-weight - command-line tool that sends commands to a GUI application. - - \sa QtSingleApplication -*/ - -/*! - Creates a QtSingleCoreApplication object. The application identifier - will be QCoreApplication::applicationFilePath(). \a argc and \a - argv are passed on to the QCoreAppliation constructor. -*/ - -QtSingleCoreApplication::QtSingleCoreApplication(int &argc, char **argv) - : QCoreApplication(argc, argv) -{ - peer = new QtLocalPeer(this); - connect(peer, SIGNAL(messageReceived(const QString&)), SIGNAL(messageReceived(const QString&))); -} - - -/*! - Creates a QtSingleCoreApplication object with the application - identifier \a appId. \a argc and \a argv are passed on to the - QCoreAppliation constructor. -*/ -QtSingleCoreApplication::QtSingleCoreApplication(const QString &appId, int &argc, char **argv) - : QCoreApplication(argc, argv) -{ - peer = new QtLocalPeer(this, appId); - connect(peer, SIGNAL(messageReceived(const QString&)), SIGNAL(messageReceived(const QString&))); -} - - -/*! - Returns true if another instance of this application is running; - otherwise false. - - This function does not find instances of this application that are - being run by a different user (on Windows: that are running in - another session). - - \sa sendMessage() -*/ - -bool QtSingleCoreApplication::isRunning() -{ - return peer->isClient(); -} - - -/*! - Tries to send the text \a message to the currently running - instance. The QtSingleCoreApplication object in the running instance - will emit the messageReceived() signal when it receives the - message. - - This function returns true if the message has been sent to, and - processed by, the current instance. If there is no instance - currently running, or if the running instance fails to process the - message within \a timeout milliseconds, this function return false. - - \sa isRunning(), messageReceived() -*/ - -bool QtSingleCoreApplication::sendMessage(const QString &message, int timeout) -{ - return peer->sendMessage(message, timeout); -} - - -/*! - Returns the application identifier. Two processes with the same - identifier will be regarded as instances of the same application. -*/ - -QString QtSingleCoreApplication::id() const -{ - return peer->applicationId(); -} - - -/*! - \fn void QtSingleCoreApplication::messageReceived(const QString& message) - - This signal is emitted when the current instance receives a \a - message from another instance of this application. - - \sa sendMessage() -*/ diff --git a/src/libtomahawk/qtsingleapp/qtsinglecoreapplication.h b/src/libtomahawk/qtsingleapp/qtsinglecoreapplication.h deleted file mode 100644 index ef529a8f6..000000000 --- a/src/libtomahawk/qtsingleapp/qtsinglecoreapplication.h +++ /dev/null @@ -1,66 +0,0 @@ -/**************************************************************************** -** -** Copyright (C) 2010 Nokia Corporation and/or its subsidiary(-ies). -** All rights reserved. -** -** Contact: Nokia Corporation (qt-info@nokia.com) -** -** This file is part of a Qt Solutions component. -** -** You may use this file under the terms of the BSD license as follows: -** -** "Redistribution and use in source and binary forms, with or without -** modification, are permitted provided that the following conditions are -** met: -** * Redistributions of source code must retain the above copyright -** notice, this list of conditions and the following disclaimer. -** * Redistributions in binary form must reproduce the above copyright -** notice, this list of conditions and the following disclaimer in -** the documentation and/or other materials provided with the -** distribution. -** * Neither the name of Nokia Corporation and its Subsidiary(-ies) nor -** the names of its contributors may be used to endorse or promote -** products derived from this software without specific prior written -** permission. -** -** THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS -** "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT -** LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR -** A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT -** OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, -** SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT -** LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, -** DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY -** THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT -** (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE -** OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE." -** -****************************************************************************/ - - -#include - -class QtLocalPeer; - -class QtSingleCoreApplication : public QCoreApplication -{ - Q_OBJECT - -public: - QtSingleCoreApplication(int &argc, char **argv); - QtSingleCoreApplication(const QString &id, int &argc, char **argv); - - bool isRunning(); - QString id() const; - -public Q_SLOTS: - bool sendMessage(const QString &message, int timeout = 5000); - - -Q_SIGNALS: - void messageReceived(const QString &message); - - -private: - QtLocalPeer* peer; -}; diff --git a/src/main.cpp b/src/main.cpp index 916ffbb2e..415be1d15 100644 --- a/src/main.cpp +++ b/src/main.cpp @@ -25,6 +25,8 @@ #endif #include + +#include "kdsingleapplicationguard/kdsingleapplicationguard.h" int main( int argc, char *argv[] ) { @@ -38,15 +40,11 @@ main( int argc, char *argv[] ) AEInstallEventHandler( 'GURL', 'GURL', h, 0, false ); #endif - try - { - TomahawkApp a( argc, argv ); - return a.exec(); - } - catch( const std::runtime_error& e ) - { - return 0; - } + TomahawkApp a( argc, argv ); + KDSingleApplicationGuard guard( &a, KDSingleApplicationGuard::AutoKillOtherInstances ); + QObject::connect( &guard, SIGNAL( instanceStarted( KDSingleApplicationGuard::Instance ) ), &a, SLOT( instanceStarted( KDSingleApplicationGuard::Instance ) ) ); + + return a.exec(); } #ifdef Q_WS_MAC diff --git a/src/tomahawkapp.cpp b/src/tomahawkapp.cpp index 7a94f11a0..d668cc012 100644 --- a/src/tomahawkapp.cpp +++ b/src/tomahawkapp.cpp @@ -151,14 +151,6 @@ TomahawkApp::TomahawkApp( int& argc, char *argv[] ) { qsrand( QTime( 0, 0, 0 ).secsTo( QTime::currentTime() ) ); - // send the first arg to an already running instance, but don't open twice no matter what - if( ( argc > 1 && sendMessage( argv[ 1 ] ) ) || sendMessage( "" ) ) { - qDebug() << "Sent message, already exists"; - throw runtime_error( "Already Running" ); - } - - connect( this, SIGNAL( messageReceived( QString ) ), this, SLOT( messageReceived( QString ) ) ); - #ifdef TOMAHAWK_HEADLESS m_headless = true; #else @@ -529,13 +521,15 @@ TomahawkApp::loadUrl( const QString& url ) void -TomahawkApp::messageReceived( const QString& msg ) +TomahawkApp::instanceStarted( KDSingleApplicationGuard::Instance instance ) { - qDebug() << "MESSAGE RECEIVED" << msg; - if( msg.isEmpty() ) { + qDebug() << "INSTANCE STARTED!" << instance.pid << instance.arguments; + + if( instance.arguments.size() < 2 ) + { return; } - loadUrl( msg ); + loadUrl( instance.arguments.at( 1 ) ); } diff --git a/thirdparty/jreen b/thirdparty/jreen index 040ca3f3c..126ef9d96 160000 --- a/thirdparty/jreen +++ b/thirdparty/jreen @@ -1 +1 @@ -Subproject commit 040ca3f3cb9b30b4845fc23054c833fda4717460 +Subproject commit 126ef9d96bf774b9808a16dd8c94001af408528b From 37f29966a88d656c11d9e8f2a137697c0c7778b8 Mon Sep 17 00:00:00 2001 From: Leo Franchi Date: Sat, 2 Apr 2011 19:34:24 -0400 Subject: [PATCH 123/329] use our export macro that works on windows :) --- .../kdsingleapplicationguard/kdsingleapplicationguard.h | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/src/libtomahawk/kdsingleapplicationguard/kdsingleapplicationguard.h b/src/libtomahawk/kdsingleapplicationguard/kdsingleapplicationguard.h index f649ef836..14706e4d0 100644 --- a/src/libtomahawk/kdsingleapplicationguard/kdsingleapplicationguard.h +++ b/src/libtomahawk/kdsingleapplicationguard/kdsingleapplicationguard.h @@ -5,6 +5,7 @@ #include #include "pimpl_ptr.h" +#include "dllmacro.h" class QCoreApplication; @@ -12,7 +13,7 @@ class QCoreApplication; void SIGINT_handler( int sig ); #endif -class KDTOOLSCORE_EXPORT KDSingleApplicationGuard : public QObject +class DLLEXPORT KDSingleApplicationGuard : public QObject { Q_OBJECT #ifndef Q_WS_WIN From 40d4636352c72d15e3bc42433847ab1d2de4afab Mon Sep 17 00:00:00 2001 From: Leo Franchi Date: Tue, 22 Mar 2011 21:33:27 -0400 Subject: [PATCH 124/329] tomahawk:// handler stuff on windows --- admin/win/nsi/tomahawk.nsi | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/admin/win/nsi/tomahawk.nsi b/admin/win/nsi/tomahawk.nsi index e9ba96112..28bdb4c59 100644 --- a/admin/win/nsi/tomahawk.nsi +++ b/admin/win/nsi/tomahawk.nsi @@ -457,6 +457,12 @@ Section -post WriteRegDWORD HKLM "Software\Microsoft\Windows\CurrentVersion\Uninstall\Tomahawk" "NoModify" "1" WriteRegDWORD HKLM "Software\Microsoft\Windows\CurrentVersion\Uninstall\Tomahawk" "NoRepair" "1" + ; Register tomahawk:// protocol handler + WriteRegStr HKCR "tomahawk" "" "URL: Tomahawk Protocol" + WriteRegStr HKCR "tomahawk\DefaultIcon" "" $INSTDIR\tomahawk.exe,1 + WriteRegStr HKCR "tomahawk\shell" "" "open" + WriteRegStr HKCR "tomahawk\shell\open\command" "" '"$INSTDIR\tomahawk.exe" "%1"' + SetDetailsPrint textonly DetailPrint "Finsihed." SectionEnd @@ -516,6 +522,8 @@ Section Uninstall DeleteRegValue HKLM "Software\Tomahawk" "" DeleteRegKey HKLM "Software\Tomahawk" + DeleteRegKey HKCR "tomahawk" + ;Start menu shortcuts. !ifdef OPTION_SECTION_SC_START_MENU SetShellVarContext all From b625b9a2658fd84b6ac1f2f45bcd84020dd12404 Mon Sep 17 00:00:00 2001 From: Christian Muehlhaeuser Date: Sun, 3 Apr 2011 10:23:05 +0200 Subject: [PATCH 125/329] * Unthread QtScriptResolver. WebKit is too lame for us. --- src/resolvers/qtscriptresolver.cpp | 125 ++++++----------------------- src/resolvers/qtscriptresolver.h | 43 ++-------- 2 files changed, 32 insertions(+), 136 deletions(-) diff --git a/src/resolvers/qtscriptresolver.cpp b/src/resolvers/qtscriptresolver.cpp index a19147b14..4a23439e6 100644 --- a/src/resolvers/qtscriptresolver.cpp +++ b/src/resolvers/qtscriptresolver.cpp @@ -32,81 +32,11 @@ QtScriptResolver::QtScriptResolver( const QString& scriptPath ) { qDebug() << Q_FUNC_INFO << scriptPath; - m_thread = new ScriptThread( scriptPath, this ); - connect( m_thread, SIGNAL( engineFound( QString, unsigned int, unsigned int, unsigned int ) ), - SLOT( onEngineFound( QString, unsigned int, unsigned int, unsigned int ) ) ); - - m_thread->start(); - - connect( this, SIGNAL( destroyed( QObject* ) ), m_thread, SLOT( deleteLater() ) ); -} - - -QtScriptResolver::~QtScriptResolver() -{ - Tomahawk::Pipeline::instance()->removeResolver( this ); - delete m_thread; -} - - -void -QtScriptResolver::resolve( const Tomahawk::query_ptr& query ) -{ - m_thread->resolve( query ); -} - - -void -QtScriptResolver::onEngineFound( const QString& name, unsigned int weight, unsigned int timeout, unsigned int preference ) -{ - m_name = name; - m_weight = weight; - m_timeout = timeout; - m_preference = preference; - - qDebug() << "QTSCRIPT" << filePath() << "READY," << endl - << "name" << m_name << endl - << "weight" << m_weight << endl - << "timeout" << m_timeout << endl - << "preference" << m_preference; - - m_ready = true; - Tomahawk::Pipeline::instance()->addResolver( this ); -} - - -ScriptThread::ScriptThread( const QString& scriptPath, QtScriptResolver* parent ) - : QThread() - , m_parent( parent ) - , m_scriptPath( scriptPath ) -{ - moveToThread( this ); -} - - -void -ScriptThread::resolve( const Tomahawk::query_ptr& query ) -{ - m_engine->resolve( query ); -} - - -void -ScriptThread::run() -{ - QTimer::singleShot( 0, this, SLOT( initEngine() ) ); - exec(); -} - - -void -ScriptThread::initEngine() -{ - m_engine = new ScriptEngine( m_parent, this ); - QFile scriptFile( m_scriptPath ); + m_engine = new ScriptEngine( this ); + QFile scriptFile( scriptPath ); if ( !scriptFile.open( QIODevice::ReadOnly ) ) { - qDebug() << Q_FUNC_INFO << "Failed loading JavaScript resolver:" << m_scriptPath; + qDebug() << Q_FUNC_INFO << "Failed loading JavaScript resolver:" << scriptPath; deleteLater(); return; } @@ -115,42 +45,39 @@ ScriptThread::initEngine() m_engine->mainFrame()->evaluateJavaScript( scriptFile.readAll() ); scriptFile.close(); - QString name; - unsigned int weight, preference, timeout; QVariantMap m = m_engine->mainFrame()->evaluateJavaScript( "getSettings();" ).toMap(); - name = m.value( "name" ).toString(); - weight = m.value( "weight", 0 ).toUInt(); - timeout = m.value( "timeout", 25 ).toUInt() * 1000; - preference = m.value( "preference", 0 ).toUInt(); + m_name = m.value( "name" ).toString(); + m_weight = m.value( "weight", 0 ).toUInt(); + m_timeout = m.value( "timeout", 25 ).toUInt() * 1000; + m_preference = m.value( "preference", 0 ).toUInt(); - qDebug() << Q_FUNC_INFO << name << weight << timeout << preference; - emit engineFound( name, weight, timeout, preference ); + qDebug() << Q_FUNC_INFO << m_name << m_weight << m_timeout << m_preference; + + m_ready = true; + Tomahawk::Pipeline::instance()->addResolver( this ); +} + + +QtScriptResolver::~QtScriptResolver() +{ + Tomahawk::Pipeline::instance()->removeResolver( this ); + delete m_engine; } void -ScriptEngine::resolve( const Tomahawk::query_ptr& query ) +QtScriptResolver::resolve( const Tomahawk::query_ptr& query ) { - if ( QThread::currentThread() != thread() ) - { -// qDebug() << "Reinvoking in correct thread:" << Q_FUNC_INFO; - QMetaObject::invokeMethod( this, "resolve", - Qt::QueuedConnection, - Q_ARG(Tomahawk::query_ptr, query) - ); - return; - } - qDebug() << Q_FUNC_INFO << query->toString(); QString eval = QString( "resolve( '%1', '%2', '%3', '%4' );" ) - .arg( query->id().replace( "'", "\\'" ) ) - .arg( query->artist().replace( "'", "\\'" ) ) - .arg( query->album().replace( "'", "\\'" ) ) - .arg( query->track().replace( "'", "\\'" ) ); + .arg( query->id().replace( "'", "\\'" ) ) + .arg( query->artist().replace( "'", "\\'" ) ) + .arg( query->album().replace( "'", "\\'" ) ) + .arg( query->track().replace( "'", "\\'" ) ); QList< Tomahawk::result_ptr > results; - QVariantMap m = mainFrame()->evaluateJavaScript( eval ).toMap(); + QVariantMap m = m_engine->mainFrame()->evaluateJavaScript( eval ).toMap(); qDebug() << "JavaScript Result:" << m; const QString qid = query->id(); @@ -169,9 +96,9 @@ ScriptEngine::resolve( const Tomahawk::query_ptr& query ) rp->setBitrate( m.value( "bitrate" ).toUInt() ); rp->setUrl( m.value( "url" ).toString() ); rp->setSize( m.value( "size" ).toUInt() ); - rp->setScore( m.value( "score" ).toFloat() * ( (float)m_resolver->weight() / 100.0 ) ); + rp->setScore( m.value( "score" ).toFloat() * ( (float)weight() / 100.0 ) ); rp->setRID( uuid() ); - rp->setFriendlySource( m_resolver->name() ); + rp->setFriendlySource( name() ); if ( m.contains( "year" ) ) { diff --git a/src/resolvers/qtscriptresolver.h b/src/resolvers/qtscriptresolver.h index 7c69fe103..a6850ac96 100644 --- a/src/resolvers/qtscriptresolver.h +++ b/src/resolvers/qtscriptresolver.h @@ -30,7 +30,6 @@ #include #include -class ScriptThread; class QtScriptResolver; class ScriptEngine : public QWebPage @@ -38,18 +37,16 @@ class ScriptEngine : public QWebPage Q_OBJECT public: - explicit ScriptEngine( QtScriptResolver* resolver, ScriptThread* parent ) - : QWebPage( (QObject*)parent ) + explicit ScriptEngine( QtScriptResolver* parent ) + : QWebPage( (QObject*) parent ) , m_parent( parent ) - , m_resolver( resolver ) - {} + { + } public slots: - void resolve( const Tomahawk::query_ptr& query ); - bool shouldInterruptJavaScript() { - return false; + return true; } protected: @@ -57,32 +54,7 @@ protected: { qDebug() << "JAVASCRIPT ERROR:" << message << lineNumber << sourceID; } private: - ScriptThread* m_parent; - QtScriptResolver* m_resolver; -}; - - -class ScriptThread : public QThread -{ -Q_OBJECT - -public: - ScriptThread( const QString& scriptPath, QtScriptResolver* parent ); - - void run(); - - virtual void resolve( const Tomahawk::query_ptr& query ); - -signals: - void engineFound( const QString& name, unsigned int weight, unsigned int timeout, unsigned int preference ); - -private slots: - void initEngine(); - -private: - ScriptEngine* m_engine; QtScriptResolver* m_parent; - QString m_scriptPath; }; @@ -106,11 +78,8 @@ public slots: signals: void finished(); -private slots: - void onEngineFound( const QString& name, unsigned int weight, unsigned int timeout, unsigned int preference ); - private: - ScriptThread* m_thread; + ScriptEngine* m_engine; QString m_name; unsigned int m_weight, m_preference, m_timeout; From 84f6886e8d5e36488c8d83d3686ca34593fef92c Mon Sep 17 00:00:00 2001 From: Christian Muehlhaeuser Date: Sun, 3 Apr 2011 11:20:35 +0200 Subject: [PATCH 126/329] * Made PlaylistItemDelegate's paint method a lot faster with manual alpha-blending. --- .../playlist/playlistitemdelegate.cpp | 23 ++++++++++++++----- 1 file changed, 17 insertions(+), 6 deletions(-) diff --git a/src/libtomahawk/playlist/playlistitemdelegate.cpp b/src/libtomahawk/playlist/playlistitemdelegate.cpp index d014691c5..ed0f2870b 100644 --- a/src/libtomahawk/playlist/playlistitemdelegate.cpp +++ b/src/libtomahawk/playlist/playlistitemdelegate.cpp @@ -72,14 +72,18 @@ PlaylistItemDelegate::paint( QPainter* painter, const QStyleOptionViewItem& opti if ( !item || item->query().isNull() ) return; + float opacity = 0.0; painter->save(); if ( item->query()->results().count() ) - painter->setOpacity( item->query()->results().at( 0 )->score() ); - else - painter->setOpacity( 0.0 ); + opacity = item->query()->results().first()->score(); - if ( painter->opacity() < 0.3 ) - painter->setOpacity( 0.3 ); + opacity = qMax( (float)0.3, opacity ); + int r = 0, g = 0, b = 0; + r = opacity * r + ( 1 - opacity ) * 255; + g = opacity * g + ( 1 - opacity ) * 255; + b = opacity * b + ( 1 - opacity ) * 255; + + QColor tc( r, g, b ); if ( item->isPlaying() ) { @@ -113,7 +117,14 @@ PlaylistItemDelegate::paint( QPainter* painter, const QStyleOptionViewItem& opti } else { - QStyledItemDelegate::paint( painter, option, index ); + if ( const QStyleOptionViewItem *vioption = qstyleoption_cast(&option)) + { + QStyleOptionViewItemV4 o( *vioption ); + o.palette.setColor( QPalette::Text, tc ); + QStyledItemDelegate::paint( painter, o, index ); + } + else + QStyledItemDelegate::paint( painter, option, index ); } painter->restore(); From 8791a6dbdc02c33923c60237909d7dd1d7bfb9a1 Mon Sep 17 00:00:00 2001 From: Christian Muehlhaeuser Date: Sun, 3 Apr 2011 11:27:03 +0200 Subject: [PATCH 127/329] * Respect style's default fore / bg colors when painting a PlaylistItem. --- .../playlist/playlistitemdelegate.cpp | 17 ++++++++++------- 1 file changed, 10 insertions(+), 7 deletions(-) diff --git a/src/libtomahawk/playlist/playlistitemdelegate.cpp b/src/libtomahawk/playlist/playlistitemdelegate.cpp index ed0f2870b..0edc22e7f 100644 --- a/src/libtomahawk/playlist/playlistitemdelegate.cpp +++ b/src/libtomahawk/playlist/playlistitemdelegate.cpp @@ -77,13 +77,16 @@ PlaylistItemDelegate::paint( QPainter* painter, const QStyleOptionViewItem& opti if ( item->query()->results().count() ) opacity = item->query()->results().first()->score(); - opacity = qMax( (float)0.3, opacity ); - int r = 0, g = 0, b = 0; - r = opacity * r + ( 1 - opacity ) * 255; - g = opacity * g + ( 1 - opacity ) * 255; - b = opacity * b + ( 1 - opacity ) * 255; + QColor textcol, bgcol; + textcol = option.palette.color( QPalette::Foreground ); + bgcol = option.palette.color( QPalette::Background ); - QColor tc( r, g, b ); + opacity = qMax( (float)0.3, opacity ); + int r = textcol.red(), g = textcol.green(), b = textcol.blue(); + r = opacity * r + ( 1 - opacity ) * bgcol.red(); + g = opacity * g + ( 1 - opacity ) * bgcol.green(); + b = opacity * b + ( 1 - opacity ) * bgcol.blue(); + textcol = QColor( r, g, b ); if ( item->isPlaying() ) { @@ -120,7 +123,7 @@ PlaylistItemDelegate::paint( QPainter* painter, const QStyleOptionViewItem& opti if ( const QStyleOptionViewItem *vioption = qstyleoption_cast(&option)) { QStyleOptionViewItemV4 o( *vioption ); - o.palette.setColor( QPalette::Text, tc ); + o.palette.setColor( QPalette::Text, textcol ); QStyledItemDelegate::paint( painter, o, index ); } else From d4569b1d38f33a23d33e5adfa9e00c5aae51a111 Mon Sep 17 00:00:00 2001 From: Steven Robertson Date: Sun, 3 Apr 2011 20:23:32 +0800 Subject: [PATCH 128/329] EnsureTomahawkShutdown not required in uninstaller. --- admin/win/nsi/tomahawk.nsi | 11 +++-------- 1 file changed, 3 insertions(+), 8 deletions(-) diff --git a/admin/win/nsi/tomahawk.nsi b/admin/win/nsi/tomahawk.nsi index 28bdb4c59..e7f1ce251 100644 --- a/admin/win/nsi/tomahawk.nsi +++ b/admin/win/nsi/tomahawk.nsi @@ -189,14 +189,9 @@ FunctionEnd no_process_${processName}_to_end: !macroend -!macro EnsureTomahawkShutdown un - Function ${un}EnsureTomahawkShutdown - !insertmacro CheckAndConfirmEndProcess "tomahawk.exe" - FunctionEnd -!macroend - -!insertmacro EnsureTomahawkShutdown "" -!insertmacro EnsureTomahawkShutdown "un." +Function EnsureTomahawkShutdown + !insertmacro CheckAndConfirmEndProcess "tomahawk.exe" +FunctionEnd ############################################################################## # # From cffa96a81f8747e8f23fe82e1d2801633cdb3864 Mon Sep 17 00:00:00 2001 From: Christian Muehlhaeuser Date: Sun, 3 Apr 2011 14:39:56 +0200 Subject: [PATCH 129/329] * Updated Changelog. --- ChangeLog | 2 ++ 1 file changed, 2 insertions(+) diff --git a/ChangeLog b/ChangeLog index b25481516..785119397 100644 --- a/ChangeLog +++ b/ChangeLog @@ -4,6 +4,8 @@ Version 0.0.3: marked as preferred. * Don't automatically try to resolve all incoming playback logs. This speeds up importing sources a lot. + * Faster painting of playlists with lots of unresolved tracks. + * The tomahawk:// protocol handler works on Windows now. Version 0.0.2: * Don't reconnect to Jabber if the settings dialog is closed successfully From 1ac61194cf5f6f52085181a1826c8830f22cc83d Mon Sep 17 00:00:00 2001 From: Christian Muehlhaeuser Date: Mon, 4 Apr 2011 06:56:33 +0200 Subject: [PATCH 130/329] * Mutex-locked logging - write/flush isn't exactly an atomic operation. --- src/tomahawkapp.cpp | 3 +++ 1 file changed, 3 insertions(+) diff --git a/src/tomahawkapp.cpp b/src/tomahawkapp.cpp index d668cc012..8af6a0457 100644 --- a/src/tomahawkapp.cpp +++ b/src/tomahawkapp.cpp @@ -80,6 +80,9 @@ ofstream logfile; void TomahawkLogHandler( QtMsgType type, const char *msg ) { + static QMutex s_mutex; + + QMutexLocker locker( &s_mutex ); switch( type ) { case QtDebugMsg: From 5e9ea36228ed888b1fb56736b257183d8bf201b2 Mon Sep 17 00:00:00 2001 From: Christian Muehlhaeuser Date: Mon, 4 Apr 2011 07:56:56 +0200 Subject: [PATCH 131/329] * Fixed result sorting order, always prefers local results with equal score now. --- src/libtomahawk/query.cpp | 21 ++++++++++++++++----- src/libtomahawk/query.h | 2 ++ 2 files changed, 18 insertions(+), 5 deletions(-) diff --git a/src/libtomahawk/query.cpp b/src/libtomahawk/query.cpp index b8c48e83c..10cf98c81 100644 --- a/src/libtomahawk/query.cpp +++ b/src/libtomahawk/query.cpp @@ -63,7 +63,7 @@ Query::addResults( const QList< Tomahawk::result_ptr >& newresults ) { bool becameSolved = false; { -// QMutexLocker lock( &m_mut ); + QMutexLocker lock( &m_mutex ); m_results.append( newresults ); qStableSort( m_results.begin(), m_results.end(), Query::resultSorter ); @@ -101,7 +101,7 @@ void Query::removeResult( const Tomahawk::result_ptr& result ) { { -// QMutexLocker lock( &m_mut ); + QMutexLocker lock( &m_mutex ); m_results.removeAll( result ); } @@ -121,7 +121,7 @@ Query::onResolvingFinished() QList< result_ptr > Query::results() const { -// QMutexLocker lock( &m_mut ); + QMutexLocker lock( &m_mutex ); return m_results; } @@ -129,7 +129,7 @@ Query::results() const unsigned int Query::numResults() const { -// QMutexLocker lock( &m_mut ); + QMutexLocker lock( &m_mutex ); return m_results.length(); } @@ -149,7 +149,18 @@ Query::id() const bool Query::resultSorter( const result_ptr& left, const result_ptr& right ) { - return left->score() > right->score(); + const float ls = left->score(); + const float rs = right->score(); + + if ( ls == rs ) + { + if ( !left->collection().isNull() && left->collection()->source()->isLocal() ) + return true; + else + return false; + } + + return ls > rs; } diff --git a/src/libtomahawk/query.h b/src/libtomahawk/query.h index bfa2b3d1d..9c84d68bf 100644 --- a/src/libtomahawk/query.h +++ b/src/libtomahawk/query.h @@ -116,6 +116,8 @@ private: QString m_track; int m_duration; QString m_resultHint; + + mutable QMutex m_mutex; }; }; //ns From b25241807492ead68c703bfb32456feec95c0a11 Mon Sep 17 00:00:00 2001 From: Christian Muehlhaeuser Date: Mon, 4 Apr 2011 07:57:37 +0200 Subject: [PATCH 132/329] * Hopefully fixed dupe-file issue. --- src/musicscanner.cpp | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/musicscanner.cpp b/src/musicscanner.cpp index 220baa1a7..db66cb1f5 100644 --- a/src/musicscanner.cpp +++ b/src/musicscanner.cpp @@ -125,8 +125,6 @@ MusicScanner::startScan() DatabaseCommand_DirMtimes* cmd = new DatabaseCommand_DirMtimes( m_dir ); connect( cmd, SIGNAL( done( QMap ) ), SLOT( setMtimes( QMap ) ) ); - connect( cmd, SIGNAL( done( QMap ) ), - SLOT( scan() ) ); Database::instance()->enqueue( QSharedPointer(cmd) ); } @@ -135,7 +133,9 @@ MusicScanner::startScan() void MusicScanner::setMtimes( const QMap& m ) { + qDebug() << Q_FUNC_INFO << m.count(); m_dirmtimes = m; + scan(); } From f78df2c087842517db8bb8f8399ee1437237d977 Mon Sep 17 00:00:00 2001 From: Christian Muehlhaeuser Date: Mon, 4 Apr 2011 07:59:22 +0200 Subject: [PATCH 133/329] * Added assert to catch dupe source. --- src/libtomahawk/network/controlconnection.cpp | 9 ++++++++- src/libtomahawk/source.cpp | 6 +++--- 2 files changed, 11 insertions(+), 4 deletions(-) diff --git a/src/libtomahawk/network/controlconnection.cpp b/src/libtomahawk/network/controlconnection.cpp index 2e122ca96..5baa0455d 100644 --- a/src/libtomahawk/network/controlconnection.cpp +++ b/src/libtomahawk/network/controlconnection.cpp @@ -75,6 +75,13 @@ ControlConnection::setup() { qDebug() << Q_FUNC_INFO << id() << name(); + if ( !m_source.isNull() ) + { + qDebug() << "This source seems to be online already."; + Q_ASSERT( false ); + return; + } + QString friendlyName; if ( Servent::isIPWhitelisted( m_sock->peerAddress() ) ) { @@ -85,7 +92,7 @@ ControlConnection::setup() } else friendlyName = name(); - + // setup source and remote collection for this peer m_source = SourceList::instance()->get( id(), friendlyName ); m_source->setControlConnection( this ); diff --git a/src/libtomahawk/source.cpp b/src/libtomahawk/source.cpp index 59ad94688..54c4394b3 100644 --- a/src/libtomahawk/source.cpp +++ b/src/libtomahawk/source.cpp @@ -30,7 +30,7 @@ using namespace Tomahawk; -Source::Source( int id, const QString &username ) +Source::Source( int id, const QString& username ) : QObject() , m_isLocal( false ) , m_online( false ) @@ -38,7 +38,7 @@ Source::Source( int id, const QString &username ) , m_id( id ) , m_cc( 0 ) { - qDebug() << Q_FUNC_INFO; + qDebug() << Q_FUNC_INFO << id << username; if ( id == 0 ) { @@ -146,6 +146,7 @@ Source::setOnline() { if ( m_online ) return; + m_online = true; // ensure username is in the database DatabaseCommand_addSource* cmd = new DatabaseCommand_addSource( m_username, m_friendlyname ); @@ -153,7 +154,6 @@ Source::setOnline() SLOT( dbLoaded( unsigned int, const QString& ) ) ); Database::instance()->enqueue( QSharedPointer(cmd) ); - m_online = true; emit online(); } From 3ba36fc38e334fc7c5a69e1d46eaa5ef7b80e1f0 Mon Sep 17 00:00:00 2001 From: Christian Muehlhaeuser Date: Mon, 4 Apr 2011 07:59:32 +0200 Subject: [PATCH 134/329] * Cleaned up debug output, lots. --- ChangeLog | 2 ++ .../databasecommand_loaddynamicplaylist.cpp | 6 +++--- .../databasecommand_loadplaylistentries.cpp | 5 ++--- src/libtomahawk/network/connection.cpp | 17 ++++++---------- src/libtomahawk/network/dbsyncconnection.cpp | 9 ++------- src/libtomahawk/pipeline.cpp | 20 +++++++++---------- src/libtomahawk/playlist.cpp | 2 +- .../playlist/dynamic/DynamicPlaylist.cpp | 6 +++--- src/libtomahawk/utils/animatedsplitter.cpp | 2 +- src/sip/jabber/jabber_p.cpp | 14 ++++++------- 10 files changed, 36 insertions(+), 47 deletions(-) diff --git a/ChangeLog b/ChangeLog index 785119397..3bcf6476b 100644 --- a/ChangeLog +++ b/ChangeLog @@ -6,6 +6,8 @@ Version 0.0.3: speeds up importing sources a lot. * Faster painting of playlists with lots of unresolved tracks. * The tomahawk:// protocol handler works on Windows now. + * Fixed launching Tomahawk from Installer with administrative permissions. + * Prefer local results when results' score is equal. Version 0.0.2: * Don't reconnect to Jabber if the settings dialog is closed successfully diff --git a/src/libtomahawk/database/databasecommand_loaddynamicplaylist.cpp b/src/libtomahawk/database/databasecommand_loaddynamicplaylist.cpp index eb80312b0..f8b449e61 100644 --- a/src/libtomahawk/database/databasecommand_loaddynamicplaylist.cpp +++ b/src/libtomahawk/database/databasecommand_loaddynamicplaylist.cpp @@ -52,9 +52,9 @@ DatabaseCommand_LoadDynamicPlaylist::exec( DatabaseImpl* dbi ) QList< QVariantMap > controls; QString playlist_guid; qDebug() << "Loading controls..." << revisionGuid(); - qDebug() << "SELECT playlist_revision.playlist, controls, plmode, pltype " - "FROM dynamic_playlist_revision, playlist_revision " - "WHERE dynamic_playlist_revision.guid = "<< revisionGuid() << " AND playlist_revision.guid = dynamic_playlist_revision.guid"; +// qDebug() << "SELECT playlist_revision.playlist, controls, plmode, pltype " +// "FROM dynamic_playlist_revision, playlist_revision " +// "WHERE dynamic_playlist_revision.guid = "<< revisionGuid() << " AND playlist_revision.guid = dynamic_playlist_revision.guid"; if( controlsQuery.first() ) { playlist_guid = controlsQuery.value( 0 ).toString(); diff --git a/src/libtomahawk/database/databasecommand_loadplaylistentries.cpp b/src/libtomahawk/database/databasecommand_loadplaylistentries.cpp index 20143dd53..f29dc8a40 100644 --- a/src/libtomahawk/database/databasecommand_loadplaylistentries.cpp +++ b/src/libtomahawk/database/databasecommand_loadplaylistentries.cpp @@ -45,7 +45,7 @@ DatabaseCommand_LoadPlaylistEntries::generateEntries( DatabaseImpl* dbi ) query_entries.bindValue( ":guid", m_revguid ); query_entries.exec(); - qDebug() << "trying to load entries:" << m_revguid; +// qDebug() << "trying to load entries:" << m_revguid; QString prevrev; QJson::Parser parser; bool ok; @@ -55,7 +55,6 @@ DatabaseCommand_LoadPlaylistEntries::generateEntries( DatabaseImpl* dbi ) QVariant v = parser.parse( query_entries.value(0).toByteArray(), &ok ); Q_ASSERT( ok && v.type() == QVariant::List ); //TODO m_guids = v.toStringList(); - // qDebug() << "Entries:" << guids; QString inclause = QString("('%1')").arg(m_guids.join("', '")); @@ -115,5 +114,5 @@ DatabaseCommand_LoadPlaylistEntries::generateEntries( DatabaseImpl* dbi ) m_islatest = query_entries_old.value( 1 ).toBool(); } - qDebug() << Q_FUNC_INFO << "entrymap:" << m_entrymap; +// qDebug() << Q_FUNC_INFO << "entrymap:" << m_entrymap; } diff --git a/src/libtomahawk/network/connection.cpp b/src/libtomahawk/network/connection.cpp index afa009713..59342fba9 100644 --- a/src/libtomahawk/network/connection.cpp +++ b/src/libtomahawk/network/connection.cpp @@ -62,16 +62,12 @@ Connection::Connection( Servent* parent ) Connection::~Connection() { - qDebug() << "DTOR connection (super)" << id() << thread(); + qDebug() << "DTOR connection (super)" << id() << thread() << m_sock.isNull(); if( !m_sock.isNull() ) { - qDebug() << "deleteLatering sock" << m_sock; +// qDebug() << "deleteLatering sock" << m_sock; m_sock->deleteLater(); } - else - { - qDebug() << "no valid sock to delete"; - } delete m_statstimer; } @@ -118,7 +114,7 @@ Connection::setFirstMessage( msg_ptr m ) void Connection::shutdown( bool waitUntilSentAll ) { - qDebug() << Q_FUNC_INFO << waitUntilSentAll; + qDebug() << Q_FUNC_INFO << waitUntilSentAll << id(); if ( m_do_shutdown ) { //qDebug() << id() << " already shutting down"; @@ -128,7 +124,7 @@ Connection::shutdown( bool waitUntilSentAll ) m_do_shutdown = true; if ( !waitUntilSentAll ) { - qDebug() << "Shutting down immediately " << id(); +// qDebug() << "Shutting down immediately " << id(); actualShutdown(); } else @@ -146,10 +142,9 @@ Connection::shutdown( bool waitUntilSentAll ) void Connection::actualShutdown() { - qDebug() << Q_FUNC_INFO; + qDebug() << Q_FUNC_INFO << m_actually_shutting_down << id(); if( m_actually_shutting_down ) { - qDebug() << "(already actually shutting down)"; return; } m_actually_shutting_down = true; @@ -159,7 +154,7 @@ Connection::actualShutdown() m_sock->disconnectFromHost(); } - qDebug() << "EMITTING finished()"; +// qDebug() << "EMITTING finished()"; emit finished(); } diff --git a/src/libtomahawk/network/dbsyncconnection.cpp b/src/libtomahawk/network/dbsyncconnection.cpp index a25d8ab8f..b3559088f 100644 --- a/src/libtomahawk/network/dbsyncconnection.cpp +++ b/src/libtomahawk/network/dbsyncconnection.cpp @@ -75,7 +75,7 @@ DBSyncConnection::~DBSyncConnection() void DBSyncConnection::idleTimeout() { - qDebug() << Q_FUNC_INFO << "*************"; + qDebug() << Q_FUNC_INFO; shutdown( true ); } @@ -87,11 +87,6 @@ DBSyncConnection::changeState( State newstate ) m_state = newstate; qDebug() << "DBSYNC State changed from" << s << "to" << newstate; emit stateChanged( newstate, s, "" ); - - if ( newstate == SYNCED ) - { - qDebug() << "Synced :)"; - } } @@ -197,7 +192,7 @@ DBSyncConnection::handleMsg( msg_ptr msg ) msg->is( Msg::DBOP ) && msg->payload() == "ok" ) { - qDebug() << "No ops to apply, we are synced."; +// qDebug() << "No ops to apply, we are synced."; changeState( SYNCED ); // calc the collection stats, to updates the "X tracks" in the sidebar etc // this is done automatically if you run a dbcmd to add files. diff --git a/src/libtomahawk/pipeline.cpp b/src/libtomahawk/pipeline.cpp index 3ffc4a154..b7d538a37 100644 --- a/src/libtomahawk/pipeline.cpp +++ b/src/libtomahawk/pipeline.cpp @@ -102,7 +102,7 @@ Pipeline::resolve( const QList& qlist, bool prioritized ) int i = 0; foreach( const query_ptr& q, qlist ) { - qDebug() << Q_FUNC_INFO << (qlonglong)q.data() << q->toString(); +// qDebug() << Q_FUNC_INFO << (qlonglong)q.data() << q->toString(); if ( !m_qids.contains( q->id() ) ) { m_qids.insert( q->id(), q ); @@ -189,7 +189,7 @@ Pipeline::reportResults( QID qid, const QList< result_ptr >& results ) if ( decQIDState( q ) == 0 ) { // All resolvers have reported back their results for this query now - qDebug() << "Finished resolving:" << q->toString(); + qDebug() << "Finished resolving:" << q->toString() << q->numResults(); if ( !q->solved() ) q->onResolvingFinished(); @@ -216,7 +216,7 @@ Pipeline::shuntNext() return; } - qDebug() << Q_FUNC_INFO << m_qidsState.count(); +// qDebug() << Q_FUNC_INFO << m_qidsState.count(); // Check if we are ready to dispatch more queries if ( m_qidsState.count() >= CONCURRENT_QUERIES ) return; @@ -246,8 +246,8 @@ Pipeline::shunt( const query_ptr& q ) if ( q->solved() ) { - qDebug() << "Query solved, pipeline aborted:" << q->toString() - << "numresults:" << q->results().length(); +// qDebug() << "Query solved, pipeline aborted:" << q->toString() +// << "numresults:" << q->results().length(); QList< result_ptr > rl; reportResults( q->id(), rl ); @@ -275,7 +275,7 @@ Pipeline::shunt( const query_ptr& q ) lasttimeout = r->timeout(); // resolvers aren't allowed to block in this call: - qDebug() << "Dispatching to resolver" << r->name(); + qDebug() << "Dispatching to resolver" << r->name() << q->toString(); thisResolver = i; r->resolve( q ); @@ -291,7 +291,7 @@ Pipeline::shunt( const query_ptr& q ) if ( thisResolver < m_resolvers.count() ) { incQIDState( q ); - qDebug() << "Shunting in" << lasttimeout << "ms, q:" << q->toString(); +// qDebug() << "Shunting in" << lasttimeout << "ms, q:" << q->toString(); new FuncTimeout( lasttimeout, boost::bind( &Pipeline::shunt, this, q ) ); } } @@ -329,7 +329,7 @@ Pipeline::incQIDState( const Tomahawk::query_ptr& query ) state = m_qidsState.value( query->id() ) + 1; } - qDebug() << Q_FUNC_INFO << "inserting to qidsstate:" << query->id() << state; +// qDebug() << Q_FUNC_INFO << "inserting to qidsstate:" << query->id() << state; m_qidsState.insert( query->id(), state ); return state; @@ -344,12 +344,12 @@ Pipeline::decQIDState( const Tomahawk::query_ptr& query ) int state = m_qidsState.value( query->id() ) - 1; if ( state ) { - qDebug() << Q_FUNC_INFO << "replacing" << query->id() << state; +// qDebug() << Q_FUNC_INFO << "replacing" << query->id() << state; m_qidsState.insert( query->id(), state ); } else { - qDebug() << Q_FUNC_INFO << "removing" << query->id() << state; +// qDebug() << Q_FUNC_INFO << "removing" << query->id() << state; m_qidsState.remove( query->id() ); } diff --git a/src/libtomahawk/playlist.cpp b/src/libtomahawk/playlist.cpp index f27e592e0..1ca21e31e 100644 --- a/src/libtomahawk/playlist.cpp +++ b/src/libtomahawk/playlist.cpp @@ -112,7 +112,7 @@ Playlist::Playlist( const source_ptr& src, , m_lastmodified( lastmod ) , m_shared( shared ) { - qDebug() << Q_FUNC_INFO << "1"; +// qDebug() << Q_FUNC_INFO << "1"; init(); } diff --git a/src/libtomahawk/playlist/dynamic/DynamicPlaylist.cpp b/src/libtomahawk/playlist/dynamic/DynamicPlaylist.cpp index 554ff5f98..6ab5eb30f 100644 --- a/src/libtomahawk/playlist/dynamic/DynamicPlaylist.cpp +++ b/src/libtomahawk/playlist/dynamic/DynamicPlaylist.cpp @@ -253,8 +253,8 @@ DynamicPlaylist::reportCreated( const Tomahawk::dynplaylist_ptr& self ) Q_ASSERT( !author().isNull() ); Q_ASSERT( !author()->collection().isNull() ); // will emit Collection::playlistCreated(...) - qDebug() << "Creating dynplaylist belonging to:" << author().data() << author().isNull(); - qDebug() << "REPORTING DYNAMIC PLAYLIST CREATED:" << this << author()->friendlyName(); +// qDebug() << "Creating dynplaylist belonging to:" << author().data() << author().isNull(); +// qDebug() << "REPORTING DYNAMIC PLAYLIST CREATED:" << this << author()->friendlyName(); author()->collection()->addDynamicPlaylist( self ); } @@ -431,7 +431,7 @@ QList< dyncontrol_ptr > DynamicPlaylist::variantsToControl( const QList< QVarian QList realControls; foreach( QVariantMap controlV, controlsV ) { dyncontrol_ptr control = GeneratorFactory::createControl( controlV.value( "type" ).toString(), controlV.value( "selectedType" ).toString() ); - qDebug() << "CReating control with data:" << controlV; + qDebug() << "Creating control with data:" << controlV; QJson::QObjectHelper::qvariant2qobject( controlV, control.data() ); realControls << control; } diff --git a/src/libtomahawk/utils/animatedsplitter.cpp b/src/libtomahawk/utils/animatedsplitter.cpp index 3d9607ee3..3ec748052 100644 --- a/src/libtomahawk/utils/animatedsplitter.cpp +++ b/src/libtomahawk/utils/animatedsplitter.cpp @@ -83,7 +83,7 @@ AnimatedSplitter::hide( int index, bool animate ) emit hidden( w ); w->setMinimumHeight( minHeight ); - qDebug() << "animating to:" << w->height() << "from" << minHeight; +// qDebug() << "animating to:" << w->height() << "from" << minHeight; m_animateForward = false; if ( animate ) diff --git a/src/sip/jabber/jabber_p.cpp b/src/sip/jabber/jabber_p.cpp index 462296682..a770934d3 100644 --- a/src/sip/jabber/jabber_p.cpp +++ b/src/sip/jabber/jabber_p.cpp @@ -56,7 +56,7 @@ Jabber_p::Jabber_p( const QString& jid, const QString& password, const QString& if( m_jid.resource().find( "tomahawk" ) == std::string::npos ) { - qDebug() << "!!! Setting your resource to 'tomahawk' prior to logging in to jabber"; +// qDebug() << "!!! Setting your resource to 'tomahawk' prior to logging in to jabber"; m_jid.setResource( QString( "tomahawk%1" ).arg( qrand() ).toStdString() ); } @@ -210,8 +210,8 @@ Jabber_p::sendMsg( const QString& to, const QString& msg ) { if ( QThread::currentThread() != thread() ) { - qDebug() << Q_FUNC_INFO << "invoking in correct thread, not" - << QThread::currentThread(); +// qDebug() << Q_FUNC_INFO << "invoking in correct thread, not" +// << QThread::currentThread(); QMetaObject::invokeMethod( this, "sendMsg", Qt::QueuedConnection, @@ -277,7 +277,7 @@ Jabber_p::addContact( const QString& jid, const QString& msg ) void Jabber_p::onConnect() { - qDebug() << "Connected to the XMPP server"; + qDebug() << "Connected to the XMPP server" << m_jid.full().c_str(); // update jid resource, servers like gtalk use resource binding and may // have changed our requested /resource if ( m_client->resource() != m_jid.resource() ) @@ -287,7 +287,6 @@ Jabber_p::onConnect() emit jidChanged( jidstr ); } - qDebug() << "Connected as:" << m_jid.full().c_str(); emit connected(); } @@ -516,7 +515,7 @@ Jabber_p::handleRoster( const Roster& roster ) for ( ; it != roster.end(); ++it ) { if ( (*it).second->subscription() != S10nBoth ) continue; - qDebug() << (*it).second->jid().c_str() << (*it).second->name().c_str(); +// qDebug() << (*it).second->jid().c_str() << (*it).second->name().c_str(); //printf("JID: %s\n", (*it).second->jid().c_str()); } @@ -539,8 +538,6 @@ Jabber_p::handlePresence( const gloox::Presence& presence ) JID jid = presence.from(); QString fulljid( jid.full().c_str() ); - qDebug() << "* handleRosterPresence" << fulljid << presence.subtype(); - if( jid == m_jid ) return; @@ -562,6 +559,7 @@ Jabber_p::handlePresence( const gloox::Presence& presence ) return; } + qDebug() << "* handleRosterPresence" << fulljid << presence.subtype(); //qDebug() << "handling presence for resource of" << res; //qDebug() << Q_FUNC_INFO << "jid:" << QString::fromStdString(item.jid()) From 89c3f266a7c4dfd34b454d83eb05a4cd343f7ac0 Mon Sep 17 00:00:00 2001 From: Christian Muehlhaeuser Date: Fri, 25 Mar 2011 10:23:43 +0000 Subject: [PATCH 135/329] * Manually merged stever's tomahawk.nsi patch. --- admin/win/nsi/tomahawk.nsi | 119 ++++++++++++++----------------------- src/main.cpp | 2 +- 2 files changed, 46 insertions(+), 75 deletions(-) diff --git a/admin/win/nsi/tomahawk.nsi b/admin/win/nsi/tomahawk.nsi index e7f1ce251..e724d3df9 100644 --- a/admin/win/nsi/tomahawk.nsi +++ b/admin/win/nsi/tomahawk.nsi @@ -4,7 +4,7 @@ ; Some installer script options (comment-out options not required) ;----------------------------------------------------------------------------- ;!define OPTION_LICENSE_AGREEMENT -;!define OPTION_UAC_PLUGIN_ENHANCED +!define OPTION_UAC_PLUGIN_ENHANCED !define OPTION_SECTION_SC_START_MENU !define OPTION_SECTION_SC_DESKTOP !define OPTION_SECTION_SC_QUICK_LAUNCH @@ -58,17 +58,10 @@ InstType Full InstType Minimal CRCCheck On SetCompressor /SOLID lzma +RequestExecutionLevel user ;Now using the UAC plugin. ReserveFile tomahawk.ini ReserveFile "${NSISDIR}\Plugins\InstallOptions.dll" -;The UAC plugin provides an elevated user. -;Otherwise request admin level here. -!ifdef OPTION_UAC_PLUGIN_ENHANCED - RequestExecutionLevel user -!else - RequestExecutionLevel admin -!endif - ;----------------------------------------------------------------------------- ; Include some required header files. ;----------------------------------------------------------------------------- @@ -79,9 +72,7 @@ ReserveFile "${NSISDIR}\Plugins\InstallOptions.dll" !include Memento.nsh ;Remember user selections. !include WinVer.nsh ;Windows version detection. !include WordFunc.nsh ;Used by VersionCompare macro function. -!ifdef OPTION_UAC_PLUGIN_ENHANCED - !include UAC.nsh ;Used by the UAC elevation to install as user or admin. -!endif +!include UAC.nsh ;Used by the UAC elevation to install as user or admin. ;----------------------------------------------------------------------------- ; Memento selections stored in registry. @@ -146,18 +137,12 @@ UninstPage custom un.UnPageUserAppData un.UnPageUserAppDataLeave ############################################################################## Function LaunchTomahawk - !ifdef OPTION_UAC_PLUGIN_ENHANCED - ${UAC.CallFunctionAsUser} LaunchTomahawkAsUser - !else - Exec "$INSTDIR\tomahawk.exe" - !endif + ${UAC.CallFunctionAsUser} LaunchTomahawkAsUser FunctionEnd -!ifdef OPTION_UAC_PLUGIN_ENHANCED Function LaunchTomahawkAsUser Exec "$INSTDIR\tomahawk.exe" FunctionEnd -!endif ############################################################################## # # @@ -260,12 +245,10 @@ Function PageLeaveReinstall Delete $R1 RMDir $INSTDIR no_remove_uninstaller: - StrCmp $R0 "2" +2 0 + StrCmp $R0 "2" 0 +3 + UAC::Unload + Quit BringToFront - !ifdef OPTION_UAC_PLUGIN_ENHANCED - UAC::Unload - Quit - !endif reinst_done: FunctionEnd @@ -380,7 +363,7 @@ SectionGroup "Shortcuts" CreateShortCut "$SMPROGRAMS\Tomahawk\LICENSE.lnk" "$INSTDIR\LICENSE.txt" CreateShortCut "$SMPROGRAMS\Tomahawk\Tomahawk.lnk" "$INSTDIR\tomahawk.exe" CreateShortCut "$SMPROGRAMS\Tomahawk\Release notes.lnk" "$INSTDIR\NOTES.txt" - CreateShortCut "$SMPROGRAMS\Tomahawk\Uninstall.lnk" "$INSTDIR\Uninstall.exe" + CreateShortCut "$SMPROGRAMS\Tomahawk\Uninstall.lnk" "$INSTDIR\uninstall.exe" SetShellVarContext current ${MementoSectionEnd} !endif @@ -424,7 +407,7 @@ Section -post SetDetailsPrint textonly DetailPrint "Writing Uninstaller" SetDetailsPrint listonly - WriteUninstaller $INSTDIR\Uninstall.exe + WriteUninstaller $INSTDIR\uninstall.exe ;Registry keys required for installer version handling and uninstaller. SetDetailsPrint textonly @@ -568,27 +551,25 @@ Function .onInit ${MementoSectionRestore} - !ifdef OPTION_UAC_PLUGIN_ENHANCED - UAC_Elevate: - UAC::RunElevated - StrCmp 1223 $0 UAC_ElevationAborted ; UAC dialog aborted by user? - StrCmp 0 $0 0 UAC_Err ; Error? - StrCmp 1 $1 0 UAC_Success ;Are we the real deal or just the wrapper? - Quit + UAC_Elevate: + UAC::RunElevated + StrCmp 1223 $0 UAC_ElevationAborted ; UAC dialog aborted by user? + StrCmp 0 $0 0 UAC_Err ; Error? + StrCmp 1 $1 0 UAC_Success ;Are we the real deal or just the wrapper? + Quit - UAC_Err: - MessageBox MB_ICONSTOP "Unable to elevate, error $0" - Abort + UAC_Err: + MessageBox MB_ICONSTOP "Unable to elevate, error $0" + Abort - UAC_ElevationAborted: - Abort + UAC_ElevationAborted: + Abort - UAC_Success: - StrCmp 1 $3 +4 ;Admin? - StrCmp 3 $1 0 UAC_ElevationAborted ;Try again? - MessageBox MB_ICONSTOP "This installer requires admin access, try again" - goto UAC_Elevate - !endif + UAC_Success: + StrCmp 1 $3 +4 ;Admin? + StrCmp 3 $1 0 UAC_ElevationAborted ;Try again? + MessageBox MB_ICONSTOP "This installer requires admin access, try again" + goto UAC_Elevate ;Prevent multiple instances. System::Call 'kernel32::CreateMutexA(i 0, i 0, t "tomahawkInstaller") i .r1 ?e' @@ -610,15 +591,11 @@ FunctionEnd Function .onInstSuccess ${MementoSectionSave} - !ifdef OPTION_UAC_PLUGIN_ENHANCED - UAC::Unload ;Must call unload! - !endif + UAC::Unload ;Must call unload! FunctionEnd Function .onInstFailed - !ifdef OPTION_UAC_PLUGIN_ENHANCED - UAC::Unload ;Must call unload! - !endif + UAC::Unload ;Must call unload! FunctionEnd ############################################################################## @@ -628,27 +605,25 @@ FunctionEnd ############################################################################## Function un.onInit - !ifdef OPTION_UAC_PLUGIN_ENHANCED - UAC_Elevate: - UAC::RunElevated - StrCmp 1223 $0 UAC_ElevationAborted ; UAC dialog aborted by user? - StrCmp 0 $0 0 UAC_Err ; Error? - StrCmp 1 $1 0 UAC_Success ;Are we the real deal or just the wrapper? - Quit + UAC_Elevate: + UAC::RunElevated + StrCmp 1223 $0 UAC_ElevationAborted ; UAC dialog aborted by user? + StrCmp 0 $0 0 UAC_Err ; Error? + StrCmp 1 $1 0 UAC_Success ;Are we the real deal or just the wrapper? + Quit - UAC_Err: - MessageBox MB_ICONSTOP "Unable to elevate, error $0" - Abort + UAC_Err: + MessageBox MB_ICONSTOP "Unable to elevate, error $0" + Abort - UAC_ElevationAborted: - Abort + UAC_ElevationAborted: + Abort - UAC_Success: - StrCmp 1 $3 +4 ;Admin? - StrCmp 3 $1 0 UAC_ElevationAborted ;Try again? - MessageBox MB_ICONSTOP "This uninstaller requires admin access, try again" - goto UAC_Elevate - !endif + UAC_Success: + StrCmp 1 $3 +4 ;Admin? + StrCmp 3 $1 0 UAC_ElevationAborted ;Try again? + MessageBox MB_ICONSTOP "This uninstaller requires admin access, try again" + goto UAC_Elevate ;Prevent multiple instances. System::Call 'kernel32::CreateMutexA(i 0, i 0, t "tomahawkUninstaller") i .r1 ?e' @@ -659,13 +634,9 @@ Function un.onInit FunctionEnd Function un.onUnInstSuccess - !ifdef OPTION_UAC_PLUGIN_ENHANCED - UAC::Unload ;Must call unload! - !endif + UAC::Unload ;Must call unload! FunctionEnd Function un.onUnInstFailed - !ifdef OPTION_UAC_PLUGIN_ENHANCED - UAC::Unload ;Must call unload! - !endif + UAC::Unload ;Must call unload! FunctionEnd diff --git a/src/main.cpp b/src/main.cpp index 415be1d15..3a6d6936e 100644 --- a/src/main.cpp +++ b/src/main.cpp @@ -38,8 +38,8 @@ main( int argc, char *argv[] ) // used for url handler AEEventHandlerUPP h = AEEventHandlerUPP( appleEventHandler ); AEInstallEventHandler( 'GURL', 'GURL', h, 0, false ); - #endif + TomahawkApp a( argc, argv ); KDSingleApplicationGuard guard( &a, KDSingleApplicationGuard::AutoKillOtherInstances ); QObject::connect( &guard, SIGNAL( instanceStarted( KDSingleApplicationGuard::Instance ) ), &a, SLOT( instanceStarted( KDSingleApplicationGuard::Instance ) ) ); From 9e20674b8409937bbd2294681d1801ada39b2126 Mon Sep 17 00:00:00 2001 From: Christian Muehlhaeuser Date: Mon, 4 Apr 2011 09:27:16 +0200 Subject: [PATCH 136/329] * Properly escaped mtimes sql query and removed further debug. --- src/libtomahawk/database/databasecommand_dirmtimes.cpp | 3 ++- src/libtomahawk/playlist/trackmodel.cpp | 4 +--- 2 files changed, 3 insertions(+), 4 deletions(-) diff --git a/src/libtomahawk/database/databasecommand_dirmtimes.cpp b/src/libtomahawk/database/databasecommand_dirmtimes.cpp index 829a609c8..76b99fb0b 100644 --- a/src/libtomahawk/database/databasecommand_dirmtimes.cpp +++ b/src/libtomahawk/database/databasecommand_dirmtimes.cpp @@ -44,7 +44,8 @@ DatabaseCommand_DirMtimes::execSelect( DatabaseImpl* dbi ) { query.prepare( QString( "SELECT name, mtime " "FROM dirs_scanned " - "WHERE name LIKE '%1%'" ).arg( m_prefix.replace( '\'',"''" ) ) ); + "WHERE name LIKE :prefix" ) ); + query.bindValue( ":prefix", m_prefix ); query.exec(); } while( query.next() ) diff --git a/src/libtomahawk/playlist/trackmodel.cpp b/src/libtomahawk/playlist/trackmodel.cpp index e3fd2083a..f96d3b026 100644 --- a/src/libtomahawk/playlist/trackmodel.cpp +++ b/src/libtomahawk/playlist/trackmodel.cpp @@ -303,7 +303,7 @@ TrackModel::removeIndex( const QModelIndex& index, bool moreToCome ) { if ( QThread::currentThread() != thread() ) { - qDebug() << "Reinvoking in correct thread:" << Q_FUNC_INFO; +// qDebug() << "Reinvoking in correct thread:" << Q_FUNC_INFO; QMetaObject::invokeMethod( this, "removeIndex", Qt::QueuedConnection, Q_ARG(const QModelIndex, index), @@ -312,8 +312,6 @@ TrackModel::removeIndex( const QModelIndex& index, bool moreToCome ) return; } - qDebug() << Q_FUNC_INFO; - if ( index.column() > 0 ) return; From efaf180b80e3299ee2289e6187e5952acd335d88 Mon Sep 17 00:00:00 2001 From: Christian Muehlhaeuser Date: Mon, 4 Apr 2011 09:34:17 +0200 Subject: [PATCH 137/329] * Forgot the wildcard match. --- src/libtomahawk/database/databasecommand_dirmtimes.cpp | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/src/libtomahawk/database/databasecommand_dirmtimes.cpp b/src/libtomahawk/database/databasecommand_dirmtimes.cpp index 76b99fb0b..5384abfd6 100644 --- a/src/libtomahawk/database/databasecommand_dirmtimes.cpp +++ b/src/libtomahawk/database/databasecommand_dirmtimes.cpp @@ -45,11 +45,12 @@ DatabaseCommand_DirMtimes::execSelect( DatabaseImpl* dbi ) query.prepare( QString( "SELECT name, mtime " "FROM dirs_scanned " "WHERE name LIKE :prefix" ) ); - query.bindValue( ":prefix", m_prefix ); + query.bindValue( ":prefix", m_prefix + "%" ); query.exec(); } while( query.next() ) { + qDebug() << query.value( 0 ).toString(); mtimes.insert( query.value( 0 ).toString(), query.value( 1 ).toUInt() ); } From e824fcc7b3bde5333652ae2cc9a18d9e0bb9fa51 Mon Sep 17 00:00:00 2001 From: Christian Muehlhaeuser Date: Mon, 4 Apr 2011 10:01:14 +0200 Subject: [PATCH 138/329] * Gather more debug to fix mtimes issue. --- src/libtomahawk/database/databasecommand_dirmtimes.cpp | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/src/libtomahawk/database/databasecommand_dirmtimes.cpp b/src/libtomahawk/database/databasecommand_dirmtimes.cpp index 5384abfd6..5b6034086 100644 --- a/src/libtomahawk/database/databasecommand_dirmtimes.cpp +++ b/src/libtomahawk/database/databasecommand_dirmtimes.cpp @@ -36,17 +36,24 @@ DatabaseCommand_DirMtimes::exec( DatabaseImpl* dbi ) void DatabaseCommand_DirMtimes::execSelect( DatabaseImpl* dbi ) { + qDebug() << Q_FUNC_INFO << m_prefix << m_update; QMap mtimes; TomahawkSqlQuery query = dbi->newquery(); + if( m_prefix.isEmpty() ) + { query.exec( "SELECT name, mtime FROM dirs_scanned" ); + } else { query.prepare( QString( "SELECT name, mtime " "FROM dirs_scanned " "WHERE name LIKE :prefix" ) ); query.bindValue( ":prefix", m_prefix + "%" ); + + qDebug() << query.lastQuery(); query.exec(); + qDebug() << query.lastQuery(); } while( query.next() ) { From 856824535544aaf7f93f557518feed51072871c8 Mon Sep 17 00:00:00 2001 From: Christian Muehlhaeuser Date: Fri, 25 Mar 2011 11:43:39 +0000 Subject: [PATCH 139/329] * Preparing release version 0.0.3. --- CMakeLists.txt | 2 +- admin/win/nsi/revision.txt | 2 +- admin/win/nsi/tomahawk.nsi | 2 +- 3 files changed, 3 insertions(+), 3 deletions(-) mode change 100755 => 100644 admin/win/nsi/revision.txt diff --git a/CMakeLists.txt b/CMakeLists.txt index d00a02eec..dde40af13 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -7,7 +7,7 @@ CMAKE_MINIMUM_REQUIRED( VERSION 2.8 ) SET( ORGANIZATION_NAME "Tomahawk" ) SET( ORGANIZATION_DOMAIN "tomahawk-player.org" ) SET( APPLICATION_NAME "Tomahawk" ) -SET( VERSION "0.0.2" ) +SET( VERSION "0.0.3" ) # set paths diff --git a/admin/win/nsi/revision.txt b/admin/win/nsi/revision.txt old mode 100755 new mode 100644 index c4fbb1cfa..d97edbb29 --- a/admin/win/nsi/revision.txt +++ b/admin/win/nsi/revision.txt @@ -1 +1 @@ -97 \ No newline at end of file +99 \ No newline at end of file diff --git a/admin/win/nsi/tomahawk.nsi b/admin/win/nsi/tomahawk.nsi index e724d3df9..416944114 100644 --- a/admin/win/nsi/tomahawk.nsi +++ b/admin/win/nsi/tomahawk.nsi @@ -35,7 +35,7 @@ !define VER_MAJOR "0" !define VER_MINOR "0" -!define VER_BUILD "2" +!define VER_BUILD "3" !define VERSION "${VER_MAJOR}.${VER_MINOR}.${VER_BUILD}" From 81f4ec2337ca33f53643b7ff0baf80ff08e8d7ed Mon Sep 17 00:00:00 2001 From: Christian Muehlhaeuser Date: Fri, 25 Mar 2011 12:10:22 +0000 Subject: [PATCH 140/329] * Update revision. --- admin/win/nsi/revision.txt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/admin/win/nsi/revision.txt b/admin/win/nsi/revision.txt index d97edbb29..105d7d9ad 100644 --- a/admin/win/nsi/revision.txt +++ b/admin/win/nsi/revision.txt @@ -1 +1 @@ -99 \ No newline at end of file +100 \ No newline at end of file From ed0574f6b72de70d69e1422ac049ffbdebe4bfbb Mon Sep 17 00:00:00 2001 From: Leo Franchi Date: Wed, 6 Apr 2011 17:20:31 -0400 Subject: [PATCH 141/329] fix epic fail of boolean condition --- src/libtomahawk/playlist/playlistmodel.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/libtomahawk/playlist/playlistmodel.cpp b/src/libtomahawk/playlist/playlistmodel.cpp index 9f95c554b..46ebf4a35 100644 --- a/src/libtomahawk/playlist/playlistmodel.cpp +++ b/src/libtomahawk/playlist/playlistmodel.cpp @@ -110,7 +110,7 @@ PlaylistModel::loadPlaylist( const Tomahawk::playlist_ptr& playlist, bool loadEn connect( plitem, SIGNAL( dataChanged() ), SLOT( onDataChanged() ) ); - if( !entry->query()->resolvingFinished() && entry->query()->playable() ) { + if( !entry->query()->resolvingFinished() && !entry->query()->playable() ) { m_waitingForResolved.append( entry->query().data() ); connect( entry->query().data(), SIGNAL( resolvingFinished( bool ) ), this, SLOT( trackResolved( bool ) ) ); } From db7b2d2523b364cc5d212776547205b93f4f54e9 Mon Sep 17 00:00:00 2001 From: Leo Franchi Date: Wed, 6 Apr 2011 21:34:59 -0400 Subject: [PATCH 142/329] don't assume that exit() will clean up the stack --- .../kdsingleapplicationguard.cpp | 231 +++++++++--------- 1 file changed, 118 insertions(+), 113 deletions(-) diff --git a/src/libtomahawk/kdsingleapplicationguard/kdsingleapplicationguard.cpp b/src/libtomahawk/kdsingleapplicationguard/kdsingleapplicationguard.cpp index 63893dbde..6ccfe4e39 100644 --- a/src/libtomahawk/kdsingleapplicationguard/kdsingleapplicationguard.cpp +++ b/src/libtomahawk/kdsingleapplicationguard/kdsingleapplicationguard.cpp @@ -8,11 +8,11 @@ #define KDSINGLEAPPLICATIONGUARD_MAX_COMMAND_LINE 1024 #endif - + KDSingleApplicationGuard::Instance::Instance( const QStringList& args, qint64 p ) - : arguments( args ), - pid( p ) +: arguments( args ), +pid( p ) { } @@ -61,31 +61,31 @@ void KDSingleApplicationGuard::timerEvent( QTimerEvent* ) using namespace kdtools; /*! - \class KDSingleApplicationGuard KDSingleApplicationGuard - \brief A guard to protect an application from having several instances. - - KDSingleApplicationGuard can be used to make sure only one instance of an - application is running at the same time. - - \note As KDSingleApplicationGuard uses QSharedMemory Qt 4.4 or later is required + * \class KDSingleApplicationGuard KDSingleApplicationGuard + * \brief A guard to protect an application from having several instances. + * + * KDSingleApplicationGuard can be used to make sure only one instance of an + * application is running at the same time. + * + * \note As KDSingleApplicationGuard uses QSharedMemory Qt 4.4 or later is required */ /*! - \fn void KDSingleApplicationGuard::instanceStarted() - This signal is emitted by the primary instance when ever one other - instance was started. + * \fn void KDSingleApplicationGuard::instanceStarted() + * This signal is emitted by the primary instance when ever one other + * instance was started. */ /*! - \fn void KDSingleApplicationGuard::instanceExited() - This signal is emitted by the primary instance when ever one other - instance was exited. + * \fn void KDSingleApplicationGuard::instanceExited() + * This signal is emitted by the primary instance when ever one other + * instance was exited. */ - + /*! - \fn void KDSingleApplicationGuard::becamePrimaryInstance() - This signal is emitted, when the current running application gets the new - primary application. The old primary application has quit. + * \fn void KDSingleApplicationGuard::becamePrimaryInstance() + * This signal is emitted, when the current running application gets the new + * primary application. The old primary application has quit. */ enum Command @@ -105,8 +105,8 @@ Q_DECLARE_OPERATORS_FOR_FLAGS( Commands ) struct ProcessInfo { explicit ProcessInfo( Command c = FreeInstance, const QStringList& arguments = QStringList(), qint64 p = -1 ) - : command( c ), - pid( p ) + : command( c ), + pid( p ) { std::fill_n( commandline, KDSINGLEAPPLICATIONGUARD_MAX_COMMAND_LINE, '\0' ); @@ -149,7 +149,7 @@ struct ProcessInfo bool operator==( const ProcessInfo& lhs, const ProcessInfo& rhs ) { return lhs.command == rhs.command && - ::memcmp( lhs.commandline, rhs.commandline, KDSINGLEAPPLICATIONGUARD_MAX_COMMAND_LINE ) == 0; + ::memcmp( lhs.commandline, rhs.commandline, KDSINGLEAPPLICATIONGUARD_MAX_COMMAND_LINE ) == 0; } bool operator!=( const ProcessInfo& lhs, const ProcessInfo& rhs ) @@ -158,21 +158,21 @@ bool operator!=( const ProcessInfo& lhs, const ProcessInfo& rhs ) } /*! - This struct contains information about the managed process system. - \internal + * This struct contains information about the managed process system. + * \internal */ struct InstanceRegister { InstanceRegister( KDSingleApplicationGuard::Policy policy = KDSingleApplicationGuard::NoPolicy ) - : policy( policy ) + : policy( policy ) { std::fill_n( info, KDSINGLEAPPLICATIONGUARD_NUMBER_OF_PROCESSES, ProcessInfo() ); ::memcpy( magicCookie, "kdsingleapp", 12 ); } /*! - Returns wheter this register was properly initialized by the first instance. - */ + * Returns wheter this register was properly initialized by the first instance. + */ bool isValid() const { return ::strcmp( magicCookie, "kdsingleapp" ) == 0; @@ -192,18 +192,18 @@ bool operator==( const InstanceRegister& lhs, const InstanceRegister& rhs ) if( lhs.info[ i ] != rhs.info[ i ] ) return false; - return true; + return true; } /*! - \internal + * \internal */ class KDSingleApplicationGuard::Private { public: Private( KDSingleApplicationGuard* qq ) - : q( qq ), - id( -1 ) + : q( qq ), + id( -1 ) { if( primaryInstance == 0 ) primaryInstance = q; @@ -258,28 +258,28 @@ void SIGINT_handler( int sig ) #endif /*! - Creates a new KDSingleApplicationGuard guarding \a parent from mulitply instances. - If \a policy is AutoKillOtherInstances (the default), all instances, which try to start, - are killed automatically and instanceStarted() is emitted. - If \a policy is NoPolicy, the other instance will run and instanceStarted() is emitted. + * Creates a new KDSingleApplicationGuard guarding \a parent from mulitply instances. + * If \a policy is AutoKillOtherInstances (the default), all instances, which try to start, + * are killed automatically and instanceStarted() is emitted. + * If \a policy is NoPolicy, the other instance will run and instanceStarted() is emitted. */ KDSingleApplicationGuard::KDSingleApplicationGuard( QCoreApplication* parent, Policy policy ) - : QObject( parent ), - d( new Private( this ) ) +: QObject( parent ), +d( new Private( this ) ) { const QString name = parent->applicationName(); Q_ASSERT_X( !name.isEmpty(), "KDSingleApplicationGuard::KDSingleApplicationGuard", "applicationName must not be emty" ); d->mem.setKey( name ); - + // if another instance crashed, the shared memory segment is still there on Unix // the following lines trigger deletion in that case -#ifndef Q_WS_WIN + #ifndef Q_WS_WIN d->mem.attach(); d->mem.detach(); -#endif - + #endif + d->policy = policy; - + const bool created = d->mem.create( sizeof( InstanceRegister ) ); if( !created ) { @@ -288,7 +288,7 @@ KDSingleApplicationGuard::KDSingleApplicationGuard( QCoreApplication* parent, Po qWarning( "KDSingleApplicationGuard: Could neither create nor attach to shared memory segment." ); return; } - + // lets wait till the other instance initialized the register bool initialized = false; while( !initialized ) @@ -298,52 +298,57 @@ KDSingleApplicationGuard::KDSingleApplicationGuard( QCoreApplication* parent, Po } } - - KDLockedSharedMemoryPointer< InstanceRegister > instances( &d->mem ); - - if( !created ) + bool killMyself = false; { - // we're _not_ the first instance - // but the - bool killOurSelf = false; - - // find a new slot... - d->id = std::find( instances->info, instances->info + KDSINGLEAPPLICATIONGUARD_NUMBER_OF_PROCESSES, ProcessInfo() ) - instances->info; - ProcessInfo& info = instances->info[ d->id ]; - info = ProcessInfo( NewInstance, parent->arguments(), QCoreApplication::applicationPid() ); - killOurSelf = instances->policy == AutoKillOtherInstances; - d->policy = instances->policy; + KDLockedSharedMemoryPointer< InstanceRegister > instances( &d->mem ); - // but the signal that we tried to start was sent to the primary application - if( killOurSelf ) + if( !created ) { - info.command |= ExitedInstance; - exit( 1 ); + // we're _not_ the first instance + // but the + bool killOurSelf = false; + + // find a new slot... + d->id = std::find( instances->info, instances->info + KDSINGLEAPPLICATIONGUARD_NUMBER_OF_PROCESSES, ProcessInfo() ) - instances->info; + ProcessInfo& info = instances->info[ d->id ]; + info = ProcessInfo( NewInstance, parent->arguments(), QCoreApplication::applicationPid() ); + killOurSelf = instances->policy == AutoKillOtherInstances; + d->policy = instances->policy; + + // but the signal that we tried to start was sent to the primary application + if( killOurSelf ) + { + info.command |= ExitedInstance; + killMyself = true; + } + } + else + { + // ok.... we are the first instance + InstanceRegister reg( policy ); // create a new list + d->id = 0; // our id = 0 + // and we've no command + reg.info[ 0 ] = ProcessInfo( NoCommand, parent->arguments(), QCoreApplication::applicationPid() ); + *instances = reg; // push this is the process list into shared memory } } - else - { - // ok.... we are the first instance - InstanceRegister reg( policy ); // create a new list - d->id = 0; // our id = 0 - // and we've no command - reg.info[ 0 ] = ProcessInfo( NoCommand, parent->arguments(), QCoreApplication::applicationPid() ); - *instances = reg; // push this is the process list into shared memory - } + // call exit after we let the locker release our memory, as exit() is not guaranteed to clean up objects on the stack + if ( killMyself ) + exit( 1 ); -#ifndef Q_WS_WIN - ::signal( SIGINT, SIGINT_handler ); -#endif + #ifndef Q_WS_WIN + ::signal( SIGINT, SIGINT_handler ); + #endif - // now listen for commands - startTimer( 250 ); + // now listen for commands + startTimer( 250 ); } /*! - Destroys this SingleApplicationGuard. - If this instance has been the primary instance and no other instance is existing anymore, - the application is shut down completely. Otherwise the destructor selects another instance to - be the primary instances. + * Destroys this SingleApplicationGuard. + * If this instance has been the primary instance and no other instance is existing anymore, + * the application is shut down completely. Otherwise the destructor selects another instance to + * be the primary instances. */ KDSingleApplicationGuard::~KDSingleApplicationGuard() { @@ -352,16 +357,16 @@ KDSingleApplicationGuard::~KDSingleApplicationGuard() d->shutdownInstance(); } - -/*! - \property KDSingleApplicationGuard::primaryInstance - Determines wheter this instance is the primary instance. - The primary instance is the first instance which was started or an instance which - got selected by KDSingleApplicationGuard's destructor, when the primary instance was - shut down. - Get this property's value using %isPrimaryInstance(), and monitor changes to it - using becamePrimaryInstance(). +/*! + * \property KDSingleApplicationGuard::primaryInstance + * Determines wheter this instance is the primary instance. + * The primary instance is the first instance which was started or an instance which + * got selected by KDSingleApplicationGuard's destructor, when the primary instance was + * shut down. + * + * Get this property's value using %isPrimaryInstance(), and monitor changes to it + * using becamePrimaryInstance(). */ bool KDSingleApplicationGuard::isPrimaryInstance() const { @@ -369,12 +374,12 @@ bool KDSingleApplicationGuard::isPrimaryInstance() const } /*! - \property KDSingleApplicationGuard::Policy - Specifies the policy KDSingleApplicationGuard is using when new instances are started. - This can only be set in the primary instance. - - Get this property's value using %policy(), set it using %setPolicy(), and monitor changes - to it using policyChanged(). + * \property KDSingleApplicationGuard::Policy + * Specifies the policy KDSingleApplicationGuard is using when new instances are started. + * This can only be set in the primary instance. + * + * Get this property's value using %policy(), set it using %setPolicy(), and monitor changes + * to it using policyChanged(). */ KDSingleApplicationGuard::Policy KDSingleApplicationGuard::policy() const { @@ -394,7 +399,7 @@ void KDSingleApplicationGuard::setPolicy( Policy policy ) } /*! - Returns a list of all currently running instances. + * Returns a list of all currently running instances. */ QList< KDSingleApplicationGuard::Instance > KDSingleApplicationGuard::instances() const { @@ -409,10 +414,10 @@ QList< KDSingleApplicationGuard::Instance > KDSingleApplicationGuard::instances( return result; } -/*! - Shuts down all other instances. This can only be called from the - the primary instance. - Shut down is done gracefully via QCoreApplication::quit(). +/*! + * Shuts down all other instances. This can only be called from the + * the primary instance. + * Shut down is done gracefully via QCoreApplication::quit(). */ void KDSingleApplicationGuard::shutdownOtherInstances() { @@ -426,9 +431,9 @@ void KDSingleApplicationGuard::shutdownOtherInstances() } /*! - Kills all other instances. This can only be called from the - the primary instance. - Killing is done via exit(1) + * Kills all other instances. This can only be called from the + * the primary instance. + * Killing is done via exit(1) */ void KDSingleApplicationGuard::killOtherInstances() { @@ -442,7 +447,7 @@ void KDSingleApplicationGuard::killOtherInstances() } /*! - \reimp + * \reimp */ void KDSingleApplicationGuard::timerEvent( QTimerEvent* event ) { @@ -456,11 +461,11 @@ void KDSingleApplicationGuard::timerEvent( QTimerEvent* event ) { KDLockedSharedMemoryPointer< InstanceRegister > instances( &d->mem ); - + for( int i = 1; i < KDSINGLEAPPLICATIONGUARD_NUMBER_OF_PROCESSES; ++i ) { ProcessInfo& info = instances->info[ i ]; - if( info.command & NewInstance ) + if( info.command & NewInstance ) { startedInstances.push_back( Instance( info.arguments(), info.pid ) ); info.command &= ~NewInstance; // clear NewInstance flag @@ -472,7 +477,7 @@ void KDSingleApplicationGuard::timerEvent( QTimerEvent* event ) } } } - + // one signal for every new instance - _after_ the memory segment was unlocked again for( QList< Instance >::const_iterator it = startedInstances.begin(); it != startedInstances.end(); ++it ) emit instanceStarted( *it ); @@ -495,7 +500,7 @@ void KDSingleApplicationGuard::timerEvent( QTimerEvent* event ) if( instances->info[ d->id ].command & BecomePrimaryCommand ) { // we became primary! - instances->info[ 0 ] = instances->info[ d->id ]; + instances->info[ 0 ] = instances->info[ d->id ]; instances->info[ d->id ] = ProcessInfo(); // change our id to 0 and declare the old slot as free d->id = 0; emit becamePrimaryInstance(); @@ -510,7 +515,7 @@ void KDSingleApplicationGuard::timerEvent( QTimerEvent* event ) d->id = -1; // becauso our d'tor won't be called anymore } } - + if( killOurSelf ) // kill our self takes precedence exit( 1 ); else if( shutDownOurSelf ) @@ -590,14 +595,14 @@ KDAB_UNITTEST_SIMPLE( KDSingleApplicationGuard, "kdcoretools" ) { spy3 = new QSignalSpy( guard3, SIGNAL( becamePrimaryInstance() ) ); assertFalse( guard3->isPrimaryInstance() ); } - + wait( 1000 ); assertEqual( spy3->count(), 1 ); assertEqual( guard3->instances().count(), 1 ); assertTrue( guard3->isPrimaryInstance() ); assertEqual( guard3->instances().first().arguments, qApp->arguments() ); - + QSignalSpy spyStarted( guard3, SIGNAL( instanceStarted( KDSingleApplicationGuard::Instance ) ) ); QSignalSpy spyExited( guard3, SIGNAL( instanceExited( KDSingleApplicationGuard::Instance ) ) ); @@ -609,13 +614,13 @@ KDAB_UNITTEST_SIMPLE( KDSingleApplicationGuard, "kdcoretools" ) { assertEqual( spyStarted.count(), 2 ); } - + wait( 1000 ); assertEqual( spyExited.count(), 2 ); delete spy3; delete guard3; - } +} #endif // KDTOOLSCORE_UNITTESTS From f6d4e0c63af9476f3d508ecb9d5760a3e9df6a5b Mon Sep 17 00:00:00 2001 From: Christian Muehlhaeuser Date: Fri, 25 Mar 2011 13:55:28 +0000 Subject: [PATCH 143/329] * Re-added launch page to installer. --- admin/win/nsi/revision.txt | 2 +- admin/win/nsi/tomahawk.nsi | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/admin/win/nsi/revision.txt b/admin/win/nsi/revision.txt index 105d7d9ad..0fd0714a5 100644 --- a/admin/win/nsi/revision.txt +++ b/admin/win/nsi/revision.txt @@ -1 +1 @@ -100 \ No newline at end of file +103 \ No newline at end of file diff --git a/admin/win/nsi/tomahawk.nsi b/admin/win/nsi/tomahawk.nsi index 416944114..11c2469ba 100644 --- a/admin/win/nsi/tomahawk.nsi +++ b/admin/win/nsi/tomahawk.nsi @@ -9,7 +9,7 @@ !define OPTION_SECTION_SC_DESKTOP !define OPTION_SECTION_SC_QUICK_LAUNCH !define OPTION_FINISHPAGE -;!define OPTION_FINISHPAGE_LAUNCHER +!define OPTION_FINISHPAGE_LAUNCHER !define OPTION_FINISHPAGE_RELEASE_NOTES ;----------------------------------------------------------------------------- From 247c65fbcc37f7100a1a2b8b7ad358d7c960b67f Mon Sep 17 00:00:00 2001 From: Jeff Mitchell Date: Fri, 25 Mar 2011 10:04:14 -0400 Subject: [PATCH 144/329] Emit finished when there's no actual data to return --- include/tomahawk/infosystem.h | 2 ++ src/infosystem/infosystem.cpp | 4 +++- 2 files changed, 5 insertions(+), 1 deletion(-) diff --git a/include/tomahawk/infosystem.h b/include/tomahawk/infosystem.h index 4b6c02dc5..c85e74f20 100644 --- a/include/tomahawk/infosystem.h +++ b/include/tomahawk/infosystem.h @@ -78,6 +78,8 @@ enum InfoType { InfoAlbumDate, InfoAlbumGenre, InfoAlbumComposer, + InfoAlbumCoverArt, + InfoMiscTopHotttness, InfoMiscTopTerms, diff --git a/src/infosystem/infosystem.cpp b/src/infosystem/infosystem.cpp index 5df0a2a01..0c0ca1513 100644 --- a/src/infosystem/infosystem.cpp +++ b/src/infosystem/infosystem.cpp @@ -59,6 +59,7 @@ void InfoSystem::getInfo(const QString &caller, const InfoType type, const QVari if (providers.isEmpty()) { emit info(QString(), Tomahawk::InfoSystem::InfoNoInfo, QVariant(), QVariant(), customData); + emit finished(caller); return; } @@ -66,6 +67,7 @@ void InfoSystem::getInfo(const QString &caller, const InfoType type, const QVari if (!ptr) { emit info(QString(), Tomahawk::InfoSystem::InfoNoInfo, QVariant(), QVariant(), customData); + emit finished(caller); return; } @@ -111,4 +113,4 @@ void InfoSystem::finishedSlot(QString target, Tomahawk::InfoSystem::InfoType typ } qDebug() << "emitting finished with target" << target; emit finished(target); -} \ No newline at end of file +} From c90a875de7a9072f3dec1113dd67f96ad30d8b07 Mon Sep 17 00:00:00 2001 From: Leo Franchi Date: Fri, 25 Mar 2011 15:31:46 -0400 Subject: [PATCH 145/329] add some eliding so playlists with long name don't increase the window size minor cleanup in ElidedLabel --- .../dynamic/widgets/CollapsibleControls.cpp | 3 +- .../dynamic/widgets/CollapsibleControls.h | 4 +- src/libtomahawk/playlist/infobar/infobar.cpp | 4 +- src/libtomahawk/playlist/infobar/infobar.ui | 11 ++++- src/libtomahawk/utils/elidedlabel.cpp | 42 +++++++++++++------ src/libtomahawk/utils/elidedlabel.h | 10 +++-- 6 files changed, 52 insertions(+), 22 deletions(-) diff --git a/src/libtomahawk/playlist/dynamic/widgets/CollapsibleControls.cpp b/src/libtomahawk/playlist/dynamic/widgets/CollapsibleControls.cpp index 36c04a2dd..65ac1564b 100644 --- a/src/libtomahawk/playlist/dynamic/widgets/CollapsibleControls.cpp +++ b/src/libtomahawk/playlist/dynamic/widgets/CollapsibleControls.cpp @@ -23,6 +23,7 @@ #include "dynamic/GeneratorInterface.h" #include "dynamic/DynamicControl.h" #include "utils/tomahawkutils.h" +#include "utils/elidedlabel.h" #include #include @@ -85,7 +86,7 @@ CollapsibleControls::init() m_summaryLayout->setMargin( 0 ); m_summaryWidget->setContentsMargins( 3, 0, 0, 0 ); - m_summary = new QLabel( m_summaryWidget ); + m_summary = new ElidedLabel( m_summaryWidget ); QFont f = m_summary->font(); f.setPointSize( f.pointSize() + 1 ); f.setBold( true ); diff --git a/src/libtomahawk/playlist/dynamic/widgets/CollapsibleControls.h b/src/libtomahawk/playlist/dynamic/widgets/CollapsibleControls.h index 36818b2dc..a242769d0 100644 --- a/src/libtomahawk/playlist/dynamic/widgets/CollapsibleControls.h +++ b/src/libtomahawk/playlist/dynamic/widgets/CollapsibleControls.h @@ -27,7 +27,7 @@ class QPaintEvent; class QHBoxLayout; class QTimeLine; class QToolButton; -class QLabel; +class ElidedLabel; class QStackedLayout; namespace Tomahawk { @@ -67,7 +67,7 @@ private: QWidget* m_summaryWidget; QHBoxLayout* m_summaryLayout; - QLabel* m_summary; + ElidedLabel* m_summary; QStackedLayout* m_expandL; QToolButton* m_summaryExpand; diff --git a/src/libtomahawk/playlist/infobar/infobar.cpp b/src/libtomahawk/playlist/infobar/infobar.cpp index c1c3a6e12..569c8a247 100644 --- a/src/libtomahawk/playlist/infobar/infobar.cpp +++ b/src/libtomahawk/playlist/infobar/infobar.cpp @@ -43,7 +43,7 @@ InfoBar::InfoBar( QWidget* parent ) boldFont.setPixelSize( 12 ); ui->descriptionLabel->setFont( boldFont ); - ui->descriptionLabel->setMargin( 2 ); + ui->descriptionLabel->setMargin( 10 ); QPalette whitePal = ui->captionLabel->palette(); whitePal.setColor( QPalette::Foreground, Qt::white ); @@ -52,6 +52,8 @@ InfoBar::InfoBar( QWidget* parent ) ui->descriptionLabel->setPalette( whitePal ); ui->captionLabel->setText( QString() ); + ui->captionLabel->setMargin( 6 ); + ui->descriptionLabel->setText( QString() ); ui->imageLabel->setText( QString() ); diff --git a/src/libtomahawk/playlist/infobar/infobar.ui b/src/libtomahawk/playlist/infobar/infobar.ui index 043b8f5d7..764fd040a 100644 --- a/src/libtomahawk/playlist/infobar/infobar.ui +++ b/src/libtomahawk/playlist/infobar/infobar.ui @@ -74,7 +74,7 @@ - + 0 @@ -87,7 +87,7 @@ - + 0 @@ -119,6 +119,13 @@ + + + ElidedLabel + QLabel +
utils/elidedlabel.h
+
+
diff --git a/src/libtomahawk/utils/elidedlabel.cpp b/src/libtomahawk/utils/elidedlabel.cpp index c490c7c85..9b4343383 100644 --- a/src/libtomahawk/utils/elidedlabel.cpp +++ b/src/libtomahawk/utils/elidedlabel.cpp @@ -22,6 +22,7 @@ #include #include #include +#include ElidedLabel::ElidedLabel( QWidget* parent, Qt::WindowFlags flags ) @@ -65,16 +66,16 @@ ElidedLabel::setText( const QString& text ) Qt::Alignment ElidedLabel::alignment() const { - return align; + return m_align; } void ElidedLabel::setAlignment( Qt::Alignment alignment ) { - if ( this->align != alignment ) + if ( m_align != alignment ) { - this->align = alignment; + m_align = alignment; update(); // no geometry change, repaint is sufficient } } @@ -83,27 +84,40 @@ ElidedLabel::setAlignment( Qt::Alignment alignment ) Qt::TextElideMode ElidedLabel::elideMode() const { - return mode; + return m_mode; } void ElidedLabel::setElideMode( Qt::TextElideMode mode ) { - if ( this->mode != mode ) + if ( m_mode != mode ) { - this->mode = mode; + m_mode = mode; updateLabel(); } } +void +ElidedLabel::setMargin( int margin ) +{ + m_margin = margin; +} + +int +ElidedLabel::margin() const +{ + return m_margin; +} + void ElidedLabel::init( const QString& txt ) { m_text = txt; - align = Qt::AlignLeft; - mode = Qt::ElideMiddle; + m_align = Qt::AlignLeft; + m_mode = Qt::ElideMiddle; + m_margin = 0; setContentsMargins( 0, 0, 0, 0 ); } @@ -128,7 +142,7 @@ ElidedLabel::sizeHint() const QSize ElidedLabel::minimumSizeHint() const { - switch ( mode ) + switch ( m_mode ) { case Qt::ElideNone: return sizeHint(); @@ -149,8 +163,10 @@ ElidedLabel::paintEvent( QPaintEvent* event ) QFrame::paintEvent( event ); QPainter p( this ); QRect r = contentsRect(); - const QString elidedText = fontMetrics().elidedText( m_text, mode, r.width() ); - p.drawText( r, align, elidedText ); + r.adjust( m_margin, m_margin, -m_margin, -m_margin ); + + const QString elidedText = fontMetrics().elidedText( m_text, m_mode, r.width() ); + p.drawText( r, m_align, elidedText ); } @@ -176,7 +192,7 @@ void ElidedLabel::mousePressEvent( QMouseEvent* event ) { QFrame::mousePressEvent( event ); - time.start(); + m_time.start(); } @@ -184,6 +200,6 @@ void ElidedLabel::mouseReleaseEvent( QMouseEvent* event ) { QFrame::mouseReleaseEvent( event ); - if ( time.elapsed() < qApp->doubleClickInterval() ) + if ( m_time.elapsed() < qApp->doubleClickInterval() ) emit clicked(); } diff --git a/src/libtomahawk/utils/elidedlabel.h b/src/libtomahawk/utils/elidedlabel.h index e78355ec0..2f45e1c97 100644 --- a/src/libtomahawk/utils/elidedlabel.h +++ b/src/libtomahawk/utils/elidedlabel.h @@ -44,6 +44,9 @@ public: Qt::TextElideMode elideMode() const; void setElideMode( Qt::TextElideMode mode ); + void setMargin( int margin ); + int margin() const; + virtual QSize sizeHint() const; virtual QSize minimumSizeHint() const; @@ -64,10 +67,11 @@ protected: virtual void paintEvent( QPaintEvent* event ); private: - QTime time; + QTime m_time; QString m_text; - Qt::Alignment align; - Qt::TextElideMode mode; + Qt::Alignment m_align; + Qt::TextElideMode m_mode; + int m_margin; }; #endif // ELIDEDLABEL_H From 8f46db649f2ca76cbdc93a59c76f43028f5e9d8d Mon Sep 17 00:00:00 2001 From: Leo Franchi Date: Fri, 25 Mar 2011 15:31:46 -0400 Subject: [PATCH 146/329] add some eliding so playlists with long name don't increase the window size minor cleanup in ElidedLabel --- .../dynamic/widgets/CollapsibleControls.cpp | 3 +- .../dynamic/widgets/CollapsibleControls.h | 4 +- src/libtomahawk/playlist/infobar/infobar.cpp | 4 +- src/libtomahawk/playlist/infobar/infobar.ui | 11 ++++- src/libtomahawk/utils/elidedlabel.cpp | 42 +++++++++++++------ src/libtomahawk/utils/elidedlabel.h | 10 +++-- 6 files changed, 52 insertions(+), 22 deletions(-) diff --git a/src/libtomahawk/playlist/dynamic/widgets/CollapsibleControls.cpp b/src/libtomahawk/playlist/dynamic/widgets/CollapsibleControls.cpp index 36c04a2dd..65ac1564b 100644 --- a/src/libtomahawk/playlist/dynamic/widgets/CollapsibleControls.cpp +++ b/src/libtomahawk/playlist/dynamic/widgets/CollapsibleControls.cpp @@ -23,6 +23,7 @@ #include "dynamic/GeneratorInterface.h" #include "dynamic/DynamicControl.h" #include "utils/tomahawkutils.h" +#include "utils/elidedlabel.h" #include #include @@ -85,7 +86,7 @@ CollapsibleControls::init() m_summaryLayout->setMargin( 0 ); m_summaryWidget->setContentsMargins( 3, 0, 0, 0 ); - m_summary = new QLabel( m_summaryWidget ); + m_summary = new ElidedLabel( m_summaryWidget ); QFont f = m_summary->font(); f.setPointSize( f.pointSize() + 1 ); f.setBold( true ); diff --git a/src/libtomahawk/playlist/dynamic/widgets/CollapsibleControls.h b/src/libtomahawk/playlist/dynamic/widgets/CollapsibleControls.h index 36818b2dc..a242769d0 100644 --- a/src/libtomahawk/playlist/dynamic/widgets/CollapsibleControls.h +++ b/src/libtomahawk/playlist/dynamic/widgets/CollapsibleControls.h @@ -27,7 +27,7 @@ class QPaintEvent; class QHBoxLayout; class QTimeLine; class QToolButton; -class QLabel; +class ElidedLabel; class QStackedLayout; namespace Tomahawk { @@ -67,7 +67,7 @@ private: QWidget* m_summaryWidget; QHBoxLayout* m_summaryLayout; - QLabel* m_summary; + ElidedLabel* m_summary; QStackedLayout* m_expandL; QToolButton* m_summaryExpand; diff --git a/src/libtomahawk/playlist/infobar/infobar.cpp b/src/libtomahawk/playlist/infobar/infobar.cpp index c1c3a6e12..569c8a247 100644 --- a/src/libtomahawk/playlist/infobar/infobar.cpp +++ b/src/libtomahawk/playlist/infobar/infobar.cpp @@ -43,7 +43,7 @@ InfoBar::InfoBar( QWidget* parent ) boldFont.setPixelSize( 12 ); ui->descriptionLabel->setFont( boldFont ); - ui->descriptionLabel->setMargin( 2 ); + ui->descriptionLabel->setMargin( 10 ); QPalette whitePal = ui->captionLabel->palette(); whitePal.setColor( QPalette::Foreground, Qt::white ); @@ -52,6 +52,8 @@ InfoBar::InfoBar( QWidget* parent ) ui->descriptionLabel->setPalette( whitePal ); ui->captionLabel->setText( QString() ); + ui->captionLabel->setMargin( 6 ); + ui->descriptionLabel->setText( QString() ); ui->imageLabel->setText( QString() ); diff --git a/src/libtomahawk/playlist/infobar/infobar.ui b/src/libtomahawk/playlist/infobar/infobar.ui index 043b8f5d7..764fd040a 100644 --- a/src/libtomahawk/playlist/infobar/infobar.ui +++ b/src/libtomahawk/playlist/infobar/infobar.ui @@ -74,7 +74,7 @@ - + 0 @@ -87,7 +87,7 @@ - + 0 @@ -119,6 +119,13 @@ + + + ElidedLabel + QLabel +
utils/elidedlabel.h
+
+
diff --git a/src/libtomahawk/utils/elidedlabel.cpp b/src/libtomahawk/utils/elidedlabel.cpp index c490c7c85..9b4343383 100644 --- a/src/libtomahawk/utils/elidedlabel.cpp +++ b/src/libtomahawk/utils/elidedlabel.cpp @@ -22,6 +22,7 @@ #include #include #include +#include ElidedLabel::ElidedLabel( QWidget* parent, Qt::WindowFlags flags ) @@ -65,16 +66,16 @@ ElidedLabel::setText( const QString& text ) Qt::Alignment ElidedLabel::alignment() const { - return align; + return m_align; } void ElidedLabel::setAlignment( Qt::Alignment alignment ) { - if ( this->align != alignment ) + if ( m_align != alignment ) { - this->align = alignment; + m_align = alignment; update(); // no geometry change, repaint is sufficient } } @@ -83,27 +84,40 @@ ElidedLabel::setAlignment( Qt::Alignment alignment ) Qt::TextElideMode ElidedLabel::elideMode() const { - return mode; + return m_mode; } void ElidedLabel::setElideMode( Qt::TextElideMode mode ) { - if ( this->mode != mode ) + if ( m_mode != mode ) { - this->mode = mode; + m_mode = mode; updateLabel(); } } +void +ElidedLabel::setMargin( int margin ) +{ + m_margin = margin; +} + +int +ElidedLabel::margin() const +{ + return m_margin; +} + void ElidedLabel::init( const QString& txt ) { m_text = txt; - align = Qt::AlignLeft; - mode = Qt::ElideMiddle; + m_align = Qt::AlignLeft; + m_mode = Qt::ElideMiddle; + m_margin = 0; setContentsMargins( 0, 0, 0, 0 ); } @@ -128,7 +142,7 @@ ElidedLabel::sizeHint() const QSize ElidedLabel::minimumSizeHint() const { - switch ( mode ) + switch ( m_mode ) { case Qt::ElideNone: return sizeHint(); @@ -149,8 +163,10 @@ ElidedLabel::paintEvent( QPaintEvent* event ) QFrame::paintEvent( event ); QPainter p( this ); QRect r = contentsRect(); - const QString elidedText = fontMetrics().elidedText( m_text, mode, r.width() ); - p.drawText( r, align, elidedText ); + r.adjust( m_margin, m_margin, -m_margin, -m_margin ); + + const QString elidedText = fontMetrics().elidedText( m_text, m_mode, r.width() ); + p.drawText( r, m_align, elidedText ); } @@ -176,7 +192,7 @@ void ElidedLabel::mousePressEvent( QMouseEvent* event ) { QFrame::mousePressEvent( event ); - time.start(); + m_time.start(); } @@ -184,6 +200,6 @@ void ElidedLabel::mouseReleaseEvent( QMouseEvent* event ) { QFrame::mouseReleaseEvent( event ); - if ( time.elapsed() < qApp->doubleClickInterval() ) + if ( m_time.elapsed() < qApp->doubleClickInterval() ) emit clicked(); } diff --git a/src/libtomahawk/utils/elidedlabel.h b/src/libtomahawk/utils/elidedlabel.h index e78355ec0..2f45e1c97 100644 --- a/src/libtomahawk/utils/elidedlabel.h +++ b/src/libtomahawk/utils/elidedlabel.h @@ -44,6 +44,9 @@ public: Qt::TextElideMode elideMode() const; void setElideMode( Qt::TextElideMode mode ); + void setMargin( int margin ); + int margin() const; + virtual QSize sizeHint() const; virtual QSize minimumSizeHint() const; @@ -64,10 +67,11 @@ protected: virtual void paintEvent( QPaintEvent* event ); private: - QTime time; + QTime m_time; QString m_text; - Qt::Alignment align; - Qt::TextElideMode mode; + Qt::Alignment m_align; + Qt::TextElideMode m_mode; + int m_margin; }; #endif // ELIDEDLABEL_H From 631c39163d5c0e2862c78de883614ecdc2c64617 Mon Sep 17 00:00:00 2001 From: Leo Franchi Date: Fri, 25 Mar 2011 17:45:32 -0400 Subject: [PATCH 147/329] don't install a static lib --- thirdparty/qxt/qxtweb-standalone/CMakeLists.txt | 2 -- 1 file changed, 2 deletions(-) diff --git a/thirdparty/qxt/qxtweb-standalone/CMakeLists.txt b/thirdparty/qxt/qxtweb-standalone/CMakeLists.txt index b1f51d1ba..ec81d04cb 100644 --- a/thirdparty/qxt/qxtweb-standalone/CMakeLists.txt +++ b/thirdparty/qxt/qxtweb-standalone/CMakeLists.txt @@ -102,5 +102,3 @@ target_link_libraries( qxtweb-standalone # ${QT_LIBRARIES} # "${CMAKE_CURRENT_SOURCE_DIR}/libqxtweb-standalone.a" # ) - -INSTALL( TARGETS qxtweb-standalone DESTINATION lib${LIB_SUFFIX} ) From 6b7300c1b3117b42d42b5a175e66b145e9b6a7d2 Mon Sep 17 00:00:00 2001 From: Jeff Mitchell Date: Fri, 25 Mar 2011 18:22:51 -0400 Subject: [PATCH 148/329] Possibly fix crash related to twitter authentication --- src/sip/twitter/twitter.cpp | 69 ++++++++++++++++++++++++++++++++----- src/sip/twitter/twitter.h | 2 ++ 2 files changed, 63 insertions(+), 8 deletions(-) diff --git a/src/sip/twitter/twitter.cpp b/src/sip/twitter/twitter.cpp index 334a6c0f0..c4e3e9bb5 100644 --- a/src/sip/twitter/twitter.cpp +++ b/src/sip/twitter/twitter.cpp @@ -130,16 +130,30 @@ TwitterPlugin::connectPlugin( bool /*startup*/ ) return m_cachedPeers.isEmpty(); } - delete m_twitterAuth.data(); + if ( refreshTwitterAuth() ) + { + QTweetAccountVerifyCredentials *credVerifier = new QTweetAccountVerifyCredentials( m_twitterAuth.data(), this ); + connect( credVerifier, SIGNAL( parsedUser(const QTweetUser &) ), SLOT( connectAuthVerifyReply(const QTweetUser &) ) ); + credVerifier->verify(); + } + + return true; +} + +bool +TwitterPlugin::refreshTwitterAuth() +{ m_twitterAuth = QWeakPointer( new TomahawkOAuthTwitter( this ) ); + + TomahawkSettings *settings = TomahawkSettings::instance(); + + if ( m_twitterAuth.isNull() ) + return false; + m_twitterAuth.data()->setNetworkAccessManager( TomahawkUtils::nam() ); m_twitterAuth.data()->setOAuthToken( settings->twitterOAuthToken().toLatin1() ); m_twitterAuth.data()->setOAuthTokenSecret( settings->twitterOAuthTokenSecret().toLatin1() ); - - QTweetAccountVerifyCredentials *credVerifier = new QTweetAccountVerifyCredentials( m_twitterAuth.data(), this ); - connect( credVerifier, SIGNAL( parsedUser(const QTweetUser &) ), SLOT( connectAuthVerifyReply(const QTweetUser &) ) ); - credVerifier->verify(); - + return true; } @@ -189,8 +203,17 @@ TwitterPlugin::connectAuthVerifyReply( const QTweetUser &user ) } else { - qDebug() << "TwitterPlugin auth pointer was null!"; - m_isAuthed = false; + if ( refreshTwitterAuth() ) + { + QTweetAccountVerifyCredentials *credVerifier = new QTweetAccountVerifyCredentials( m_twitterAuth.data(), this ); + connect( credVerifier, SIGNAL( parsedUser(const QTweetUser &) ), SLOT( connectAuthVerifyReply(const QTweetUser &) ) ); + credVerifier->verify(); + } + else + { + qDebug() << "TwitterPlugin auth pointer was null!"; + m_isAuthed = false; + } } } } @@ -201,6 +224,21 @@ TwitterPlugin::checkTimerFired() if ( !isValid() ) return; + if ( m_twitterAuth.isNull() ) + { + if ( refreshTwitterAuth() ) + { + QTweetAccountVerifyCredentials *credVerifier = new QTweetAccountVerifyCredentials( m_twitterAuth.data(), this ); + connect( credVerifier, SIGNAL( parsedUser(const QTweetUser &) ), SLOT( connectAuthVerifyReply(const QTweetUser &) ) ); + credVerifier->verify(); + } + else + { + qDebug() << "TwitterPlugin auth went null somehow and could not refresh"; + return; + } + } + if ( m_cachedFriendsSinceId == 0 ) m_cachedFriendsSinceId = TomahawkSettings::instance()->twitterCachedFriendsSinceId(); @@ -224,6 +262,21 @@ TwitterPlugin::connectTimerFired() { if ( !isValid() || m_cachedPeers.isEmpty() ) return; + + if ( m_twitterAuth.isNull() ) + { + if ( refreshTwitterAuth() ) + { + QTweetAccountVerifyCredentials *credVerifier = new QTweetAccountVerifyCredentials( m_twitterAuth.data(), this ); + connect( credVerifier, SIGNAL( parsedUser(const QTweetUser &) ), SLOT( connectAuthVerifyReply(const QTweetUser &) ) ); + credVerifier->verify(); + } + else + { + qDebug() << "TwitterPlugin auth went null somehow and could not refresh"; + return; + } + } QString myScreenName = TomahawkSettings::instance()->twitterScreenName(); QList peerlist = m_cachedPeers.keys(); diff --git a/src/sip/twitter/twitter.h b/src/sip/twitter/twitter.h index 3b03440af..e0c38b479 100644 --- a/src/sip/twitter/twitter.h +++ b/src/sip/twitter/twitter.h @@ -91,6 +91,8 @@ private slots: void makeConnection( const QString &screenName, const QHash< QString, QVariant > &peerdata ); private: + bool refreshTwitterAuth(); + QWeakPointer< TomahawkOAuthTwitter > m_twitterAuth; QWeakPointer< QTweetFriendsTimeline > m_friendsTimeline; QWeakPointer< QTweetMentions > m_mentions; From 422f9a9022d7500e29a0fd55ba7a8c8c5985e98f Mon Sep 17 00:00:00 2001 From: Jeff Mitchell Date: Fri, 25 Mar 2011 18:22:51 -0400 Subject: [PATCH 149/329] Possibly fix crash related to twitter authentication --- src/sip/twitter/twitter.cpp | 69 ++++++++++++++++++++++++++++++++----- src/sip/twitter/twitter.h | 2 ++ 2 files changed, 63 insertions(+), 8 deletions(-) diff --git a/src/sip/twitter/twitter.cpp b/src/sip/twitter/twitter.cpp index 334a6c0f0..c4e3e9bb5 100644 --- a/src/sip/twitter/twitter.cpp +++ b/src/sip/twitter/twitter.cpp @@ -130,16 +130,30 @@ TwitterPlugin::connectPlugin( bool /*startup*/ ) return m_cachedPeers.isEmpty(); } - delete m_twitterAuth.data(); + if ( refreshTwitterAuth() ) + { + QTweetAccountVerifyCredentials *credVerifier = new QTweetAccountVerifyCredentials( m_twitterAuth.data(), this ); + connect( credVerifier, SIGNAL( parsedUser(const QTweetUser &) ), SLOT( connectAuthVerifyReply(const QTweetUser &) ) ); + credVerifier->verify(); + } + + return true; +} + +bool +TwitterPlugin::refreshTwitterAuth() +{ m_twitterAuth = QWeakPointer( new TomahawkOAuthTwitter( this ) ); + + TomahawkSettings *settings = TomahawkSettings::instance(); + + if ( m_twitterAuth.isNull() ) + return false; + m_twitterAuth.data()->setNetworkAccessManager( TomahawkUtils::nam() ); m_twitterAuth.data()->setOAuthToken( settings->twitterOAuthToken().toLatin1() ); m_twitterAuth.data()->setOAuthTokenSecret( settings->twitterOAuthTokenSecret().toLatin1() ); - - QTweetAccountVerifyCredentials *credVerifier = new QTweetAccountVerifyCredentials( m_twitterAuth.data(), this ); - connect( credVerifier, SIGNAL( parsedUser(const QTweetUser &) ), SLOT( connectAuthVerifyReply(const QTweetUser &) ) ); - credVerifier->verify(); - + return true; } @@ -189,8 +203,17 @@ TwitterPlugin::connectAuthVerifyReply( const QTweetUser &user ) } else { - qDebug() << "TwitterPlugin auth pointer was null!"; - m_isAuthed = false; + if ( refreshTwitterAuth() ) + { + QTweetAccountVerifyCredentials *credVerifier = new QTweetAccountVerifyCredentials( m_twitterAuth.data(), this ); + connect( credVerifier, SIGNAL( parsedUser(const QTweetUser &) ), SLOT( connectAuthVerifyReply(const QTweetUser &) ) ); + credVerifier->verify(); + } + else + { + qDebug() << "TwitterPlugin auth pointer was null!"; + m_isAuthed = false; + } } } } @@ -201,6 +224,21 @@ TwitterPlugin::checkTimerFired() if ( !isValid() ) return; + if ( m_twitterAuth.isNull() ) + { + if ( refreshTwitterAuth() ) + { + QTweetAccountVerifyCredentials *credVerifier = new QTweetAccountVerifyCredentials( m_twitterAuth.data(), this ); + connect( credVerifier, SIGNAL( parsedUser(const QTweetUser &) ), SLOT( connectAuthVerifyReply(const QTweetUser &) ) ); + credVerifier->verify(); + } + else + { + qDebug() << "TwitterPlugin auth went null somehow and could not refresh"; + return; + } + } + if ( m_cachedFriendsSinceId == 0 ) m_cachedFriendsSinceId = TomahawkSettings::instance()->twitterCachedFriendsSinceId(); @@ -224,6 +262,21 @@ TwitterPlugin::connectTimerFired() { if ( !isValid() || m_cachedPeers.isEmpty() ) return; + + if ( m_twitterAuth.isNull() ) + { + if ( refreshTwitterAuth() ) + { + QTweetAccountVerifyCredentials *credVerifier = new QTweetAccountVerifyCredentials( m_twitterAuth.data(), this ); + connect( credVerifier, SIGNAL( parsedUser(const QTweetUser &) ), SLOT( connectAuthVerifyReply(const QTweetUser &) ) ); + credVerifier->verify(); + } + else + { + qDebug() << "TwitterPlugin auth went null somehow and could not refresh"; + return; + } + } QString myScreenName = TomahawkSettings::instance()->twitterScreenName(); QList peerlist = m_cachedPeers.keys(); diff --git a/src/sip/twitter/twitter.h b/src/sip/twitter/twitter.h index 3b03440af..e0c38b479 100644 --- a/src/sip/twitter/twitter.h +++ b/src/sip/twitter/twitter.h @@ -91,6 +91,8 @@ private slots: void makeConnection( const QString &screenName, const QHash< QString, QVariant > &peerdata ); private: + bool refreshTwitterAuth(); + QWeakPointer< TomahawkOAuthTwitter > m_twitterAuth; QWeakPointer< QTweetFriendsTimeline > m_friendsTimeline; QWeakPointer< QTweetMentions > m_mentions; From 0530faf15cc7c97615ee8aca57fe1df1f5f193fe Mon Sep 17 00:00:00 2001 From: Christian Muehlhaeuser Date: Sat, 26 Mar 2011 07:06:35 +0100 Subject: [PATCH 150/329] * Updated README. --- README | 20 ++++++++------------ 1 file changed, 8 insertions(+), 12 deletions(-) diff --git a/README b/README index b5814e47a..f33c4f948 100644 --- a/README +++ b/README @@ -1,9 +1,9 @@ Quickstart on Ubuntu -------------------- - $ sudo apt-get install build-essential cmake libtag1c2a libtag1-dev liblastfm-dev libqt4-dev \ - libqt4-sql-sqlite libvorbis-dev libmad0-dev libflac++-dev libasound2-dev \ - libboost-dev zlib1g-dev libgnutls-dev pkg-config + $ sudo apt-get install build-essential cmake libtag1c2a libtag1-dev libqt4-dev libqt4-sql-sqlite \ + libvorbis-dev libmad0-dev libflac++-dev libasound2-dev libboost-dev zlib1g-dev \ + libgnutls-dev pkg-config Gloox 1.0 (XMPP library) @@ -30,7 +30,7 @@ QJson (Qt JSON library) $ ./configure && make $ sudo make install -libEchonest 1.1.1 +libEchonest 1.1.4 --------------- See: http://projects.kde.org/projects/playground/libs/libechonest/ @@ -57,7 +57,7 @@ Quickstart on OS X Install homebrew $ ruby -e "$(curl -fsSL https://gist.github.com/raw/323731/install_homebrew.rb)" - $ brew install cmake qt qjson gloox libmad libvorbis flac taglib boost liblastfm + $ brew install cmake qt qjson gloox libmad libvorbis flac taglib boost Install libEchnoest & CLucene as per the above instructions. @@ -94,23 +94,19 @@ Dependencies libvorbis 1.2.3 http://xiph.org/vorbis/ libogg 1.1.4 http://xiph.org/ogg/ libflac++ 1.2.0 http://flac.sourceforge.net/ - liblastfm 0.3.3 http://github.com/mxcl/liblastfm/ - libechonest 1.1.1 http://projects.kde.org/projects/playground/libs/libechonest/ + libechonest 1.1.4 http://projects.kde.org/projects/playground/libs/libechonest/ Third party libraries that we ship with our source: RtAudio 4.0.7 http://www.music.mcgill.ca/~gary/rtaudio/ MiniUPnP http://miniupnp.free.fr/ + liblastfm 0.4.0 http://github.com/jonocole/liblastfm/ To build the app: ----------------- $ mkdir build && cd build - - Pick one of the following two choices. If uncertain pick the second one, you probably want a GUI. - $ cmake -Dgui=no .. # enables headless mode, build without GUI - $ cmake .. # normal build including GUI - + $ cmake .. $ make To run the app: From 6da813d4c3c5c677637c4fa4ee37cf44330ce710 Mon Sep 17 00:00:00 2001 From: Christian Muehlhaeuser Date: Sat, 26 Mar 2011 07:19:15 +0100 Subject: [PATCH 151/329] * Updated stable README. --- README | 10 +++------- 1 file changed, 3 insertions(+), 7 deletions(-) diff --git a/README b/README index b5814e47a..a90caf57e 100644 --- a/README +++ b/README @@ -30,7 +30,7 @@ QJson (Qt JSON library) $ ./configure && make $ sudo make install -libEchonest 1.1.1 +libEchonest 1.1.4 --------------- See: http://projects.kde.org/projects/playground/libs/libechonest/ @@ -95,7 +95,7 @@ Dependencies libogg 1.1.4 http://xiph.org/ogg/ libflac++ 1.2.0 http://flac.sourceforge.net/ liblastfm 0.3.3 http://github.com/mxcl/liblastfm/ - libechonest 1.1.1 http://projects.kde.org/projects/playground/libs/libechonest/ + libechonest 1.1.4 http://projects.kde.org/projects/playground/libs/libechonest/ Third party libraries that we ship with our source: @@ -106,11 +106,7 @@ Dependencies To build the app: ----------------- $ mkdir build && cd build - - Pick one of the following two choices. If uncertain pick the second one, you probably want a GUI. - $ cmake -Dgui=no .. # enables headless mode, build without GUI - $ cmake .. # normal build including GUI - + $ cmake .. $ make To run the app: From 5c09fd2360b3e4c2e8611fc6d1d238e47ae6e023 Mon Sep 17 00:00:00 2001 From: Jeff Mitchell Date: Sat, 26 Mar 2011 09:36:31 -0400 Subject: [PATCH 152/329] Make a Last FM InfoSystem plugin and use it to handle now playing and scrobbling --- include/tomahawk/infosystem.h | 3 + src/CMakeLists.txt | 2 + src/infosystem/infoplugins/lastfmplugin.cpp | 251 ++++++++++++++++++++ src/infosystem/infoplugins/lastfmplugin.h | 69 ++++++ src/infosystem/infosystem.cpp | 3 + src/scrobbler.cpp | 192 +++------------ src/scrobbler.h | 17 +- src/tomahawkapp.cpp | 6 +- 8 files changed, 376 insertions(+), 167 deletions(-) create mode 100644 src/infosystem/infoplugins/lastfmplugin.cpp create mode 100644 src/infosystem/infoplugins/lastfmplugin.h diff --git a/include/tomahawk/infosystem.h b/include/tomahawk/infosystem.h index c85e74f20..80c400301 100644 --- a/include/tomahawk/infosystem.h +++ b/include/tomahawk/infosystem.h @@ -83,6 +83,9 @@ enum InfoType { InfoMiscTopHotttness, InfoMiscTopTerms, + InfoMiscSubmitNowPlaying, + InfoMiscSubmitScrobble, + InfoNoInfo }; diff --git a/src/CMakeLists.txt b/src/CMakeLists.txt index 20b2eb693..271c84211 100644 --- a/src/CMakeLists.txt +++ b/src/CMakeLists.txt @@ -36,6 +36,7 @@ SET( tomahawkSources ${tomahawkSources} infosystem/infosystem.cpp infosystem/infoplugins/echonestplugin.cpp + infosystem/infoplugins/lastfmplugin.cpp infosystem/infoplugins/musixmatchplugin.cpp web/api_v1.cpp @@ -77,6 +78,7 @@ SET( tomahawkHeaders ${tomahawkHeaders} sip/SipHandler.h infosystem/infoplugins/echonestplugin.h + infosystem/infoplugins/lastfmplugin.h infosystem/infoplugins/musixmatchplugin.h web/api_v1.h diff --git a/src/infosystem/infoplugins/lastfmplugin.cpp b/src/infosystem/infoplugins/lastfmplugin.cpp new file mode 100644 index 000000000..22e9c70cb --- /dev/null +++ b/src/infosystem/infoplugins/lastfmplugin.cpp @@ -0,0 +1,251 @@ +/* === This file is part of Tomahawk Player - === + * + * Copyright 2010-2011, Christian Muehlhaeuser + * + * Tomahawk is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * Tomahawk is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with Tomahawk. If not, see . + */ + +#include "lastfmplugin.h" + +#include +#include +#include + +#include "album.h" +#include "typedefs.h" +#include "audio/audioengine.h" +#include "tomahawksettings.h" +#include "tomahawk/tomahawkapp.h" + +#include +#include + +using namespace Tomahawk::InfoSystem; + +static QString +md5( const QByteArray& src ) +{ + QByteArray const digest = QCryptographicHash::hash( src, QCryptographicHash::Md5 ); + return QString::fromLatin1( digest.toHex() ).rightJustified( 32, '0' ); +} + + +LastFmPlugin::LastFmPlugin( QObject* parent ) + : InfoPlugin(parent) + , m_scrobbler( 0 ) + , m_authJob( 0 ) +{ + QSet< InfoType > supportedTypes; + supportedTypes << Tomahawk::InfoSystem::InfoMiscSubmitScrobble << Tomahawk::InfoSystem::InfoMiscSubmitNowPlaying; + qobject_cast< InfoSystem* >(parent)->registerInfoTypes(this, supportedTypes); + +/* + Your API Key is 7194b85b6d1f424fe1668173a78c0c4a + Your secret is ba80f1df6d27ae63e9cb1d33ccf2052f +*/ + + // Flush session key cache + TomahawkSettings::instance()->setLastFmSessionKey( QByteArray() ); + + lastfm::ws::ApiKey = "7194b85b6d1f424fe1668173a78c0c4a"; + lastfm::ws::SharedSecret = "ba80f1df6d27ae63e9cb1d33ccf2052f"; + lastfm::ws::Username = TomahawkSettings::instance()->lastFmUsername(); + + m_pw = TomahawkSettings::instance()->lastFmPassword(); + + if( TomahawkSettings::instance()->scrobblingEnabled() && !lastfm::ws::Username.isEmpty() ) + { + createScrobbler(); + } + + //HACK work around a bug in liblastfm---it doesn't create its config dir, so when it + // tries to write the track cache, it fails silently. until we have a fixed version, do this + // code taken from Amarok (src/services/lastfm/ScrobblerAdapter.cpp) +#ifdef Q_WS_X11 + QString lpath = QDir::home().filePath( ".local/share/Last.fm" ); + QDir ldir = QDir( lpath ); + if( !ldir.exists() ) + { + ldir.mkpath( lpath ); + } +#endif + + connect( TomahawkSettings::instance(), SIGNAL( changed() ), + SLOT( settingsChanged() ), Qt::QueuedConnection ); +} + + +LastFmPlugin::~LastFmPlugin() +{ + delete m_scrobbler; +} + +void +LastFmPlugin::getInfo( const QString &caller, const InfoType type, const QVariant& data, Tomahawk::InfoSystem::InfoCustomDataHash customData ) +{ + qDebug() << Q_FUNC_INFO; + if ( type == InfoMiscSubmitNowPlaying ) + nowPlaying( caller, type, data, customData ); + else if ( type == InfoMiscSubmitScrobble ) + scrobble( caller, type, data, customData ); + else + dataError( caller, type, data, customData ); +} + +void +LastFmPlugin::nowPlaying( const QString &caller, const InfoType type, const QVariant& data, Tomahawk::InfoSystem::InfoCustomDataHash &customData ) +{ + if ( !data.canConvert< Tomahawk::InfoSystem::InfoCustomDataHash >() || !m_scrobbler ) + { + dataError( caller, type, data, customData ); + return; + } + InfoCustomDataHash hash = data.value< Tomahawk::InfoSystem::InfoCustomDataHash >(); + if ( !hash.contains( "title" ) || !hash.contains( "artist" ) || !hash.contains( "album" ) || !hash.contains( "duration" ) ) + { + dataError( caller, type, data, customData ); + return; + } + + m_track = lastfm::MutableTrack(); + m_track.stamp(); + + m_track.setTitle( hash["title"].toString() ); + m_track.setArtist( hash["artist"].toString() ); + m_track.setAlbum( hash["album"].toString() ); + m_track.setDuration( hash["duration"].toUInt() ); + m_track.setSource( lastfm::Track::Player ); + + m_scrobbler->nowPlaying( m_track ); + emit info( caller, type, data, QVariant(), customData ); + emit finished( caller, type ); +} + +void +LastFmPlugin::dataError( const QString &caller, const InfoType type, const QVariant& data, Tomahawk::InfoSystem::InfoCustomDataHash &customData ) +{ + emit info( caller, type, data, QVariant(), customData ); + emit finished( caller, type ); + return; +} + +void +LastFmPlugin::scrobble( const QString &caller, const InfoType type, const QVariant& data, Tomahawk::InfoSystem::InfoCustomDataHash &customData ) +{ + Q_ASSERT( QThread::currentThread() == thread() ); + + if ( !m_scrobbler || m_track.isNull() ) + { + dataError( caller, type, data, customData ); + return; + } + + qDebug() << Q_FUNC_INFO << m_track.toString(); + m_scrobbler->cache( m_track ); + m_scrobbler->submit(); + + emit info( caller, type, data, QVariant(), customData ); + emit finished( caller, type ); +} + + +void +LastFmPlugin::settingsChanged() +{ + if( !m_scrobbler && TomahawkSettings::instance()->scrobblingEnabled() ) + { // can simply create the scrobbler + lastfm::ws::Username = TomahawkSettings::instance()->lastFmUsername(); + m_pw = TomahawkSettings::instance()->lastFmPassword(); + + createScrobbler(); + } + else if( m_scrobbler && !TomahawkSettings::instance()->scrobblingEnabled() ) + { + delete m_scrobbler; + m_scrobbler = 0; + } + else if( TomahawkSettings::instance()->lastFmUsername() != lastfm::ws::Username || + TomahawkSettings::instance()->lastFmPassword() != m_pw ) + { + lastfm::ws::Username = TomahawkSettings::instance()->lastFmUsername(); + // credentials have changed, have to re-create scrobbler for them to take effect + if( m_scrobbler ) + delete m_scrobbler; + + createScrobbler(); + } +} + + +void +LastFmPlugin::onAuthenticated() +{ + if( !m_authJob ) + { + qDebug() << Q_FUNC_INFO << "Help! No longer got a last.fm auth job!"; + return; + } + + if( m_authJob->error() == QNetworkReply::NoError ) + { + lastfm::XmlQuery lfm = lastfm::XmlQuery( m_authJob->readAll() ); + + if( lfm.children( "error" ).size() > 0 ) + { + qDebug() << "Error from authenticating with Last.fm service:" << lfm.text(); + TomahawkSettings::instance()->setLastFmSessionKey( QByteArray() ); + + } + else + { + lastfm::ws::SessionKey = lfm[ "session" ][ "key" ].text(); + + TomahawkSettings::instance()->setLastFmSessionKey( lastfm::ws::SessionKey.toLatin1() ); + + if( TomahawkSettings::instance()->scrobblingEnabled() ) + m_scrobbler = new lastfm::Audioscrobbler( "thk" ); + } + } + else + { + qDebug() << "Got error in Last.fm authentication job:" << m_authJob->errorString(); + } + + m_authJob->deleteLater(); +} + + +void +LastFmPlugin::createScrobbler() +{ + if( TomahawkSettings::instance()->lastFmSessionKey().isEmpty() ) // no session key, so get one + { + QString authToken = md5( ( lastfm::ws::Username.toLower() + md5( m_pw.toUtf8() ) ).toUtf8() ); + + QMap query; + query[ "method" ] = "auth.getMobileSession"; + query[ "username" ] = lastfm::ws::Username; + query[ "authToken" ] = authToken; + m_authJob = lastfm::ws::post( query ); + + connect( m_authJob, SIGNAL( finished() ), SLOT( onAuthenticated() ) ); + } + else + { + lastfm::ws::SessionKey = TomahawkSettings::instance()->lastFmSessionKey(); + + m_scrobbler = new lastfm::Audioscrobbler( "thk" ); + m_scrobbler->moveToThread( thread() ); + } +} diff --git a/src/infosystem/infoplugins/lastfmplugin.h b/src/infosystem/infoplugins/lastfmplugin.h new file mode 100644 index 000000000..775bfd3c8 --- /dev/null +++ b/src/infosystem/infoplugins/lastfmplugin.h @@ -0,0 +1,69 @@ +/* === This file is part of Tomahawk Player - === + * + * Copyright 2010-2011, Christian Muehlhaeuser + * + * Tomahawk is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * Tomahawk is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with Tomahawk. If not, see . + */ + +#ifndef LASTFMPLUGIN_H +#define LASTFMPLUGIN_H +#include "tomahawk/infosystem.h" +#include "result.h" + +#include +#include +#include + +#include + +class QNetworkReply; + +namespace Tomahawk +{ + +namespace InfoSystem +{ + +class LastFmPlugin : public InfoPlugin +{ + Q_OBJECT + +public: + LastFmPlugin( QObject *parent ); + virtual ~LastFmPlugin(); + + void getInfo( const QString &caller, const InfoType type, const QVariant &data, InfoCustomDataHash customData ); + +public slots: + void settingsChanged(); + void onAuthenticated(); + +private: + void scrobble( const QString &caller, const InfoType type, const QVariant& data, InfoCustomDataHash &customData ); + void createScrobbler(); + void nowPlaying( const QString &caller, const InfoType type, const QVariant& data, InfoCustomDataHash &customData ); + void dataError( const QString &caller, const InfoType type, const QVariant& data, InfoCustomDataHash &customData ); + + lastfm::MutableTrack m_track; + lastfm::Audioscrobbler* m_scrobbler; + QString m_pw; + + QNetworkReply* m_authJob; +}; + +} + +} + +#endif // LASTFMPLUGIN_H diff --git a/src/infosystem/infosystem.cpp b/src/infosystem/infosystem.cpp index 0c0ca1513..893c78162 100644 --- a/src/infosystem/infosystem.cpp +++ b/src/infosystem/infosystem.cpp @@ -19,6 +19,7 @@ #include "tomahawk/infosystem.h" #include "infoplugins/echonestplugin.h" #include "infoplugins/musixmatchplugin.h" +#include "infoplugins/lastfmplugin.h" using namespace Tomahawk::InfoSystem; @@ -33,6 +34,8 @@ InfoSystem::InfoSystem(QObject *parent) m_plugins.append(enptr); InfoPluginPtr mmptr(new MusixMatchPlugin(this)); m_plugins.append(mmptr); + InfoPluginPtr lfmptr(new LastFmPlugin(this)); + m_plugins.append(lfmptr); } void InfoSystem::registerInfoTypes(const InfoPluginPtr &plugin, const QSet< InfoType >& types) diff --git a/src/scrobbler.cpp b/src/scrobbler.cpp index f1439fe39..06ebe33f5 100644 --- a/src/scrobbler.cpp +++ b/src/scrobbler.cpp @@ -27,67 +27,27 @@ #include "audio/audioengine.h" #include "tomahawksettings.h" #include "tomahawk/tomahawkapp.h" +#include "tomahawk/infosystem.h" -#include -#include - - -static QString -md5( const QByteArray& src ) -{ - QByteArray const digest = QCryptographicHash::hash( src, QCryptographicHash::Md5 ); - return QString::fromLatin1( digest.toHex() ).rightJustified( 32, '0' ); -} - +static QString s_infoIdentifier = QString("SCROBBLER"); Scrobbler::Scrobbler( QObject* parent ) : QObject( parent ) - , m_scrobbler( 0 ) , m_reachedScrobblePoint( false ) - , m_authJob( 0 ) { -/* - Your API Key is 7194b85b6d1f424fe1668173a78c0c4a - Your secret is ba80f1df6d27ae63e9cb1d33ccf2052f -*/ - - // Flush session key cache - TomahawkSettings::instance()->setLastFmSessionKey( QByteArray() ); - - lastfm::ws::ApiKey = "7194b85b6d1f424fe1668173a78c0c4a"; - lastfm::ws::SharedSecret = "ba80f1df6d27ae63e9cb1d33ccf2052f"; - lastfm::ws::Username = TomahawkSettings::instance()->lastFmUsername(); - - m_pw = TomahawkSettings::instance()->lastFmPassword(); - - if( TomahawkSettings::instance()->scrobblingEnabled() && !lastfm::ws::Username.isEmpty() ) - { - createScrobbler(); - } - - //HACK work around a bug in liblastfm---it doesn't create its config dir, so when it - // tries to write the track cache, it fails silently. until we have a fixed version, do this - // code taken from Amarok (src/services/lastfm/ScrobblerAdapter.cpp) -#ifdef Q_WS_X11 - QString lpath = QDir::home().filePath( ".local/share/Last.fm" ); - QDir ldir = QDir( lpath ); - if( !ldir.exists() ) - { - ldir.mkpath( lpath ); - } -#endif - - connect( TomahawkSettings::instance(), SIGNAL( changed() ), - SLOT( settingsChanged() ), Qt::QueuedConnection ); - connect( AudioEngine::instance(), SIGNAL( timerSeconds( unsigned int ) ), SLOT( engineTick( unsigned int ) ), Qt::QueuedConnection ); + + connect( TomahawkApp::instance()->infoSystem(), + SIGNAL( info( QString, Tomahawk::InfoSystem::InfoType, QVariant, QVariant, Tomahawk::InfoSystem::InfoCustomDataHash ) ), + SLOT( infoSystemInfo( QString, Tomahawk::InfoSystem::InfoType, QVariant, QVariant, Tomahawk::InfoSystem::InfoCustomDataHash ) ) ); + + connect( TomahawkApp::instance()->infoSystem(), SIGNAL( finished( QString ) ), SLOT( infoSystemFinished( QString ) ) ); } Scrobbler::~Scrobbler() { - delete m_scrobbler; } @@ -97,26 +57,23 @@ Scrobbler::trackStarted( const Tomahawk::result_ptr& track ) Q_ASSERT( QThread::currentThread() == thread() ); // qDebug() << Q_FUNC_INFO; - if( !m_scrobbler ) - return; - if( m_reachedScrobblePoint ) { m_reachedScrobblePoint = false; scrobble(); } - m_track = lastfm::MutableTrack(); - m_track.stamp(); - - m_track.setTitle( track->track() ); - m_track.setArtist( track->artist()->name() ); - m_track.setAlbum( track->album()->name() ); - m_track.setDuration( track->duration() ); - m_track.setSource( lastfm::Track::Player ); - - m_scrobbler->nowPlaying( m_track ); - m_scrobblePoint = ScrobblePoint( m_track.duration() / 2 ); + Tomahawk::InfoSystem::InfoCustomDataHash trackInfo; + + trackInfo["title"] = QVariant::fromValue< QString >( track->track() ); + trackInfo["artist"] = QVariant::fromValue< QString >( track->artist()->name() ); + trackInfo["album"] = QVariant::fromValue< QString >( track->album()->name() ); + trackInfo["duration"] = QVariant::fromValue< uint >( track->duration() ); + TomahawkApp::instance()->infoSystem()->getInfo( + s_infoIdentifier, Tomahawk::InfoSystem::InfoMiscSubmitNowPlaying, + QVariant::fromValue< Tomahawk::InfoSystem::InfoCustomDataHash >( trackInfo ), Tomahawk::InfoSystem::InfoCustomDataHash() ); + + m_scrobblePoint = ScrobblePoint( track->duration() / 2 ); } @@ -159,102 +116,31 @@ void Scrobbler::scrobble() { Q_ASSERT( QThread::currentThread() == thread() ); - - if ( !m_scrobbler || m_track.isNull() ) - return; - - qDebug() << Q_FUNC_INFO << m_track.toString(); - m_scrobbler->cache( m_track ); - m_scrobbler->submit(); -} - - -void -Scrobbler::settingsChanged() -{ - if( !m_scrobbler && TomahawkSettings::instance()->scrobblingEnabled() ) - { // can simply create the scrobbler - lastfm::ws::Username = TomahawkSettings::instance()->lastFmUsername(); - m_pw = TomahawkSettings::instance()->lastFmPassword(); - - createScrobbler(); - } - else if( m_scrobbler && !TomahawkSettings::instance()->scrobblingEnabled() ) - { - delete m_scrobbler; - m_scrobbler = 0; - } - else if( TomahawkSettings::instance()->lastFmUsername() != lastfm::ws::Username || - TomahawkSettings::instance()->lastFmPassword() != m_pw ) - { - lastfm::ws::Username = TomahawkSettings::instance()->lastFmUsername(); - // credentials have changed, have to re-create scrobbler for them to take effect - if( m_scrobbler ) - delete m_scrobbler; - - createScrobbler(); - } -} - - -void -Scrobbler::onAuthenticated() -{ - if( !m_authJob ) - { - qDebug() << Q_FUNC_INFO << "Help! No longer got a last.fm auth job!"; - return; - } - if( m_authJob->error() == QNetworkReply::NoError ) - { - lastfm::XmlQuery lfm = lastfm::XmlQuery( m_authJob->readAll() ); - - if( lfm.children( "error" ).size() > 0 ) - { - qDebug() << "Error from authenticating with Last.fm service:" << lfm.text(); - TomahawkSettings::instance()->setLastFmSessionKey( QByteArray() ); - - } - else - { - lastfm::ws::SessionKey = lfm[ "session" ][ "key" ].text(); - - TomahawkSettings::instance()->setLastFmSessionKey( lastfm::ws::SessionKey.toLatin1() ); - - if( TomahawkSettings::instance()->scrobblingEnabled() ) - m_scrobbler = new lastfm::Audioscrobbler( "thk" ); - } - } - else - { - qDebug() << "Got error in Last.fm authentication job:" << m_authJob->errorString(); - } - - m_authJob->deleteLater(); + TomahawkApp::instance()->infoSystem()->getInfo( + s_infoIdentifier, Tomahawk::InfoSystem::InfoMiscSubmitScrobble, + QVariant(), Tomahawk::InfoSystem::InfoCustomDataHash() ); } - void -Scrobbler::createScrobbler() +Scrobbler::infoSystemInfo( QString caller, Tomahawk::InfoSystem::InfoType type, QVariant input, QVariant output, Tomahawk::InfoSystem::InfoCustomDataHash customData ) { - if( TomahawkSettings::instance()->lastFmSessionKey().isEmpty() ) // no session key, so get one + if ( caller == s_infoIdentifier ) { - QString authToken = md5( ( lastfm::ws::Username.toLower() + md5( m_pw.toUtf8() ) ).toUtf8() ); - - QMap query; - query[ "method" ] = "auth.getMobileSession"; - query[ "username" ] = lastfm::ws::Username; - query[ "authToken" ] = authToken; - m_authJob = lastfm::ws::post( query ); - - connect( m_authJob, SIGNAL( finished() ), SLOT( onAuthenticated() ) ); - } - else - { - lastfm::ws::SessionKey = TomahawkSettings::instance()->lastFmSessionKey(); - - m_scrobbler = new lastfm::Audioscrobbler( "thk" ); - m_scrobbler->moveToThread( thread() ); + qDebug() << Q_FUNC_INFO; + if ( type == Tomahawk::InfoSystem::InfoMiscSubmitNowPlaying ) + qDebug() << "Scrobbler received now playing response from InfoSystem"; + else if ( type == Tomahawk::InfoSystem::InfoMiscSubmitScrobble ) + qDebug() << "Scrobbler received scrobble response from InfoSystem"; } } + +void +Scrobbler::infoSystemFinished( QString target ) +{ + if ( target == s_infoIdentifier ) + { + qDebug() << Q_FUNC_INFO; + qDebug() << "Scrobbler received done signal from InfoSystem"; + } +} \ No newline at end of file diff --git a/src/scrobbler.h b/src/scrobbler.h index 69750427e..4ed020fba 100644 --- a/src/scrobbler.h +++ b/src/scrobbler.h @@ -21,13 +21,12 @@ #include "result.h" -#include -#include -#include +#include "lastfm/ScrobblePoint" + +#include "tomahawk/infosystem.h" #include -class QNetworkReply; /** * Simple class that listens to signals from AudioEngine and scrobbles * what it is playing. @@ -46,20 +45,14 @@ public slots: void trackStopped(); void engineTick( unsigned int secondsElapsed ); - void settingsChanged(); - void onAuthenticated(); + void infoSystemInfo( QString caller, Tomahawk::InfoSystem::InfoType type, QVariant input, QVariant output, Tomahawk::InfoSystem::InfoCustomDataHash customData ); + void infoSystemFinished( QString target ); private: void scrobble(); - void createScrobbler(); - lastfm::MutableTrack m_track; - lastfm::Audioscrobbler* m_scrobbler; - QString m_pw; bool m_reachedScrobblePoint; ScrobblePoint m_scrobblePoint; - - QNetworkReply* m_authJob; }; diff --git a/src/tomahawkapp.cpp b/src/tomahawkapp.cpp index eb178ede0..d9bf16e57 100644 --- a/src/tomahawkapp.cpp +++ b/src/tomahawkapp.cpp @@ -213,6 +213,9 @@ TomahawkApp::TomahawkApp( int& argc, char *argv[] ) connect( m_shortcutHandler, SIGNAL( mute() ), m_audioEngine, SLOT( mute() ) ); } + qDebug() << "Init InfoSystem."; + m_infoSystem = new Tomahawk::InfoSystem::InfoSystem( this ); + #ifdef LIBLASTFM_FOUND qDebug() << "Init Scrobbler."; m_scrobbler = new Scrobbler( this ); @@ -236,6 +239,7 @@ TomahawkApp::TomahawkApp( int& argc, char *argv[] ) #endif // Set up proxy + //FIXME: This overrides the lastfm proxy above? if( TomahawkSettings::instance()->proxyType() != QNetworkProxy::NoProxy && !TomahawkSettings::instance()->proxyHost().isEmpty() ) { @@ -252,8 +256,6 @@ TomahawkApp::TomahawkApp( int& argc, char *argv[] ) qDebug() << "Init SIP system."; m_sipHandler = new SipHandler( this ); - qDebug() << "Init InfoSystem."; - m_infoSystem = new Tomahawk::InfoSystem::InfoSystem( this ); #ifndef TOMAHAWK_HEADLESS if ( !m_headless ) From 575ee1548aa63deec0b5727868e843e46a1d71d7 Mon Sep 17 00:00:00 2001 From: Jeff Mitchell Date: Sat, 26 Mar 2011 11:47:54 -0400 Subject: [PATCH 153/329] Fetch cover art using LastFm via the InfoSystem plugin --- src/audiocontrols.cpp | 62 ++++++++++++++- src/audiocontrols.h | 3 + src/infosystem/infoplugins/lastfmplugin.cpp | 85 ++++++++++++++++++--- src/infosystem/infoplugins/lastfmplugin.h | 2 + 4 files changed, 139 insertions(+), 13 deletions(-) diff --git a/src/audiocontrols.cpp b/src/audiocontrols.cpp index d160df1d6..cd6f7682e 100644 --- a/src/audiocontrols.cpp +++ b/src/audiocontrols.cpp @@ -31,6 +31,7 @@ #define LASTFM_DEFAULT_COVER "http://cdn.last.fm/flatness/catalogue/noimage" +static QString s_infoIdentifier = QString("AUDIOCONTROLS"); AudioControls::AudioControls( QWidget* parent ) : QWidget( parent ) @@ -166,6 +167,12 @@ AudioControls::AudioControls( QWidget* parent ) m_defaultCover = QPixmap( RESPATH "images/no-album-art-placeholder.png" ) .scaled( ui->coverImage->size(), Qt::IgnoreAspectRatio, Qt::SmoothTransformation ); + connect( TomahawkApp::instance()->infoSystem(), + SIGNAL( info( QString, Tomahawk::InfoSystem::InfoType, QVariant, QVariant, Tomahawk::InfoSystem::InfoCustomDataHash ) ), + SLOT( infoSystemInfo( QString, Tomahawk::InfoSystem::InfoType, QVariant, QVariant, Tomahawk::InfoSystem::InfoCustomDataHash ) ) ); + + connect( TomahawkApp::instance()->infoSystem(), SIGNAL( finished( QString ) ), SLOT( infoSystemFinished( QString ) ) ); + onPlaybackStopped(); // initial state } @@ -242,12 +249,59 @@ AudioControls::onPlaybackStarted( const Tomahawk::result_ptr& result ) onPlaybackLoading( result ); - QString imgurl = "http://ws.audioscrobbler.com/2.0/?method=album.imageredirect&artist=%1&album=%2&size=medium&api_key=7a90f6672a04b809ee309af169f34b8b"; - QNetworkRequest req( imgurl.arg( result->artist()->name() ).arg( result->album()->name() ) ); - QNetworkReply* reply = TomahawkUtils::nam()->get( req ); - connect( reply, SIGNAL( finished() ), SLOT( onCoverArtDownloaded() ) ); + QString artistName = result->artist()->name(); + QString albumName = result->album()->name(); + + Tomahawk::InfoSystem::InfoCustomDataHash trackInfo; + + trackInfo["artist"] = QVariant::fromValue< QString >( result->artist()->name() ); + trackInfo["album"] = QVariant::fromValue< QString >( result->album()->name() ); + TomahawkApp::instance()->infoSystem()->getInfo( + s_infoIdentifier, Tomahawk::InfoSystem::InfoAlbumCoverArt, + QVariant::fromValue< Tomahawk::InfoSystem::InfoCustomDataHash >( trackInfo ), Tomahawk::InfoSystem::InfoCustomDataHash() ); } +void +AudioControls::infoSystemInfo( QString caller, Tomahawk::InfoSystem::InfoType type, QVariant input, QVariant output, Tomahawk::InfoSystem::InfoCustomDataHash customData ) +{ + qDebug() << Q_FUNC_INFO; + if ( caller != s_infoIdentifier || type != Tomahawk::InfoSystem::InfoAlbumCoverArt ) + { + qDebug() << "info of wrong type or not with our identifier"; + return; + } + + if ( m_currentTrack.isNull() ) + { + qDebug() << "Current track is null when trying to apply fetched cover art"; + return; + } + + if ( !output.canConvert< Tomahawk::InfoSystem::InfoCustomDataHash >() ) + { + qDebug() << "Cannot convert fetched art from a QByteArray"; + return; + } + + Tomahawk::InfoSystem::InfoCustomDataHash returnedData = output.value< Tomahawk::InfoSystem::InfoCustomDataHash >(); + const QByteArray ba = returnedData["imgbytes"].toByteArray(); + if ( ba.length() ) + { + QPixmap pm; + pm.loadFromData( ba ); + + if ( pm.isNull() || returnedData["url"].toString().startsWith( LASTFM_DEFAULT_COVER ) ) + ui->coverImage->setPixmap( m_defaultCover ); + else + ui->coverImage->setPixmap( pm.scaled( ui->coverImage->size(), Qt::IgnoreAspectRatio, Qt::SmoothTransformation ) ); + } +} + +void +AudioControls::infoSystemFinished( QString target ) +{ + qDebug() << Q_FUNC_INFO; +} void AudioControls::onPlaybackLoading( const Tomahawk::result_ptr& result ) diff --git a/src/audiocontrols.h b/src/audiocontrols.h index 8de6f765c..0491745c3 100644 --- a/src/audiocontrols.h +++ b/src/audiocontrols.h @@ -23,6 +23,7 @@ #include "result.h" #include "playlistinterface.h" +#include "tomahawk/infosystem.h" namespace Ui { @@ -44,6 +45,8 @@ signals: public slots: void onRepeatModeChanged( PlaylistInterface::RepeatMode mode ); void onShuffleModeChanged( bool enabled ); + void infoSystemInfo( QString caller, Tomahawk::InfoSystem::InfoType type, QVariant input, QVariant output, Tomahawk::InfoSystem::InfoCustomDataHash customData ); + void infoSystemFinished( QString target ); protected: void changeEvent( QEvent* e ); diff --git a/src/infosystem/infoplugins/lastfmplugin.cpp b/src/infosystem/infoplugins/lastfmplugin.cpp index 22e9c70cb..81272b86f 100644 --- a/src/infosystem/infoplugins/lastfmplugin.cpp +++ b/src/infosystem/infoplugins/lastfmplugin.cpp @@ -47,7 +47,7 @@ LastFmPlugin::LastFmPlugin( QObject* parent ) , m_authJob( 0 ) { QSet< InfoType > supportedTypes; - supportedTypes << Tomahawk::InfoSystem::InfoMiscSubmitScrobble << Tomahawk::InfoSystem::InfoMiscSubmitNowPlaying; + supportedTypes << InfoMiscSubmitScrobble << InfoMiscSubmitNowPlaying << InfoAlbumCoverArt; qobject_cast< InfoSystem* >(parent)->registerInfoTypes(this, supportedTypes); /* @@ -91,6 +91,14 @@ LastFmPlugin::~LastFmPlugin() delete m_scrobbler; } +void +LastFmPlugin::dataError( const QString &caller, const InfoType type, const QVariant& data, Tomahawk::InfoSystem::InfoCustomDataHash &customData ) +{ + emit info( caller, type, data, QVariant(), customData ); + emit finished( caller, type ); + return; +} + void LastFmPlugin::getInfo( const QString &caller, const InfoType type, const QVariant& data, Tomahawk::InfoSystem::InfoCustomDataHash customData ) { @@ -99,6 +107,8 @@ LastFmPlugin::getInfo( const QString &caller, const InfoType type, const QVarian nowPlaying( caller, type, data, customData ); else if ( type == InfoMiscSubmitScrobble ) scrobble( caller, type, data, customData ); + else if ( type == InfoAlbumCoverArt ) + fetchCoverArt( caller, type, data, customData ); else dataError( caller, type, data, customData ); } @@ -132,14 +142,6 @@ LastFmPlugin::nowPlaying( const QString &caller, const InfoType type, const QVar emit finished( caller, type ); } -void -LastFmPlugin::dataError( const QString &caller, const InfoType type, const QVariant& data, Tomahawk::InfoSystem::InfoCustomDataHash &customData ) -{ - emit info( caller, type, data, QVariant(), customData ); - emit finished( caller, type ); - return; -} - void LastFmPlugin::scrobble( const QString &caller, const InfoType type, const QVariant& data, Tomahawk::InfoSystem::InfoCustomDataHash &customData ) { @@ -159,6 +161,71 @@ LastFmPlugin::scrobble( const QString &caller, const InfoType type, const QVaria emit finished( caller, type ); } +void +LastFmPlugin::fetchCoverArt( const QString &caller, const InfoType type, const QVariant& data, Tomahawk::InfoSystem::InfoCustomDataHash &customData ) +{ + qDebug() << Q_FUNC_INFO; + if ( !data.canConvert< Tomahawk::InfoSystem::InfoCustomDataHash >() ) + { + dataError( caller, type, data, customData ); + return; + } + InfoCustomDataHash hash = data.value< Tomahawk::InfoSystem::InfoCustomDataHash >(); + if ( !hash.contains( "artist" ) || !hash.contains( "album" ) ) + { + dataError( caller, type, data, customData ); + return; + } + + QString artistName = hash["artist"].toString(); + QString albumName = hash["album"].toString(); + + QString imgurl = "http://ws.audioscrobbler.com/2.0/?method=album.imageredirect&artist=%1&album=%2&size=medium&api_key=7a90f6672a04b809ee309af169f34b8b"; + QNetworkRequest req( imgurl.arg( artistName ).arg( albumName ) ); + QNetworkReply* reply = TomahawkUtils::nam()->get( req ); + reply->setProperty("customData", QVariant::fromValue(customData)); + reply->setProperty("origData", data); + reply->setProperty("caller", caller); + reply->setProperty("type", (uint)(type) ); + + connect( reply, SIGNAL( finished() ), SLOT( coverArtReturned() ) ); +} + +void +LastFmPlugin::coverArtReturned() +{ + qDebug() << Q_FUNC_INFO; + QNetworkReply* reply = qobject_cast( sender() ); + QUrl redir = reply->attribute( QNetworkRequest::RedirectionTargetAttribute ).toUrl(); + if ( redir.isEmpty() ) + { + const QByteArray ba = reply->readAll(); + Tomahawk::InfoSystem::InfoCustomDataHash returnedData; + returnedData["imgbytes"] = ba; + returnedData["url"] = reply->url().toString(); + emit info( + reply->property( "caller" ).toString(), + (Tomahawk::InfoSystem::InfoType)(reply->property( "type" ).toUInt()), + reply->property( "origData" ), + returnedData, + reply->property( "customData" ).value< Tomahawk::InfoSystem::InfoCustomDataHash >() + ); + emit finished( reply->property( "caller" ).toString(), (Tomahawk::InfoSystem::InfoType)(reply->property( "type" ).toUInt()) ); + } + else + { + // Follow HTTP redirect + QNetworkRequest req( redir ); + QNetworkReply* newReply = TomahawkUtils::nam()->get( req ); + newReply->setProperty( "origData", reply->property( "origData" ) ); + newReply->setProperty( "customData", reply->property( "customData" ) ); + newReply->setProperty( "caller", reply->property( "caller" ) ); + newReply->setProperty( "type", reply->property( "type" ) ); + connect( newReply, SIGNAL( finished() ), SLOT( coverArtReturned() ) ); + } + + reply->deleteLater(); +} void LastFmPlugin::settingsChanged() diff --git a/src/infosystem/infoplugins/lastfmplugin.h b/src/infosystem/infoplugins/lastfmplugin.h index 775bfd3c8..3f396edfb 100644 --- a/src/infosystem/infoplugins/lastfmplugin.h +++ b/src/infosystem/infoplugins/lastfmplugin.h @@ -48,8 +48,10 @@ public: public slots: void settingsChanged(); void onAuthenticated(); + void coverArtReturned(); private: + void fetchCoverArt( const QString &caller, const InfoType type, const QVariant& data, Tomahawk::InfoSystem::InfoCustomDataHash &customData ); void scrobble( const QString &caller, const InfoType type, const QVariant& data, InfoCustomDataHash &customData ); void createScrobbler(); void nowPlaying( const QString &caller, const InfoType type, const QVariant& data, InfoCustomDataHash &customData ); From 3182df93ddccdf1656f821b7e765f81b6e86825f Mon Sep 17 00:00:00 2001 From: Jeff Mitchell Date: Sat, 26 Mar 2011 12:25:35 -0400 Subject: [PATCH 154/329] Don't install liblastfm2 headers --- thirdparty/liblastfm2/src/CMakeLists.txt | 4 ---- 1 file changed, 4 deletions(-) diff --git a/thirdparty/liblastfm2/src/CMakeLists.txt b/thirdparty/liblastfm2/src/CMakeLists.txt index c7d569442..a9e3344cb 100644 --- a/thirdparty/liblastfm2/src/CMakeLists.txt +++ b/thirdparty/liblastfm2/src/CMakeLists.txt @@ -7,10 +7,6 @@ macro(copy_header from to) ${CMAKE_CURRENT_BINARY_DIR}/lastfm/${to} COPY_ONLY ) - install( - FILES ${CMAKE_CURRENT_BINARY_DIR}/lastfm/${to} - DESTINATION include/lastfm/ - ) endmacro(copy_header) # Copy headers From 4677d761acd5e0d9c789a8e7f5917ad41f01c98e Mon Sep 17 00:00:00 2001 From: Dominik Schmidt Date: Sat, 26 Mar 2011 18:26:31 +0100 Subject: [PATCH 155/329] Make the sip plugin aware of subscription requests again --- src/sip/jabber/jabber_p.cpp | 24 ++++++++++++++++++++---- src/sip/jabber/jabber_p.h | 6 ++++++ 2 files changed, 26 insertions(+), 4 deletions(-) diff --git a/src/sip/jabber/jabber_p.cpp b/src/sip/jabber/jabber_p.cpp index aafd29543..aa2a5598d 100644 --- a/src/sip/jabber/jabber_p.cpp +++ b/src/sip/jabber/jabber_p.cpp @@ -26,6 +26,7 @@ #include #include #include +#include using namespace gloox; using namespace std; @@ -148,6 +149,7 @@ Jabber_p::go() m_client->registerPresenceHandler( this ); m_client->registerConnectionListener( this ); + m_client->rosterManager()->registerRosterListener( this ); m_client->logInstance().registerLogHandler( LogLevelWarning, LogAreaAll, this ); m_client->registerMessageHandler( this ); @@ -598,10 +600,24 @@ bool Jabber_p::handleSubscriptionRequest( const JID& jid, const std::string& /*msg*/ ) { qDebug() << Q_FUNC_INFO << jid.bare().c_str(); - StringList groups; - groups.push_back( "Tomahawk" ); - m_client->rosterManager()->subscribe( jid, "", groups, "" ); - return true; + + QMessageBox::StandardButton allowSubscription; + allowSubscription = QMessageBox::question( 0, + tr("Friend Request in Jabber"), + QString(tr("Do you want to be friends with %1?")).arg(QLatin1String(jid.bare().c_str())), + QMessageBox::Ok | QMessageBox::Cancel + ); + + if(allowSubscription == QMessageBox::Ok) + { + StringList groups; + groups.push_back( "Tomahawk" ); + m_client->rosterManager()->subscribe( jid, "", groups, "" ); + + return true; + } + + return false; } diff --git a/src/sip/jabber/jabber_p.h b/src/sip/jabber/jabber_p.h index 3e14c7c69..f051915bc 100644 --- a/src/sip/jabber/jabber_p.h +++ b/src/sip/jabber/jabber_p.h @@ -76,6 +76,7 @@ class SIPDLLEXPORT Jabber_p : public gloox::MessageHandler, public gloox::VCardHandler, public gloox::PresenceHandler, + public gloox::RosterListener, gloox::LogHandler //public gloox::DiscoHandler, { @@ -109,6 +110,11 @@ public: virtual void handleRoster( const gloox::Roster& roster ); virtual void handleRosterError( const gloox::IQ& /*iq*/ ); + + // not actually used, just needed for the interface to support subscription requests, see handlePresence for presence handling + virtual void handleRosterPresence( const gloox::RosterItem&, const std::string&, gloox::Presence::PresenceType, const std::string& ) {} + virtual void handleSelfPresence( const gloox::RosterItem&, const std::string&, gloox::Presence::PresenceType, const std::string& ) {} + virtual void handlePresence( const gloox::Presence& presence ); virtual bool handleSubscription( const gloox::JID& jid, const std::string& /*msg*/ ); virtual bool handleSubscriptionRequest( const gloox::JID& jid, const std::string& /*msg*/ ); From 9496885d63d2493b36cbe141834571cc8387fc6f Mon Sep 17 00:00:00 2001 From: Dominik Schmidt Date: Sat, 26 Mar 2011 19:58:31 +0100 Subject: [PATCH 156/329] Don't ask the user again to ack the subscription request in sip_jabber if the requesting contact is already on the roster --- src/sip/jabber/jabber_p.cpp | 11 +++++++++++ 1 file changed, 11 insertions(+) diff --git a/src/sip/jabber/jabber_p.cpp b/src/sip/jabber/jabber_p.cpp index aa2a5598d..9f2d6276b 100644 --- a/src/sip/jabber/jabber_p.cpp +++ b/src/sip/jabber/jabber_p.cpp @@ -601,6 +601,17 @@ Jabber_p::handleSubscriptionRequest( const JID& jid, const std::string& /*msg*/ { qDebug() << Q_FUNC_INFO << jid.bare().c_str(); + // check if the requester is already on the roster + RosterItem *item = m_client->rosterManager()->getRosterItem(jid); + if(item) + { + qDebug() << Q_FUNC_INFO << "Already on the roster so we assume ack'ing subscription request is okay..."; + return true; + } + + + + // ask whether to accept subscription request or not QMessageBox::StandardButton allowSubscription; allowSubscription = QMessageBox::question( 0, tr("Friend Request in Jabber"), From 8faa782650d827e4f7703b4f1709de49459e5042 Mon Sep 17 00:00:00 2001 From: Jeff Mitchell Date: Sat, 26 Mar 2011 15:50:28 -0400 Subject: [PATCH 157/329] You can now send global, mentions, or private (direct message) Got Tomahawk? tweets --- src/sip/twitter/twitter.cpp | 178 +++++++++++++----------- src/sip/twitter/twitter.h | 1 + src/sip/twitter/twitterconfigwidget.cpp | 111 +++++++++++++-- src/sip/twitter/twitterconfigwidget.h | 8 +- src/sip/twitter/twitterconfigwidget.ui | 164 +++++++++++++++++++++- 5 files changed, 362 insertions(+), 100 deletions(-) diff --git a/src/sip/twitter/twitter.cpp b/src/sip/twitter/twitter.cpp index c4e3e9bb5..0bd5c69e7 100644 --- a/src/sip/twitter/twitter.cpp +++ b/src/sip/twitter/twitter.cpp @@ -370,12 +370,57 @@ TwitterPlugin::friendsTimelineStatuses( const QList< QTweetStatus > &statuses ) QMetaObject::invokeMethod( this, "pollDirectMessages", Qt::AutoConnection ); } +void +TwitterPlugin::parseGotTomahawk( const QRegExp ®ex, const QString &screenName, const QString &text ) +{ + QString myScreenName = TomahawkSettings::instance()->twitterScreenName(); + qDebug() << "TwitterPlugin found an exact matching Got Tomahawk? mention or direct message from user " << screenName; + if ( text.startsWith( '@' ) && regex.captureCount() >= 2 && regex.cap( 1 ) != QString( '@' + myScreenName ) ) + { + qDebug() << "TwitterPlugin skipping mention because it's directed @someone that isn't us"; + return; + } + + QString node; + for ( int i = 0; i < regex.captureCount(); ++i ) + { + if ( regex.cap( i ) == QString( "Got Tomahawk?" ) ) + { + QString nodeCap = regex.cap( i + 1 ); + nodeCap.chop( 1 ); + node = nodeCap.mid( 1 ); + } + } + if ( node.isEmpty() ) + { + qDebug() << "TwitterPlugin could not parse node out of the tweet"; + return; + } + else + qDebug() << "TwitterPlugin parsed node " << node << " out of the tweet"; + + if ( screenName == myScreenName && node == Database::instance()->dbid() ) + { + qDebug() << "My screen name and my dbid found; ignoring"; + return; + } + + QHash< QString, QVariant > peerData; + if( m_cachedPeers.contains( screenName ) ) + { + peerData = m_cachedPeers[screenName].toHash(); + //force a re-send of info but no need to re-register + peerData["resend"] = QVariant::fromValue< bool >( true ); + } + peerData["node"] = QVariant::fromValue< QString >( node ); + QMetaObject::invokeMethod( this, "registerOffer", Q_ARG( QString, screenName ), QGenericArgument( "QHash< QString, QVariant >", (const void*)&peerData ) ); +} + void TwitterPlugin::mentionsStatuses( const QList< QTweetStatus > &statuses ) { qDebug() << Q_FUNC_INFO; QRegExp regex( QString( "^(@[a-zA-Z0-9]+ )?(Got Tomahawk\\?) (\\{[a-fA-F0-9\\-]+\\}) (.*)$" ), Qt::CaseSensitive, QRegExp::RegExp2 ); - QString myScreenName = TomahawkSettings::instance()->twitterScreenName(); QHash< QString, QTweetStatus > latestHash; foreach ( QTweetStatus status, statuses ) @@ -393,50 +438,9 @@ TwitterPlugin::mentionsStatuses( const QList< QTweetStatus > &statuses ) { if ( status.id() > m_cachedMentionsSinceId ) m_cachedMentionsSinceId = status.id(); - + if ( regex.exactMatch( status.text() ) ) - { - qDebug() << "TwitterPlugin found an exact matching mention from user " << status.user().screenName(); - if ( status.text().startsWith( '@' ) && regex.captureCount() >= 2 && regex.cap( 1 ) != QString( '@' + myScreenName ) ) - { - qDebug() << "TwitterPlugin skipping mention because it's directed @someone that isn't us"; - continue; - } - - QString node; - for ( int i = 0; i < regex.captureCount(); ++i ) - { - if ( regex.cap( i ) == QString( "Got Tomahawk?" ) ) - { - QString nodeCap = regex.cap( i + 1 ); - nodeCap.chop( 1 ); - node = nodeCap.mid( 1 ); - } - } - if ( node.isEmpty() ) - { - qDebug() << "TwitterPlugin could not parse node out of the tweet"; - continue; - } - else - qDebug() << "TwitterPlugin parsed node " << node << " out of the tweet"; - - if ( status.user().screenName() == myScreenName && node == Database::instance()->dbid() ) - { - qDebug() << "My screen name and my dbid found; ignoring"; - continue; - } - - QHash< QString, QVariant > peerData; - if( m_cachedPeers.contains( status.user().screenName() ) ) - { - peerData = m_cachedPeers[status.user().screenName()].toHash(); - //force a re-send of info but no need to re-register - peerData["resend"] = QVariant::fromValue< bool >( true ); - } - peerData["node"] = QVariant::fromValue< QString >( node ); - QMetaObject::invokeMethod( this, "registerOffer", Q_ARG( QString, status.user().screenName() ), QGenericArgument( "QHash< QString, QVariant >", (const void*)&peerData ) ); - } + parseGotTomahawk( regex, status.user().screenName(), status.text() ); } TomahawkSettings::instance()->setTwitterCachedMentionsSinceId( m_cachedMentionsSinceId ); @@ -471,6 +475,9 @@ TwitterPlugin::directMessages( const QList< QTweetDMStatus > &messages ) { qDebug() << Q_FUNC_INFO; + QRegExp regex( QString( "^(@[a-zA-Z0-9]+ )?(Got Tomahawk\\?) (\\{[a-fA-F0-9\\-]+\\}) (.*)$" ), Qt::CaseSensitive, QRegExp::RegExp2 ); + QString myScreenName = TomahawkSettings::instance()->twitterScreenName(); + QHash< QString, QTweetDMStatus > latestHash; foreach ( QTweetDMStatus status, messages ) { @@ -488,50 +495,55 @@ TwitterPlugin::directMessages( const QList< QTweetDMStatus > &messages ) qDebug() << "TwitterPlugin checking direct message from " << status.senderScreenName() << " with content " << status.text(); if ( status.id() > m_cachedDirectMessagesSinceId ) m_cachedDirectMessagesSinceId = status.id(); - QStringList splitList = status.text().split(':'); - qDebug() << "TwitterPlugin found " << splitList.length() << " parts to the message; the parts are:"; - foreach( QString part, splitList ) - qDebug() << part; - if ( splitList.length() != 5 ) - continue; - if ( splitList[0] != "TOMAHAWKPEER" ) - continue; - if ( !splitList[1].startsWith( "Host=" ) || !splitList[2].startsWith( "Port=" ) || !splitList[3].startsWith( "Node=" ) || !splitList[4].startsWith( "PKey=" ) ) - continue; - int port = splitList[2].mid( 5 ).toInt(); - if ( port == 0 ) - continue; - QString host = splitList[1].mid( 5 ); - QString node = splitList[3].mid( 5 ); - QString pkey = splitList[4].mid( 5 ); - QStringList splitNode = node.split('*'); - if ( splitNode.length() != 2 ) + + if ( regex.exactMatch( status.text() ) ) + parseGotTomahawk( regex, status.sender().screenName(), status.text() ); + else { - qDebug() << "Old-style node info found, ignoring"; - continue; - } - qDebug() << "TwitterPlugin found a peerstart message from " << status.senderScreenName() << " with host " << host << " and port " << port << " and pkey " << pkey << " and node " << splitNode[0] << " destined for node " << splitNode[1]; + QStringList splitList = status.text().split(':'); + qDebug() << "TwitterPlugin found " << splitList.length() << " parts to the message; the parts are:"; + foreach( QString part, splitList ) + qDebug() << part; + if ( splitList.length() != 5 ) + continue; + if ( splitList[0] != "TOMAHAWKPEER" ) + continue; + if ( !splitList[1].startsWith( "Host=" ) || !splitList[2].startsWith( "Port=" ) || !splitList[3].startsWith( "Node=" ) || !splitList[4].startsWith( "PKey=" ) ) + continue; + int port = splitList[2].mid( 5 ).toInt(); + if ( port == 0 ) + continue; + QString host = splitList[1].mid( 5 ); + QString node = splitList[3].mid( 5 ); + QString pkey = splitList[4].mid( 5 ); + QStringList splitNode = node.split('*'); + if ( splitNode.length() != 2 ) + { + qDebug() << "Old-style node info found, ignoring"; + continue; + } + qDebug() << "TwitterPlugin found a peerstart message from " << status.senderScreenName() << " with host " << host << " and port " << port << " and pkey " << pkey << " and node " << splitNode[0] << " destined for node " << splitNode[1]; + - - QHash< QString, QVariant > peerData = ( m_cachedPeers.contains( status.senderScreenName() ) ) ? - m_cachedPeers[status.senderScreenName()].toHash() : - QHash< QString, QVariant >(); - - peerData["host"] = QVariant::fromValue< QString >( host ); - peerData["port"] = QVariant::fromValue< int >( port ); - peerData["pkey"] = QVariant::fromValue< QString >( pkey ); - peerData["node"] = QVariant::fromValue< QString >( splitNode[0] ); - peerData["dirty"] = QVariant::fromValue< bool >( true ); + QHash< QString, QVariant > peerData = ( m_cachedPeers.contains( status.senderScreenName() ) ) ? + m_cachedPeers[status.senderScreenName()].toHash() : + QHash< QString, QVariant >(); + + peerData["host"] = QVariant::fromValue< QString >( host ); + peerData["port"] = QVariant::fromValue< int >( port ); + peerData["pkey"] = QVariant::fromValue< QString >( pkey ); + peerData["node"] = QVariant::fromValue< QString >( splitNode[0] ); + peerData["dirty"] = QVariant::fromValue< bool >( true ); - QMetaObject::invokeMethod( this, "registerOffer", Q_ARG( QString, status.senderScreenName() ), QGenericArgument( "QHash< QString, QVariant >", (const void*)&peerData ) ); + QMetaObject::invokeMethod( this, "registerOffer", Q_ARG( QString, status.senderScreenName() ), QGenericArgument( "QHash< QString, QVariant >", (const void*)&peerData ) ); - if ( Database::instance()->dbid().startsWith( splitNode[1] ) ) - { - qDebug() << "TwitterPlugin found message destined for this node; destroying it"; - if ( !m_directMessageDestroy.isNull() ) - m_directMessageDestroy.data()->destroyMessage( status.id() ); + if ( Database::instance()->dbid().startsWith( splitNode[1] ) ) + { + qDebug() << "TwitterPlugin found message destined for this node; destroying it"; + if ( !m_directMessageDestroy.isNull() ) + m_directMessageDestroy.data()->destroyMessage( status.id() ); + } } - } TomahawkSettings::instance()->setTwitterCachedDirectMessagesSinceId( m_cachedDirectMessagesSinceId ); diff --git a/src/sip/twitter/twitter.h b/src/sip/twitter/twitter.h index e0c38b479..839ec88c3 100644 --- a/src/sip/twitter/twitter.h +++ b/src/sip/twitter/twitter.h @@ -92,6 +92,7 @@ private slots: private: bool refreshTwitterAuth(); + void parseGotTomahawk( const QRegExp ®ex, const QString &screenName, const QString &text ); QWeakPointer< TomahawkOAuthTwitter > m_twitterAuth; QWeakPointer< QTweetFriendsTimeline > m_friendsTimeline; diff --git a/src/sip/twitter/twitterconfigwidget.cpp b/src/sip/twitter/twitterconfigwidget.cpp index dbf484652..15e38c56d 100644 --- a/src/sip/twitter/twitterconfigwidget.cpp +++ b/src/sip/twitter/twitterconfigwidget.cpp @@ -26,6 +26,7 @@ #include "tomahawkoauthtwitter.h" #include #include +#include #include @@ -39,8 +40,11 @@ TwitterConfigWidget::TwitterConfigWidget(SipPlugin* plugin, QWidget *parent) : connect(ui->twitterAuthenticateButton, SIGNAL(pressed()), this, SLOT(authDeauthTwitter())); connect(ui->twitterTweetGotTomahawkButton, SIGNAL(pressed()), - this, SLOT(startPostGotTomahawkStatus())); - + this, SLOT(startPostGlobalGotTomahawkStatus())); + connect(ui->twitterUserTweetButton, SIGNAL(pressed()), + this, SLOT(startPostUserGotTomahawkStatus())); + connect(ui->twitterDirectTweetButton, SIGNAL(pressed()), + this, SLOT(startPostDirectGotTomahawkStatus())); TomahawkSettings* s = TomahawkSettings::instance(); if ( s->twitterOAuthToken().isEmpty() || s->twitterOAuthTokenSecret().isEmpty() || s->twitterScreenName().isEmpty() ) @@ -48,7 +52,14 @@ TwitterConfigWidget::TwitterConfigWidget(SipPlugin* plugin, QWidget *parent) : ui->twitterStatusLabel->setText("Status: No saved credentials"); ui->twitterAuthenticateButton->setText( "Authenticate" ); ui->twitterInstructionsInfoLabel->setVisible( false ); + ui->twitterGlobalTweetLabel->setVisible( false ); + ui->twitterUserTweetLabel->setVisible( false ); + ui->twitterDirectTweetLabel->setVisible( false ); ui->twitterTweetGotTomahawkButton->setVisible( false ); + ui->twitterUserTweetButton->setVisible( false ); + ui->twitterUserTweetLineEdit->setVisible( false ); + ui->twitterDirectTweetButton->setVisible( false ); + ui->twitterDirectTweetLineEdit->setVisible( false ); emit twitterAuthed( false ); } @@ -57,7 +68,14 @@ TwitterConfigWidget::TwitterConfigWidget(SipPlugin* plugin, QWidget *parent) : ui->twitterStatusLabel->setText("Status: Credentials saved"); ui->twitterAuthenticateButton->setText( "De-authenticate" ); ui->twitterInstructionsInfoLabel->setVisible( true ); + ui->twitterGlobalTweetLabel->setVisible( true ); + ui->twitterUserTweetLabel->setVisible( true ); + ui->twitterDirectTweetLabel->setVisible( true ); ui->twitterTweetGotTomahawkButton->setVisible( true ); + ui->twitterUserTweetButton->setVisible( true ); + ui->twitterUserTweetLineEdit->setVisible( true ); + ui->twitterDirectTweetButton->setVisible( true ); + ui->twitterDirectTweetLineEdit->setVisible( true ); emit twitterAuthed( true ); } @@ -115,7 +133,14 @@ TwitterConfigWidget::authenticateVerifyReply( const QTweetUser &user ) ui->twitterStatusLabel->setText("Status: Credentials saved"); ui->twitterAuthenticateButton->setText( "De-authenticate" ); ui->twitterInstructionsInfoLabel->setVisible( true ); + ui->twitterGlobalTweetLabel->setVisible( true ); + ui->twitterUserTweetLabel->setVisible( true ); + ui->twitterDirectTweetLabel->setVisible( true ); ui->twitterTweetGotTomahawkButton->setVisible( true ); + ui->twitterUserTweetButton->setVisible( true ); + ui->twitterUserTweetLineEdit->setVisible( true ); + ui->twitterDirectTweetButton->setVisible( true ); + ui->twitterDirectTweetLineEdit->setVisible( true ); m_plugin->connectPlugin( false ); @@ -132,7 +157,6 @@ TwitterConfigWidget::authenticateVerifyError( QTweetNetBase::ErrorCode code, con return; } - void TwitterConfigWidget::deauthenticateTwitter() { @@ -145,11 +169,49 @@ TwitterConfigWidget::deauthenticateTwitter() ui->twitterStatusLabel->setText("Status: No saved credentials"); ui->twitterAuthenticateButton->setText( "Authenticate" ); ui->twitterInstructionsInfoLabel->setVisible( false ); + ui->twitterGlobalTweetLabel->setVisible( false ); + ui->twitterUserTweetLabel->setVisible( false ); + ui->twitterDirectTweetLabel->setVisible( false ); ui->twitterTweetGotTomahawkButton->setVisible( false ); + ui->twitterUserTweetButton->setVisible( false ); + ui->twitterUserTweetLineEdit->setVisible( false ); + ui->twitterDirectTweetButton->setVisible( false ); + ui->twitterDirectTweetLineEdit->setVisible( false ); emit twitterAuthed( false ); } +void +TwitterConfigWidget::startPostGlobalGotTomahawkStatus() +{ + m_postGTtype = "global"; + startPostGotTomahawkStatus(); +} + +void +TwitterConfigWidget::startPostUserGotTomahawkStatus() +{ + if ( ui->twitterUserTweetLineEdit->text().isEmpty() || ui->twitterUserTweetLineEdit->text() == "@" ) + { + QMessageBox::critical( 0, QString("Tweetin' Error"), QString("You cannot leave the user name empty when sending a mention.") ); + return; + } + m_postGTtype = "user"; + startPostGotTomahawkStatus(); +} + +void +TwitterConfigWidget::startPostDirectGotTomahawkStatus() +{ + if ( ui->twitterDirectTweetLineEdit->text().isEmpty() || ui->twitterDirectTweetLineEdit->text() == "@" ) + { + QMessageBox::critical( 0, QString("Tweetin' Error"), QString("You cannot leave the user name empty when sending a direct message.") ); + return; + } + m_postGTtype = "direct"; + startPostGotTomahawkStatus(); +} + void TwitterConfigWidget::startPostGotTomahawkStatus() { @@ -170,7 +232,6 @@ TwitterConfigWidget::startPostGotTomahawkStatus() credVerifier->verify(); } - void TwitterConfigWidget::postGotTomahawkStatusAuthVerifyReply( const QTweetUser &user ) { @@ -186,14 +247,36 @@ TwitterConfigWidget::postGotTomahawkStatusAuthVerifyReply( const QTweetUser &use twitAuth->setNetworkAccessManager( TomahawkUtils::nam() ); twitAuth->setOAuthToken( s->twitterOAuthToken().toLatin1() ); twitAuth->setOAuthTokenSecret( s->twitterOAuthTokenSecret().toLatin1() ); - QTweetStatusUpdate *statUpdate = new QTweetStatusUpdate( twitAuth, this ); - connect( statUpdate, SIGNAL( postedStatus(const QTweetStatus &) ), SLOT( postGotTomahawkStatusUpdateReply(const QTweetStatus &) ) ); - connect( statUpdate, SIGNAL( error(QTweetNetBase::ErrorCode, const QString&) ), SLOT( postGotTomahawkStatusUpdateError(QTweetNetBase::ErrorCode, const QString &) ) ); - QString uuid = QUuid::createUuid(); - statUpdate->post( QString( "Got Tomahawk? {" ) + Database::instance()->dbid() + QString( "} (" ) + uuid.mid( 1, 8 ) + QString( ")" ) + QString( " http://gettomahawk.com" ) ); + if ( m_postGTtype != "direct" ) + { + QTweetStatusUpdate *statUpdate = new QTweetStatusUpdate( twitAuth, this ); + connect( statUpdate, SIGNAL( postedStatus(const QTweetStatus &) ), SLOT( postGotTomahawkStatusUpdateReply(const QTweetStatus &) ) ); + connect( statUpdate, SIGNAL( error(QTweetNetBase::ErrorCode, const QString&) ), SLOT( postGotTomahawkStatusUpdateError(QTweetNetBase::ErrorCode, const QString &) ) ); + QString uuid = QUuid::createUuid(); + QString message = QString( "Got Tomahawk? {" ) + Database::instance()->dbid() + QString( "} (" ) + uuid.mid( 1, 8 ) + QString( ")" ) + QString( " http://gettomahawk.com" ); + if ( m_postGTtype == "user" ) + { + QString user = ui->twitterUserTweetLineEdit->text(); + if ( user.startsWith( "@" ) ) + user.remove( 0, 1 ); + message = QString( "@" ) + user + QString( " " ) + message; + } + statUpdate->post( message ); + } + else + { + QTweetDirectMessageNew *statUpdate = new QTweetDirectMessageNew( twitAuth, this ); + connect( statUpdate, SIGNAL( parsedDirectMessage(const QTweetDMStatus &)), SLOT( postGotTomahawkDirectMessageReply(const QTweetDMStatus &) ) ); + connect( statUpdate, SIGNAL( error(QTweetNetBase::ErrorCode, const QString&) ), SLOT( postGotTomahawkStatusUpdateError(QTweetNetBase::ErrorCode, const QString &) ) ); + QString uuid = QUuid::createUuid(); + QString message = QString( "Got Tomahawk? {" ) + Database::instance()->dbid() + QString( "} (" ) + uuid.mid( 1, 8 ) + QString( ")" ) + QString( " http://gettomahawk.com" ); + QString user = ui->twitterDirectTweetLineEdit->text(); + if ( user.startsWith( "@" ) ) + user.remove( 0, 1 ); + statUpdate->post( user, message ); + } } - void TwitterConfigWidget::postGotTomahawkStatusUpdateReply( const QTweetStatus& status ) { @@ -203,6 +286,14 @@ TwitterConfigWidget::postGotTomahawkStatusUpdateReply( const QTweetStatus& statu QMessageBox::information( 0, QString("Tweeted!"), QString("Your tweet has been posted!") ); } +void +TwitterConfigWidget::postGotTomahawkDirectMessageReply( const QTweetDMStatus& status ) +{ + if ( status.id() == 0 ) + QMessageBox::critical( 0, QString("Tweetin' Error"), QString("There was an error posting your direct message -- sorry!") ); + else + QMessageBox::information( 0, QString("Tweeted!"), QString("Your message has been posted!") ); +} void TwitterConfigWidget::postGotTomahawkStatusUpdateError( QTweetNetBase::ErrorCode code, const QString& errorMsg ) diff --git a/src/sip/twitter/twitterconfigwidget.h b/src/sip/twitter/twitterconfigwidget.h index 5521c7f7b..3d18db321 100644 --- a/src/sip/twitter/twitterconfigwidget.h +++ b/src/sip/twitter/twitterconfigwidget.h @@ -22,6 +22,7 @@ #include "sip/SipPlugin.h" #include +#include #include #include @@ -45,19 +46,24 @@ signals: private slots: void authDeauthTwitter(); + void startPostGlobalGotTomahawkStatus(); + void startPostUserGotTomahawkStatus(); + void startPostDirectGotTomahawkStatus(); void startPostGotTomahawkStatus(); void authenticateVerifyReply( const QTweetUser &user ); void authenticateVerifyError( QTweetNetBase::ErrorCode code, const QString &errorMsg ); void postGotTomahawkStatusAuthVerifyReply( const QTweetUser &user ); void postGotTomahawkStatusUpdateReply( const QTweetStatus &status ); + void postGotTomahawkDirectMessageReply( const QTweetDMStatus &status ); void postGotTomahawkStatusUpdateError( QTweetNetBase::ErrorCode, const QString &errorMsg ); private: void authenticateTwitter(); void deauthenticateTwitter(); - + Ui::TwitterConfigWidget *ui; SipPlugin *m_plugin; + QString m_postGTtype; }; #endif // TWITTERCONFIGWIDGET_H diff --git a/src/sip/twitter/twitterconfigwidget.ui b/src/sip/twitter/twitterconfigwidget.ui index f7109915a..52c99105b 100644 --- a/src/sip/twitter/twitterconfigwidget.ui +++ b/src/sip/twitter/twitterconfigwidget.ui @@ -65,9 +65,9 @@ - Here's how it works: just press the button below to tweet "Got Tomahawk?" and some necessary information. Then be (very) patient. Twitter is an asynchronous protocol so it can take a bit! + Here's how it works: just press one of the buttons below to tweet "Got Tomahawk?" and some necessary information. Then be (very) patient. Twitter is an asynchronous protocol so it can take a bit! -If connections to peers seem to have been lost, just press the button again to re-post a tweet for resynchronization. +If connections to peers seem to have been lost, just press the appropriate button again to re-post a tweet for resynchronization. true @@ -75,11 +75,163 @@ If connections to peers seem to have been lost, just press the button again to r - - - Press here to have Tomahawk post a tweet + + + Qt::Vertical - + + QSizePolicy::Fixed + + + + 20 + 10 + + + + + + + + + + + 0 + 0 + + + + Use this button to send a normal, public tweet: + + + + + + + + + + + + + + + 0 + 0 + + + + Press here to post a public tweet + + + + + + + + + + + Use this button to send a public @mention to the user you enter: + + + + + + + + + + + + + + 0 + 0 + + + + + 250 + 0 + + + + e.g. @tomahawkplayer + + + + + + + + 0 + 0 + + + + Press here to post a public @mention + + + + + + + + + + + + + Use this button to send a private, direct message to the user you enter: + + + + + + + + + + + + + + 0 + 0 + + + + + 250 + 0 + + + + e.g. tomahawkplayer + + + + + + + + 0 + 0 + + + + Press here to post a private message + + + + + + + + + + From f16812143f23a38f489d8668e4a905fa088f72f4 Mon Sep 17 00:00:00 2001 From: Jeff Mitchell Date: Sat, 26 Mar 2011 16:49:26 -0400 Subject: [PATCH 158/329] Display a helpful message when someone gives non-JSON data to the xmpp bot --- src/sip/jabber/jabber_p.cpp | 11 +++++++++++ 1 file changed, 11 insertions(+) diff --git a/src/sip/jabber/jabber_p.cpp b/src/sip/jabber/jabber_p.cpp index aafd29543..1b0af71cf 100644 --- a/src/sip/jabber/jabber_p.cpp +++ b/src/sip/jabber/jabber_p.cpp @@ -24,8 +24,10 @@ #include #include #include +#include #include #include +#include using namespace gloox; using namespace std; @@ -428,6 +430,15 @@ Jabber_p::handleMessage( const Message& m, MessageSession * /*session*/ ) //sendMsg( from, QString("You said %1").arg(msg) ); + QJson::Parser parser; + bool ok; + QVariant v = parser.parse( msg.toAscii(), &ok ); + if ( !ok || v.type() != QVariant::Map ) + { + sendMsg( from, QString( "I'm sorry -- I'm just an automatic presence used by Tomahawk Player (http://gettomahawk.com). If you are getting this message, the person you are trying to reach is probably not signed on, so please try again later!" ) ); + return; + } + emit msgReceived( from, msg ); } From 9558270d86be86dff2e5bee53871f75360d085e1 Mon Sep 17 00:00:00 2001 From: Jeff Mitchell Date: Sat, 26 Mar 2011 16:49:26 -0400 Subject: [PATCH 159/329] Display a helpful message when someone gives non-JSON data to the xmpp bot --- src/sip/jabber/jabber_p.cpp | 11 +++++++++++ 1 file changed, 11 insertions(+) diff --git a/src/sip/jabber/jabber_p.cpp b/src/sip/jabber/jabber_p.cpp index 9f2d6276b..2c080004b 100644 --- a/src/sip/jabber/jabber_p.cpp +++ b/src/sip/jabber/jabber_p.cpp @@ -24,9 +24,11 @@ #include #include #include +#include #include #include #include +#include using namespace gloox; using namespace std; @@ -430,6 +432,15 @@ Jabber_p::handleMessage( const Message& m, MessageSession * /*session*/ ) //sendMsg( from, QString("You said %1").arg(msg) ); + QJson::Parser parser; + bool ok; + QVariant v = parser.parse( msg.toAscii(), &ok ); + if ( !ok || v.type() != QVariant::Map ) + { + sendMsg( from, QString( "I'm sorry -- I'm just an automatic presence used by Tomahawk Player (http://gettomahawk.com). If you are getting this message, the person you are trying to reach is probably not signed on, so please try again later!" ) ); + return; + } + emit msgReceived( from, msg ); } From 1697b27aca011691c87b8335d11065327590e4f2 Mon Sep 17 00:00:00 2001 From: Leo Franchi Date: Sat, 26 Mar 2011 18:29:35 -0400 Subject: [PATCH 160/329] fix compile on osx don't use API that doesn't exist in snow leopard---same code is in liblastfmv1. if jono wants to rewrite the path detection code in snow leopard-compatible APIs, we can use that --- thirdparty/liblastfm2/src/core/misc.cpp | 33 +------------------ .../ws/mac/MNetworkConnectionMonitor_mac.cpp | 2 +- 2 files changed, 2 insertions(+), 33 deletions(-) diff --git a/thirdparty/liblastfm2/src/core/misc.cpp b/thirdparty/liblastfm2/src/core/misc.cpp index 13b5d16af..006089925 100644 --- a/thirdparty/liblastfm2/src/core/misc.cpp +++ b/thirdparty/liblastfm2/src/core/misc.cpp @@ -60,38 +60,7 @@ static QDir dataDotDot() return QDir::home(); #elif defined(Q_WS_MAC) - - #define EIT( x ) { OSErr err = x; if (err != noErr) throw 1; } - try - { - short vRefNum = 0; - long dirId; - EIT( ::FindFolder( kOnAppropriateDisk, - kApplicationSupportFolderType, - kDontCreateFolder, - &vRefNum, - &dirId ) ); - - // Now we have a vRefNum and a dirID - but *not* an Unix-Path as string. - // Lets make one based from this: - FSSpec fsspec; - EIT( ::FSMakeFSSpec( vRefNum, dirId, NULL, &fsspec ) ); - - // ...and build an FSRef based on thes FSSpec. - FSRef fsref; - EIT( ::FSpMakeFSRef( &fsspec, &fsref ) ); - - // ...then extract the Unix Path as a C-String from the FSRef - unsigned char path[512]; - EIT( ::FSRefMakePath( &fsref, path, 512 ) ); - - return QDir::homePath() + QString::fromUtf8( (char*)path ); - } - catch (int) - { - return QDir::home().filePath( "Library/Application Support" ); - } - + return QDir::home().filePath( "Library/Application Support" ); #elif defined(Q_WS_X11) return QDir::home().filePath( ".local/share" ); diff --git a/thirdparty/liblastfm2/src/ws/mac/MNetworkConnectionMonitor_mac.cpp b/thirdparty/liblastfm2/src/ws/mac/MNetworkConnectionMonitor_mac.cpp index f9a03f47a..9f99e89fd 100644 --- a/thirdparty/liblastfm2/src/ws/mac/MNetworkConnectionMonitor_mac.cpp +++ b/thirdparty/liblastfm2/src/ws/mac/MNetworkConnectionMonitor_mac.cpp @@ -19,7 +19,7 @@ */ #include "MNetworkConnectionMonitor.h" -#include "ws/ws.h" +#include "../ws.h" #include #include From 5056aadcd20160756378c5da3d7ed0c67a0cbc8a Mon Sep 17 00:00:00 2001 From: Dominik Schmidt Date: Sun, 27 Mar 2011 01:24:18 +0100 Subject: [PATCH 161/329] Don't open more than one SubscriptionRequest window and auto-ack only contacts that the user is already subscribed to --- src/sip/jabber/jabber_p.cpp | 41 +++++++++++++++++++++++++++++-------- src/sip/jabber/jabber_p.h | 2 ++ 2 files changed, 34 insertions(+), 9 deletions(-) diff --git a/src/sip/jabber/jabber_p.cpp b/src/sip/jabber/jabber_p.cpp index 2c080004b..294ea032a 100644 --- a/src/sip/jabber/jabber_p.cpp +++ b/src/sip/jabber/jabber_p.cpp @@ -614,24 +614,46 @@ Jabber_p::handleSubscriptionRequest( const JID& jid, const std::string& /*msg*/ // check if the requester is already on the roster RosterItem *item = m_client->rosterManager()->getRosterItem(jid); - if(item) + if(item && + ( + item->subscription() == gloox::S10nNoneOut || // Contact and user are not subscribed to each other, and user has sent contact a subscription request but contact has not replied yet. + item->subscription() == gloox::S10nTo || // User is subscribed to contact (one-way). + item->subscription() == gloox::S10nToIn // User is subscribed to contact, and contact has sent user a subscription request but user has not replied yet. + ) + ) { - qDebug() << Q_FUNC_INFO << "Already on the roster so we assume ack'ing subscription request is okay..."; + qDebug() << Q_FUNC_INFO << jid.bare().c_str() << "Already on the roster so we assume ack'ing subscription request is okay..."; return true; } + if( !m_subscriptionConfirmBoxes.value(jid).isNull() ) + { + qDebug() << Q_FUNC_INFO << jid.bare().c_str() << " confirmBox already open" ; + // the user decides with the already open box, so we can return false here + return false; + } - // ask whether to accept subscription request or not - QMessageBox::StandardButton allowSubscription; - allowSubscription = QMessageBox::question( 0, - tr("Friend Request in Jabber"), - QString(tr("Do you want to be friends with %1?")).arg(QLatin1String(jid.bare().c_str())), - QMessageBox::Ok | QMessageBox::Cancel - ); + // preparing the confirm box for the user + QWeakPointer confirmBox = QWeakPointer(new QMessageBox( + QMessageBox::Question, + tr("Friend Request in Jabber"), + QString(tr("Do you want to be friends with %1?")).arg(QLatin1String(jid.bare().c_str())), + QMessageBox::Ok | QMessageBox::Cancel, + 0)); + + // add confirmBox to m_subscriptionConfirmBoxes + m_subscriptionConfirmBoxes.insert(jid, confirmBox); + + // display the box and wait for the answer + QMessageBox::StandardButton allowSubscription = static_cast(confirmBox.data()->exec()); + + // we got an answer, deleting the box + confirmBox.data()->deleteLater(); if(allowSubscription == QMessageBox::Ok) { + qDebug() << Q_FUNC_INFO << jid.bare().c_str() << "accepted by user, adding to roster"; StringList groups; groups.push_back( "Tomahawk" ); m_client->rosterManager()->subscribe( jid, "", groups, "" ); @@ -639,6 +661,7 @@ Jabber_p::handleSubscriptionRequest( const JID& jid, const std::string& /*msg*/ return true; } + qDebug() << Q_FUNC_INFO << jid.bare().c_str() << "declined by user"; return false; } diff --git a/src/sip/jabber/jabber_p.h b/src/sip/jabber/jabber_p.h index f051915bc..6f9431985 100644 --- a/src/sip/jabber/jabber_p.h +++ b/src/sip/jabber/jabber_p.h @@ -30,6 +30,7 @@ #include #include #include +#include #include @@ -164,6 +165,7 @@ private: QSharedPointer m_vcardManager; QString m_server; QScopedPointer m_notifier; + QHash > m_subscriptionConfirmBoxes; }; #endif // JABBER_H From 99e1b6f327c7fa69f569c01248f6605c5dfbc383 Mon Sep 17 00:00:00 2001 From: Dominik Schmidt Date: Sun, 27 Mar 2011 04:29:09 +0200 Subject: [PATCH 162/329] Make the subscription request box non-blocking. --- src/sip/jabber/jabber_p.cpp | 46 +++++++++++++++++++++++++++++-------- src/sip/jabber/jabber_p.h | 1 + 2 files changed, 37 insertions(+), 10 deletions(-) diff --git a/src/sip/jabber/jabber_p.cpp b/src/sip/jabber/jabber_p.cpp index 294ea032a..9e28ed034 100644 --- a/src/sip/jabber/jabber_p.cpp +++ b/src/sip/jabber/jabber_p.cpp @@ -151,7 +151,7 @@ Jabber_p::go() m_client->registerPresenceHandler( this ); m_client->registerConnectionListener( this ); - m_client->rosterManager()->registerRosterListener( this ); + m_client->rosterManager()->registerRosterListener( this, false ); // false means async m_client->logInstance().registerLogHandler( LogLevelWarning, LogAreaAll, this ); m_client->registerMessageHandler( this ); @@ -614,6 +614,8 @@ Jabber_p::handleSubscriptionRequest( const JID& jid, const std::string& /*msg*/ // check if the requester is already on the roster RosterItem *item = m_client->rosterManager()->getRosterItem(jid); + if(item) qDebug() << "subscription status:" << static_cast( item->subscription() ); + if(item && ( item->subscription() == gloox::S10nNoneOut || // Contact and user are not subscribed to each other, and user has sent contact a subscription request but contact has not replied yet. @@ -623,7 +625,9 @@ Jabber_p::handleSubscriptionRequest( const JID& jid, const std::string& /*msg*/ ) { qDebug() << Q_FUNC_INFO << jid.bare().c_str() << "Already on the roster so we assume ack'ing subscription request is okay..."; - return true; + + m_client->rosterManager()->ackSubscriptionRequest( jid, true ); + return false; } if( !m_subscriptionConfirmBoxes.value(jid).isNull() ) @@ -631,25 +635,46 @@ Jabber_p::handleSubscriptionRequest( const JID& jid, const std::string& /*msg*/ qDebug() << Q_FUNC_INFO << jid.bare().c_str() << " confirmBox already open" ; // the user decides with the already open box, so we can return false here + m_client->rosterManager()->ackSubscriptionRequest( jid, false ); return false; } // preparing the confirm box for the user - QWeakPointer confirmBox = QWeakPointer(new QMessageBox( + QWeakPointer confirmBox = QWeakPointer( new QMessageBox( QMessageBox::Question, tr("Friend Request in Jabber"), QString(tr("Do you want to be friends with %1?")).arg(QLatin1String(jid.bare().c_str())), QMessageBox::Ok | QMessageBox::Cancel, - 0)); + 0) ); // add confirmBox to m_subscriptionConfirmBoxes - m_subscriptionConfirmBoxes.insert(jid, confirmBox); + m_subscriptionConfirmBoxes.insert( jid, confirmBox ); // display the box and wait for the answer - QMessageBox::StandardButton allowSubscription = static_cast(confirmBox.data()->exec()); + confirmBox.data()->open( this, SLOT( onSubscriptionRequestConfirmed( int ) ) ); + + return false; +} + +void +Jabber_p::onSubscriptionRequestConfirmed(int result) +{ + qDebug() << Q_FUNC_INFO; + + QList< QWeakPointer > confirmBoxes = m_subscriptionConfirmBoxes.values(); + JID jid; + foreach(QWeakPointer currentBox, confirmBoxes) + { + if( !currentBox.isNull() && currentBox.data() == sender() ) + { + jid = m_subscriptionConfirmBoxes.key( currentBox ); + } + } // we got an answer, deleting the box - confirmBox.data()->deleteLater(); + sender()->deleteLater(); + + QMessageBox::StandardButton allowSubscription = static_cast( result ); if(allowSubscription == QMessageBox::Ok) { @@ -658,14 +683,15 @@ Jabber_p::handleSubscriptionRequest( const JID& jid, const std::string& /*msg*/ groups.push_back( "Tomahawk" ); m_client->rosterManager()->subscribe( jid, "", groups, "" ); - return true; + m_client->rosterManager()->ackSubscriptionRequest( jid, true ); + return; } qDebug() << Q_FUNC_INFO << jid.bare().c_str() << "declined by user"; - return false; + m_client->rosterManager()->ackSubscriptionRequest( jid, false ); + return; } - bool Jabber_p::handleUnsubscriptionRequest( const JID& jid, const std::string& /*msg*/ ) { diff --git a/src/sip/jabber/jabber_p.h b/src/sip/jabber/jabber_p.h index 6f9431985..9a9ddee0f 100644 --- a/src/sip/jabber/jabber_p.h +++ b/src/sip/jabber/jabber_p.h @@ -154,6 +154,7 @@ public slots: private slots: void doJabberRecv(); + void onSubscriptionRequestConfirmed(int result); private: bool presenceMeansOnline( gloox::Presence::PresenceType p ); From cb405cde1583c9313ed8da69c9f529608fea0f71 Mon Sep 17 00:00:00 2001 From: Dominik Schmidt Date: Sat, 26 Mar 2011 18:26:31 +0100 Subject: [PATCH 163/329] Make the sip plugin aware of subscription requests again --- src/sip/jabber/jabber_p.cpp | 24 ++++++++++++++++++++---- src/sip/jabber/jabber_p.h | 6 ++++++ 2 files changed, 26 insertions(+), 4 deletions(-) diff --git a/src/sip/jabber/jabber_p.cpp b/src/sip/jabber/jabber_p.cpp index 1b0af71cf..3cb9b69d7 100644 --- a/src/sip/jabber/jabber_p.cpp +++ b/src/sip/jabber/jabber_p.cpp @@ -28,6 +28,7 @@ #include #include #include +#include using namespace gloox; using namespace std; @@ -150,6 +151,7 @@ Jabber_p::go() m_client->registerPresenceHandler( this ); m_client->registerConnectionListener( this ); + m_client->rosterManager()->registerRosterListener( this ); m_client->logInstance().registerLogHandler( LogLevelWarning, LogAreaAll, this ); m_client->registerMessageHandler( this ); @@ -609,10 +611,24 @@ bool Jabber_p::handleSubscriptionRequest( const JID& jid, const std::string& /*msg*/ ) { qDebug() << Q_FUNC_INFO << jid.bare().c_str(); - StringList groups; - groups.push_back( "Tomahawk" ); - m_client->rosterManager()->subscribe( jid, "", groups, "" ); - return true; + + QMessageBox::StandardButton allowSubscription; + allowSubscription = QMessageBox::question( 0, + tr("Friend Request in Jabber"), + QString(tr("Do you want to be friends with %1?")).arg(QLatin1String(jid.bare().c_str())), + QMessageBox::Ok | QMessageBox::Cancel + ); + + if(allowSubscription == QMessageBox::Ok) + { + StringList groups; + groups.push_back( "Tomahawk" ); + m_client->rosterManager()->subscribe( jid, "", groups, "" ); + + return true; + } + + return false; } diff --git a/src/sip/jabber/jabber_p.h b/src/sip/jabber/jabber_p.h index 3e14c7c69..f051915bc 100644 --- a/src/sip/jabber/jabber_p.h +++ b/src/sip/jabber/jabber_p.h @@ -76,6 +76,7 @@ class SIPDLLEXPORT Jabber_p : public gloox::MessageHandler, public gloox::VCardHandler, public gloox::PresenceHandler, + public gloox::RosterListener, gloox::LogHandler //public gloox::DiscoHandler, { @@ -109,6 +110,11 @@ public: virtual void handleRoster( const gloox::Roster& roster ); virtual void handleRosterError( const gloox::IQ& /*iq*/ ); + + // not actually used, just needed for the interface to support subscription requests, see handlePresence for presence handling + virtual void handleRosterPresence( const gloox::RosterItem&, const std::string&, gloox::Presence::PresenceType, const std::string& ) {} + virtual void handleSelfPresence( const gloox::RosterItem&, const std::string&, gloox::Presence::PresenceType, const std::string& ) {} + virtual void handlePresence( const gloox::Presence& presence ); virtual bool handleSubscription( const gloox::JID& jid, const std::string& /*msg*/ ); virtual bool handleSubscriptionRequest( const gloox::JID& jid, const std::string& /*msg*/ ); From dffcd89c7c7d337b5ff2a89ae7fab27543014f05 Mon Sep 17 00:00:00 2001 From: Jeff Mitchell Date: Sun, 27 Mar 2011 00:48:16 -0400 Subject: [PATCH 164/329] Don't just look at a user's latest tweet -- look at the latest tweet that matches our regex. Alternately for DMs look at latest matching regex or matching TOMAHAWKPEER format. Also some consolidation. --- src/sip/twitter/twitter.cpp | 147 +++++++++++++++--------------------- 1 file changed, 59 insertions(+), 88 deletions(-) diff --git a/src/sip/twitter/twitter.cpp b/src/sip/twitter/twitter.cpp index 0bd5c69e7..50541b367 100644 --- a/src/sip/twitter/twitter.cpp +++ b/src/sip/twitter/twitter.cpp @@ -33,6 +33,7 @@ #include #include +static QString s_gotTomahawkRegex = QString( "^(@[a-zA-Z0-9]+ )?(Got Tomahawk\\?) (\\{[a-fA-F0-9\\-]+\\}) (.*)$" ); TwitterPlugin::TwitterPlugin() : SipPlugin() @@ -295,81 +296,6 @@ TwitterPlugin::connectTimerFired() } } -void -TwitterPlugin::friendsTimelineStatuses( const QList< QTweetStatus > &statuses ) -{ - qDebug() << Q_FUNC_INFO; - QRegExp regex( QString( "^(@[a-zA-Z0-9]+ )?(Got Tomahawk\\?) (\\{[a-fA-F0-9\\-]+\\}) (.*)$" ), Qt::CaseSensitive, QRegExp::RegExp2 ); - QString myScreenName = TomahawkSettings::instance()->twitterScreenName(); - - QHash< QString, QTweetStatus > latestHash; - foreach ( QTweetStatus status, statuses ) - { - if ( !latestHash.contains( status.user().screenName() ) ) - latestHash[status.user().screenName()] = status; - else - { - if ( status.id() > latestHash[status.user().screenName()].id() ) - latestHash[status.user().screenName()] = status; - } - } - - foreach( QTweetStatus status, latestHash.values() ) - { - if ( status.id() > m_cachedFriendsSinceId ) - m_cachedFriendsSinceId = status.id(); - - if ( regex.exactMatch( status.text() ) ) - { - qDebug() << "TwitterPlugin found an exact tweet from friend " << status.user().screenName(); - if ( status.text().startsWith( '@' ) && regex.captureCount() >= 2 && regex.cap( 1 ) != QString( '@' + myScreenName ) ) - { - qDebug() << "TwitterPlugin skipping tweet because it's directed @someone that isn't us"; - continue; - } - - QString node; - for ( int i = 0; i < regex.captureCount(); ++i ) - { - if ( regex.cap( i ) == QString( "Got Tomahawk?" ) ) - { - QString nodeCap = regex.cap( i + 1 ); - nodeCap.chop( 1 ); - node = nodeCap.mid( 1 ); - } - } - if ( node.isEmpty() ) - { - qDebug() << "TwitterPlugin could not parse node out of the tweet"; - continue; - } - else - qDebug() << "TwitterPlugin parsed node " << node << " out of the tweet"; - - if ( status.user().screenName() == myScreenName && node == Database::instance()->dbid() ) - { - qDebug() << "My screen name and my dbid found; ignoring"; - continue; - } - - QHash< QString, QVariant > peerData; - if( m_cachedPeers.contains( status.user().screenName() ) ) - { - peerData = m_cachedPeers[status.user().screenName()].toHash(); - //force a re-send of info but no need to re-register - peerData["resend"] = QVariant::fromValue< bool >( true ); - } - peerData["node"] = QVariant::fromValue< QString >( node ); - QMetaObject::invokeMethod( this, "registerOffer", Q_ARG( QString, status.user().screenName() ), QGenericArgument( "QHash< QString, QVariant >", (const void*)&peerData ) ); - } - } - - TomahawkSettings::instance()->setTwitterCachedFriendsSinceId( m_cachedFriendsSinceId ); - - m_finishedFriends = true; - QMetaObject::invokeMethod( this, "pollDirectMessages", Qt::AutoConnection ); -} - void TwitterPlugin::parseGotTomahawk( const QRegExp ®ex, const QString &screenName, const QString &text ) { @@ -417,14 +343,53 @@ TwitterPlugin::parseGotTomahawk( const QRegExp ®ex, const QString &screenName } void -TwitterPlugin::mentionsStatuses( const QList< QTweetStatus > &statuses ) +TwitterPlugin::friendsTimelineStatuses( const QList< QTweetStatus > &statuses ) { qDebug() << Q_FUNC_INFO; - QRegExp regex( QString( "^(@[a-zA-Z0-9]+ )?(Got Tomahawk\\?) (\\{[a-fA-F0-9\\-]+\\}) (.*)$" ), Qt::CaseSensitive, QRegExp::RegExp2 ); + QRegExp regex( s_gotTomahawkRegex, Qt::CaseSensitive, QRegExp::RegExp2 ); + QString myScreenName = TomahawkSettings::instance()->twitterScreenName(); QHash< QString, QTweetStatus > latestHash; foreach ( QTweetStatus status, statuses ) { + if ( !regex.exactMatch( status.text() ) ) + continue; + + if ( !latestHash.contains( status.user().screenName() ) ) + latestHash[status.user().screenName()] = status; + else + { + if ( status.id() > latestHash[status.user().screenName()].id() ) + latestHash[status.user().screenName()] = status; + } + } + + foreach( QTweetStatus status, latestHash.values() ) + { + if ( status.id() > m_cachedFriendsSinceId ) + m_cachedFriendsSinceId = status.id(); + + parseGotTomahawk( regex, status.user().screenName(), status.text() ); + } + + TomahawkSettings::instance()->setTwitterCachedFriendsSinceId( m_cachedFriendsSinceId ); + + m_finishedFriends = true; + QMetaObject::invokeMethod( this, "pollDirectMessages", Qt::AutoConnection ); +} + +void +TwitterPlugin::mentionsStatuses( const QList< QTweetStatus > &statuses ) +{ + qDebug() << Q_FUNC_INFO; + QRegExp regex( s_gotTomahawkRegex, Qt::CaseSensitive, QRegExp::RegExp2 ); + + QHash< QString, QTweetStatus > latestHash; + foreach ( QTweetStatus status, statuses ) + { + if ( !regex.exactMatch( status.text() ) ) + continue; + if ( !latestHash.contains( status.user().screenName() ) ) latestHash[status.user().screenName()] = status; else @@ -439,8 +404,7 @@ TwitterPlugin::mentionsStatuses( const QList< QTweetStatus > &statuses ) if ( status.id() > m_cachedMentionsSinceId ) m_cachedMentionsSinceId = status.id(); - if ( regex.exactMatch( status.text() ) ) - parseGotTomahawk( regex, status.user().screenName(), status.text() ); + parseGotTomahawk( regex, status.user().screenName(), status.text() ); } TomahawkSettings::instance()->setTwitterCachedMentionsSinceId( m_cachedMentionsSinceId ); @@ -475,12 +439,26 @@ TwitterPlugin::directMessages( const QList< QTweetDMStatus > &messages ) { qDebug() << Q_FUNC_INFO; - QRegExp regex( QString( "^(@[a-zA-Z0-9]+ )?(Got Tomahawk\\?) (\\{[a-fA-F0-9\\-]+\\}) (.*)$" ), Qt::CaseSensitive, QRegExp::RegExp2 ); + QRegExp regex( s_gotTomahawkRegex, Qt::CaseSensitive, QRegExp::RegExp2 ); QString myScreenName = TomahawkSettings::instance()->twitterScreenName(); QHash< QString, QTweetDMStatus > latestHash; foreach ( QTweetDMStatus status, messages ) { + if ( !regex.exactMatch( status.text() ) ) + { + QStringList splitList = status.text().split(':'); + if ( splitList.length() != 5 ) + continue; + if ( splitList[0] != "TOMAHAWKPEER" ) + continue; + if ( !splitList[1].startsWith( "Host=" ) || !splitList[2].startsWith( "Port=" ) || !splitList[3].startsWith( "Node=" ) || !splitList[4].startsWith( "PKey=" ) ) + continue; + int port = splitList[2].mid( 5 ).toInt(); + if ( port == 0 ) + continue; + } + if ( !latestHash.contains( status.senderScreenName() ) ) latestHash[status.senderScreenName()] = status; else @@ -504,15 +482,8 @@ TwitterPlugin::directMessages( const QList< QTweetDMStatus > &messages ) qDebug() << "TwitterPlugin found " << splitList.length() << " parts to the message; the parts are:"; foreach( QString part, splitList ) qDebug() << part; - if ( splitList.length() != 5 ) - continue; - if ( splitList[0] != "TOMAHAWKPEER" ) - continue; - if ( !splitList[1].startsWith( "Host=" ) || !splitList[2].startsWith( "Port=" ) || !splitList[3].startsWith( "Node=" ) || !splitList[4].startsWith( "PKey=" ) ) - continue; + //validity is checked above int port = splitList[2].mid( 5 ).toInt(); - if ( port == 0 ) - continue; QString host = splitList[1].mid( 5 ); QString node = splitList[3].mid( 5 ); QString pkey = splitList[4].mid( 5 ); From 5167891846b898bb437bbcd8bc5c8fff8ca841aa Mon Sep 17 00:00:00 2001 From: Dominik Schmidt Date: Sun, 27 Mar 2011 16:15:51 +0200 Subject: [PATCH 165/329] Set CLUCENE_MIN_VERSION default value to the version we need, needed until macro_optional_find_package is fixed to support versions --- CMakeModules/FindCLucene.cmake | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/CMakeModules/FindCLucene.cmake b/CMakeModules/FindCLucene.cmake index 5a00f45ab..52116d01f 100644 --- a/CMakeModules/FindCLucene.cmake +++ b/CMakeModules/FindCLucene.cmake @@ -13,7 +13,7 @@ INCLUDE(CheckSymbolExists) INCLUDE(FindLibraryWithDebug) if(NOT CLUCENE_MIN_VERSION) - set(CLUCENE_MIN_VERSION "0.9.19") + set(CLUCENE_MIN_VERSION "0.9.23") endif(NOT CLUCENE_MIN_VERSION) IF(EXISTS ${PROJECT_CMAKE}/CLuceneConfig.cmake) From da409f9fa7c26309384deb1ca074edb2bda9565e Mon Sep 17 00:00:00 2001 From: Dominik Schmidt Date: Sun, 27 Mar 2011 16:15:51 +0200 Subject: [PATCH 166/329] Set CLUCENE_MIN_VERSION default value to the version we need, needed until macro_optional_find_package is fixed to support versions --- CMakeModules/FindCLucene.cmake | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/CMakeModules/FindCLucene.cmake b/CMakeModules/FindCLucene.cmake index 5a00f45ab..52116d01f 100644 --- a/CMakeModules/FindCLucene.cmake +++ b/CMakeModules/FindCLucene.cmake @@ -13,7 +13,7 @@ INCLUDE(CheckSymbolExists) INCLUDE(FindLibraryWithDebug) if(NOT CLUCENE_MIN_VERSION) - set(CLUCENE_MIN_VERSION "0.9.19") + set(CLUCENE_MIN_VERSION "0.9.23") endif(NOT CLUCENE_MIN_VERSION) IF(EXISTS ${PROJECT_CMAKE}/CLuceneConfig.cmake) From db53aabcf27fe7d9dccd566b7011f230a148d10b Mon Sep 17 00:00:00 2001 From: Jeff Mitchell Date: Sat, 26 Mar 2011 15:50:28 -0400 Subject: [PATCH 167/329] You can now send global, mentions, or private (direct message) Got Tomahawk? tweets --- src/sip/twitter/twitter.cpp | 178 +++++++++++++----------- src/sip/twitter/twitter.h | 1 + src/sip/twitter/twitterconfigwidget.cpp | 111 +++++++++++++-- src/sip/twitter/twitterconfigwidget.h | 8 +- src/sip/twitter/twitterconfigwidget.ui | 164 +++++++++++++++++++++- 5 files changed, 362 insertions(+), 100 deletions(-) diff --git a/src/sip/twitter/twitter.cpp b/src/sip/twitter/twitter.cpp index c4e3e9bb5..0bd5c69e7 100644 --- a/src/sip/twitter/twitter.cpp +++ b/src/sip/twitter/twitter.cpp @@ -370,12 +370,57 @@ TwitterPlugin::friendsTimelineStatuses( const QList< QTweetStatus > &statuses ) QMetaObject::invokeMethod( this, "pollDirectMessages", Qt::AutoConnection ); } +void +TwitterPlugin::parseGotTomahawk( const QRegExp ®ex, const QString &screenName, const QString &text ) +{ + QString myScreenName = TomahawkSettings::instance()->twitterScreenName(); + qDebug() << "TwitterPlugin found an exact matching Got Tomahawk? mention or direct message from user " << screenName; + if ( text.startsWith( '@' ) && regex.captureCount() >= 2 && regex.cap( 1 ) != QString( '@' + myScreenName ) ) + { + qDebug() << "TwitterPlugin skipping mention because it's directed @someone that isn't us"; + return; + } + + QString node; + for ( int i = 0; i < regex.captureCount(); ++i ) + { + if ( regex.cap( i ) == QString( "Got Tomahawk?" ) ) + { + QString nodeCap = regex.cap( i + 1 ); + nodeCap.chop( 1 ); + node = nodeCap.mid( 1 ); + } + } + if ( node.isEmpty() ) + { + qDebug() << "TwitterPlugin could not parse node out of the tweet"; + return; + } + else + qDebug() << "TwitterPlugin parsed node " << node << " out of the tweet"; + + if ( screenName == myScreenName && node == Database::instance()->dbid() ) + { + qDebug() << "My screen name and my dbid found; ignoring"; + return; + } + + QHash< QString, QVariant > peerData; + if( m_cachedPeers.contains( screenName ) ) + { + peerData = m_cachedPeers[screenName].toHash(); + //force a re-send of info but no need to re-register + peerData["resend"] = QVariant::fromValue< bool >( true ); + } + peerData["node"] = QVariant::fromValue< QString >( node ); + QMetaObject::invokeMethod( this, "registerOffer", Q_ARG( QString, screenName ), QGenericArgument( "QHash< QString, QVariant >", (const void*)&peerData ) ); +} + void TwitterPlugin::mentionsStatuses( const QList< QTweetStatus > &statuses ) { qDebug() << Q_FUNC_INFO; QRegExp regex( QString( "^(@[a-zA-Z0-9]+ )?(Got Tomahawk\\?) (\\{[a-fA-F0-9\\-]+\\}) (.*)$" ), Qt::CaseSensitive, QRegExp::RegExp2 ); - QString myScreenName = TomahawkSettings::instance()->twitterScreenName(); QHash< QString, QTweetStatus > latestHash; foreach ( QTweetStatus status, statuses ) @@ -393,50 +438,9 @@ TwitterPlugin::mentionsStatuses( const QList< QTweetStatus > &statuses ) { if ( status.id() > m_cachedMentionsSinceId ) m_cachedMentionsSinceId = status.id(); - + if ( regex.exactMatch( status.text() ) ) - { - qDebug() << "TwitterPlugin found an exact matching mention from user " << status.user().screenName(); - if ( status.text().startsWith( '@' ) && regex.captureCount() >= 2 && regex.cap( 1 ) != QString( '@' + myScreenName ) ) - { - qDebug() << "TwitterPlugin skipping mention because it's directed @someone that isn't us"; - continue; - } - - QString node; - for ( int i = 0; i < regex.captureCount(); ++i ) - { - if ( regex.cap( i ) == QString( "Got Tomahawk?" ) ) - { - QString nodeCap = regex.cap( i + 1 ); - nodeCap.chop( 1 ); - node = nodeCap.mid( 1 ); - } - } - if ( node.isEmpty() ) - { - qDebug() << "TwitterPlugin could not parse node out of the tweet"; - continue; - } - else - qDebug() << "TwitterPlugin parsed node " << node << " out of the tweet"; - - if ( status.user().screenName() == myScreenName && node == Database::instance()->dbid() ) - { - qDebug() << "My screen name and my dbid found; ignoring"; - continue; - } - - QHash< QString, QVariant > peerData; - if( m_cachedPeers.contains( status.user().screenName() ) ) - { - peerData = m_cachedPeers[status.user().screenName()].toHash(); - //force a re-send of info but no need to re-register - peerData["resend"] = QVariant::fromValue< bool >( true ); - } - peerData["node"] = QVariant::fromValue< QString >( node ); - QMetaObject::invokeMethod( this, "registerOffer", Q_ARG( QString, status.user().screenName() ), QGenericArgument( "QHash< QString, QVariant >", (const void*)&peerData ) ); - } + parseGotTomahawk( regex, status.user().screenName(), status.text() ); } TomahawkSettings::instance()->setTwitterCachedMentionsSinceId( m_cachedMentionsSinceId ); @@ -471,6 +475,9 @@ TwitterPlugin::directMessages( const QList< QTweetDMStatus > &messages ) { qDebug() << Q_FUNC_INFO; + QRegExp regex( QString( "^(@[a-zA-Z0-9]+ )?(Got Tomahawk\\?) (\\{[a-fA-F0-9\\-]+\\}) (.*)$" ), Qt::CaseSensitive, QRegExp::RegExp2 ); + QString myScreenName = TomahawkSettings::instance()->twitterScreenName(); + QHash< QString, QTweetDMStatus > latestHash; foreach ( QTweetDMStatus status, messages ) { @@ -488,50 +495,55 @@ TwitterPlugin::directMessages( const QList< QTweetDMStatus > &messages ) qDebug() << "TwitterPlugin checking direct message from " << status.senderScreenName() << " with content " << status.text(); if ( status.id() > m_cachedDirectMessagesSinceId ) m_cachedDirectMessagesSinceId = status.id(); - QStringList splitList = status.text().split(':'); - qDebug() << "TwitterPlugin found " << splitList.length() << " parts to the message; the parts are:"; - foreach( QString part, splitList ) - qDebug() << part; - if ( splitList.length() != 5 ) - continue; - if ( splitList[0] != "TOMAHAWKPEER" ) - continue; - if ( !splitList[1].startsWith( "Host=" ) || !splitList[2].startsWith( "Port=" ) || !splitList[3].startsWith( "Node=" ) || !splitList[4].startsWith( "PKey=" ) ) - continue; - int port = splitList[2].mid( 5 ).toInt(); - if ( port == 0 ) - continue; - QString host = splitList[1].mid( 5 ); - QString node = splitList[3].mid( 5 ); - QString pkey = splitList[4].mid( 5 ); - QStringList splitNode = node.split('*'); - if ( splitNode.length() != 2 ) + + if ( regex.exactMatch( status.text() ) ) + parseGotTomahawk( regex, status.sender().screenName(), status.text() ); + else { - qDebug() << "Old-style node info found, ignoring"; - continue; - } - qDebug() << "TwitterPlugin found a peerstart message from " << status.senderScreenName() << " with host " << host << " and port " << port << " and pkey " << pkey << " and node " << splitNode[0] << " destined for node " << splitNode[1]; + QStringList splitList = status.text().split(':'); + qDebug() << "TwitterPlugin found " << splitList.length() << " parts to the message; the parts are:"; + foreach( QString part, splitList ) + qDebug() << part; + if ( splitList.length() != 5 ) + continue; + if ( splitList[0] != "TOMAHAWKPEER" ) + continue; + if ( !splitList[1].startsWith( "Host=" ) || !splitList[2].startsWith( "Port=" ) || !splitList[3].startsWith( "Node=" ) || !splitList[4].startsWith( "PKey=" ) ) + continue; + int port = splitList[2].mid( 5 ).toInt(); + if ( port == 0 ) + continue; + QString host = splitList[1].mid( 5 ); + QString node = splitList[3].mid( 5 ); + QString pkey = splitList[4].mid( 5 ); + QStringList splitNode = node.split('*'); + if ( splitNode.length() != 2 ) + { + qDebug() << "Old-style node info found, ignoring"; + continue; + } + qDebug() << "TwitterPlugin found a peerstart message from " << status.senderScreenName() << " with host " << host << " and port " << port << " and pkey " << pkey << " and node " << splitNode[0] << " destined for node " << splitNode[1]; + - - QHash< QString, QVariant > peerData = ( m_cachedPeers.contains( status.senderScreenName() ) ) ? - m_cachedPeers[status.senderScreenName()].toHash() : - QHash< QString, QVariant >(); - - peerData["host"] = QVariant::fromValue< QString >( host ); - peerData["port"] = QVariant::fromValue< int >( port ); - peerData["pkey"] = QVariant::fromValue< QString >( pkey ); - peerData["node"] = QVariant::fromValue< QString >( splitNode[0] ); - peerData["dirty"] = QVariant::fromValue< bool >( true ); + QHash< QString, QVariant > peerData = ( m_cachedPeers.contains( status.senderScreenName() ) ) ? + m_cachedPeers[status.senderScreenName()].toHash() : + QHash< QString, QVariant >(); + + peerData["host"] = QVariant::fromValue< QString >( host ); + peerData["port"] = QVariant::fromValue< int >( port ); + peerData["pkey"] = QVariant::fromValue< QString >( pkey ); + peerData["node"] = QVariant::fromValue< QString >( splitNode[0] ); + peerData["dirty"] = QVariant::fromValue< bool >( true ); - QMetaObject::invokeMethod( this, "registerOffer", Q_ARG( QString, status.senderScreenName() ), QGenericArgument( "QHash< QString, QVariant >", (const void*)&peerData ) ); + QMetaObject::invokeMethod( this, "registerOffer", Q_ARG( QString, status.senderScreenName() ), QGenericArgument( "QHash< QString, QVariant >", (const void*)&peerData ) ); - if ( Database::instance()->dbid().startsWith( splitNode[1] ) ) - { - qDebug() << "TwitterPlugin found message destined for this node; destroying it"; - if ( !m_directMessageDestroy.isNull() ) - m_directMessageDestroy.data()->destroyMessage( status.id() ); + if ( Database::instance()->dbid().startsWith( splitNode[1] ) ) + { + qDebug() << "TwitterPlugin found message destined for this node; destroying it"; + if ( !m_directMessageDestroy.isNull() ) + m_directMessageDestroy.data()->destroyMessage( status.id() ); + } } - } TomahawkSettings::instance()->setTwitterCachedDirectMessagesSinceId( m_cachedDirectMessagesSinceId ); diff --git a/src/sip/twitter/twitter.h b/src/sip/twitter/twitter.h index e0c38b479..839ec88c3 100644 --- a/src/sip/twitter/twitter.h +++ b/src/sip/twitter/twitter.h @@ -92,6 +92,7 @@ private slots: private: bool refreshTwitterAuth(); + void parseGotTomahawk( const QRegExp ®ex, const QString &screenName, const QString &text ); QWeakPointer< TomahawkOAuthTwitter > m_twitterAuth; QWeakPointer< QTweetFriendsTimeline > m_friendsTimeline; diff --git a/src/sip/twitter/twitterconfigwidget.cpp b/src/sip/twitter/twitterconfigwidget.cpp index dbf484652..15e38c56d 100644 --- a/src/sip/twitter/twitterconfigwidget.cpp +++ b/src/sip/twitter/twitterconfigwidget.cpp @@ -26,6 +26,7 @@ #include "tomahawkoauthtwitter.h" #include #include +#include #include @@ -39,8 +40,11 @@ TwitterConfigWidget::TwitterConfigWidget(SipPlugin* plugin, QWidget *parent) : connect(ui->twitterAuthenticateButton, SIGNAL(pressed()), this, SLOT(authDeauthTwitter())); connect(ui->twitterTweetGotTomahawkButton, SIGNAL(pressed()), - this, SLOT(startPostGotTomahawkStatus())); - + this, SLOT(startPostGlobalGotTomahawkStatus())); + connect(ui->twitterUserTweetButton, SIGNAL(pressed()), + this, SLOT(startPostUserGotTomahawkStatus())); + connect(ui->twitterDirectTweetButton, SIGNAL(pressed()), + this, SLOT(startPostDirectGotTomahawkStatus())); TomahawkSettings* s = TomahawkSettings::instance(); if ( s->twitterOAuthToken().isEmpty() || s->twitterOAuthTokenSecret().isEmpty() || s->twitterScreenName().isEmpty() ) @@ -48,7 +52,14 @@ TwitterConfigWidget::TwitterConfigWidget(SipPlugin* plugin, QWidget *parent) : ui->twitterStatusLabel->setText("Status: No saved credentials"); ui->twitterAuthenticateButton->setText( "Authenticate" ); ui->twitterInstructionsInfoLabel->setVisible( false ); + ui->twitterGlobalTweetLabel->setVisible( false ); + ui->twitterUserTweetLabel->setVisible( false ); + ui->twitterDirectTweetLabel->setVisible( false ); ui->twitterTweetGotTomahawkButton->setVisible( false ); + ui->twitterUserTweetButton->setVisible( false ); + ui->twitterUserTweetLineEdit->setVisible( false ); + ui->twitterDirectTweetButton->setVisible( false ); + ui->twitterDirectTweetLineEdit->setVisible( false ); emit twitterAuthed( false ); } @@ -57,7 +68,14 @@ TwitterConfigWidget::TwitterConfigWidget(SipPlugin* plugin, QWidget *parent) : ui->twitterStatusLabel->setText("Status: Credentials saved"); ui->twitterAuthenticateButton->setText( "De-authenticate" ); ui->twitterInstructionsInfoLabel->setVisible( true ); + ui->twitterGlobalTweetLabel->setVisible( true ); + ui->twitterUserTweetLabel->setVisible( true ); + ui->twitterDirectTweetLabel->setVisible( true ); ui->twitterTweetGotTomahawkButton->setVisible( true ); + ui->twitterUserTweetButton->setVisible( true ); + ui->twitterUserTweetLineEdit->setVisible( true ); + ui->twitterDirectTweetButton->setVisible( true ); + ui->twitterDirectTweetLineEdit->setVisible( true ); emit twitterAuthed( true ); } @@ -115,7 +133,14 @@ TwitterConfigWidget::authenticateVerifyReply( const QTweetUser &user ) ui->twitterStatusLabel->setText("Status: Credentials saved"); ui->twitterAuthenticateButton->setText( "De-authenticate" ); ui->twitterInstructionsInfoLabel->setVisible( true ); + ui->twitterGlobalTweetLabel->setVisible( true ); + ui->twitterUserTweetLabel->setVisible( true ); + ui->twitterDirectTweetLabel->setVisible( true ); ui->twitterTweetGotTomahawkButton->setVisible( true ); + ui->twitterUserTweetButton->setVisible( true ); + ui->twitterUserTweetLineEdit->setVisible( true ); + ui->twitterDirectTweetButton->setVisible( true ); + ui->twitterDirectTweetLineEdit->setVisible( true ); m_plugin->connectPlugin( false ); @@ -132,7 +157,6 @@ TwitterConfigWidget::authenticateVerifyError( QTweetNetBase::ErrorCode code, con return; } - void TwitterConfigWidget::deauthenticateTwitter() { @@ -145,11 +169,49 @@ TwitterConfigWidget::deauthenticateTwitter() ui->twitterStatusLabel->setText("Status: No saved credentials"); ui->twitterAuthenticateButton->setText( "Authenticate" ); ui->twitterInstructionsInfoLabel->setVisible( false ); + ui->twitterGlobalTweetLabel->setVisible( false ); + ui->twitterUserTweetLabel->setVisible( false ); + ui->twitterDirectTweetLabel->setVisible( false ); ui->twitterTweetGotTomahawkButton->setVisible( false ); + ui->twitterUserTweetButton->setVisible( false ); + ui->twitterUserTweetLineEdit->setVisible( false ); + ui->twitterDirectTweetButton->setVisible( false ); + ui->twitterDirectTweetLineEdit->setVisible( false ); emit twitterAuthed( false ); } +void +TwitterConfigWidget::startPostGlobalGotTomahawkStatus() +{ + m_postGTtype = "global"; + startPostGotTomahawkStatus(); +} + +void +TwitterConfigWidget::startPostUserGotTomahawkStatus() +{ + if ( ui->twitterUserTweetLineEdit->text().isEmpty() || ui->twitterUserTweetLineEdit->text() == "@" ) + { + QMessageBox::critical( 0, QString("Tweetin' Error"), QString("You cannot leave the user name empty when sending a mention.") ); + return; + } + m_postGTtype = "user"; + startPostGotTomahawkStatus(); +} + +void +TwitterConfigWidget::startPostDirectGotTomahawkStatus() +{ + if ( ui->twitterDirectTweetLineEdit->text().isEmpty() || ui->twitterDirectTweetLineEdit->text() == "@" ) + { + QMessageBox::critical( 0, QString("Tweetin' Error"), QString("You cannot leave the user name empty when sending a direct message.") ); + return; + } + m_postGTtype = "direct"; + startPostGotTomahawkStatus(); +} + void TwitterConfigWidget::startPostGotTomahawkStatus() { @@ -170,7 +232,6 @@ TwitterConfigWidget::startPostGotTomahawkStatus() credVerifier->verify(); } - void TwitterConfigWidget::postGotTomahawkStatusAuthVerifyReply( const QTweetUser &user ) { @@ -186,14 +247,36 @@ TwitterConfigWidget::postGotTomahawkStatusAuthVerifyReply( const QTweetUser &use twitAuth->setNetworkAccessManager( TomahawkUtils::nam() ); twitAuth->setOAuthToken( s->twitterOAuthToken().toLatin1() ); twitAuth->setOAuthTokenSecret( s->twitterOAuthTokenSecret().toLatin1() ); - QTweetStatusUpdate *statUpdate = new QTweetStatusUpdate( twitAuth, this ); - connect( statUpdate, SIGNAL( postedStatus(const QTweetStatus &) ), SLOT( postGotTomahawkStatusUpdateReply(const QTweetStatus &) ) ); - connect( statUpdate, SIGNAL( error(QTweetNetBase::ErrorCode, const QString&) ), SLOT( postGotTomahawkStatusUpdateError(QTweetNetBase::ErrorCode, const QString &) ) ); - QString uuid = QUuid::createUuid(); - statUpdate->post( QString( "Got Tomahawk? {" ) + Database::instance()->dbid() + QString( "} (" ) + uuid.mid( 1, 8 ) + QString( ")" ) + QString( " http://gettomahawk.com" ) ); + if ( m_postGTtype != "direct" ) + { + QTweetStatusUpdate *statUpdate = new QTweetStatusUpdate( twitAuth, this ); + connect( statUpdate, SIGNAL( postedStatus(const QTweetStatus &) ), SLOT( postGotTomahawkStatusUpdateReply(const QTweetStatus &) ) ); + connect( statUpdate, SIGNAL( error(QTweetNetBase::ErrorCode, const QString&) ), SLOT( postGotTomahawkStatusUpdateError(QTweetNetBase::ErrorCode, const QString &) ) ); + QString uuid = QUuid::createUuid(); + QString message = QString( "Got Tomahawk? {" ) + Database::instance()->dbid() + QString( "} (" ) + uuid.mid( 1, 8 ) + QString( ")" ) + QString( " http://gettomahawk.com" ); + if ( m_postGTtype == "user" ) + { + QString user = ui->twitterUserTweetLineEdit->text(); + if ( user.startsWith( "@" ) ) + user.remove( 0, 1 ); + message = QString( "@" ) + user + QString( " " ) + message; + } + statUpdate->post( message ); + } + else + { + QTweetDirectMessageNew *statUpdate = new QTweetDirectMessageNew( twitAuth, this ); + connect( statUpdate, SIGNAL( parsedDirectMessage(const QTweetDMStatus &)), SLOT( postGotTomahawkDirectMessageReply(const QTweetDMStatus &) ) ); + connect( statUpdate, SIGNAL( error(QTweetNetBase::ErrorCode, const QString&) ), SLOT( postGotTomahawkStatusUpdateError(QTweetNetBase::ErrorCode, const QString &) ) ); + QString uuid = QUuid::createUuid(); + QString message = QString( "Got Tomahawk? {" ) + Database::instance()->dbid() + QString( "} (" ) + uuid.mid( 1, 8 ) + QString( ")" ) + QString( " http://gettomahawk.com" ); + QString user = ui->twitterDirectTweetLineEdit->text(); + if ( user.startsWith( "@" ) ) + user.remove( 0, 1 ); + statUpdate->post( user, message ); + } } - void TwitterConfigWidget::postGotTomahawkStatusUpdateReply( const QTweetStatus& status ) { @@ -203,6 +286,14 @@ TwitterConfigWidget::postGotTomahawkStatusUpdateReply( const QTweetStatus& statu QMessageBox::information( 0, QString("Tweeted!"), QString("Your tweet has been posted!") ); } +void +TwitterConfigWidget::postGotTomahawkDirectMessageReply( const QTweetDMStatus& status ) +{ + if ( status.id() == 0 ) + QMessageBox::critical( 0, QString("Tweetin' Error"), QString("There was an error posting your direct message -- sorry!") ); + else + QMessageBox::information( 0, QString("Tweeted!"), QString("Your message has been posted!") ); +} void TwitterConfigWidget::postGotTomahawkStatusUpdateError( QTweetNetBase::ErrorCode code, const QString& errorMsg ) diff --git a/src/sip/twitter/twitterconfigwidget.h b/src/sip/twitter/twitterconfigwidget.h index 5521c7f7b..3d18db321 100644 --- a/src/sip/twitter/twitterconfigwidget.h +++ b/src/sip/twitter/twitterconfigwidget.h @@ -22,6 +22,7 @@ #include "sip/SipPlugin.h" #include +#include #include #include @@ -45,19 +46,24 @@ signals: private slots: void authDeauthTwitter(); + void startPostGlobalGotTomahawkStatus(); + void startPostUserGotTomahawkStatus(); + void startPostDirectGotTomahawkStatus(); void startPostGotTomahawkStatus(); void authenticateVerifyReply( const QTweetUser &user ); void authenticateVerifyError( QTweetNetBase::ErrorCode code, const QString &errorMsg ); void postGotTomahawkStatusAuthVerifyReply( const QTweetUser &user ); void postGotTomahawkStatusUpdateReply( const QTweetStatus &status ); + void postGotTomahawkDirectMessageReply( const QTweetDMStatus &status ); void postGotTomahawkStatusUpdateError( QTweetNetBase::ErrorCode, const QString &errorMsg ); private: void authenticateTwitter(); void deauthenticateTwitter(); - + Ui::TwitterConfigWidget *ui; SipPlugin *m_plugin; + QString m_postGTtype; }; #endif // TWITTERCONFIGWIDGET_H diff --git a/src/sip/twitter/twitterconfigwidget.ui b/src/sip/twitter/twitterconfigwidget.ui index f7109915a..52c99105b 100644 --- a/src/sip/twitter/twitterconfigwidget.ui +++ b/src/sip/twitter/twitterconfigwidget.ui @@ -65,9 +65,9 @@ - Here's how it works: just press the button below to tweet "Got Tomahawk?" and some necessary information. Then be (very) patient. Twitter is an asynchronous protocol so it can take a bit! + Here's how it works: just press one of the buttons below to tweet "Got Tomahawk?" and some necessary information. Then be (very) patient. Twitter is an asynchronous protocol so it can take a bit! -If connections to peers seem to have been lost, just press the button again to re-post a tweet for resynchronization. +If connections to peers seem to have been lost, just press the appropriate button again to re-post a tweet for resynchronization. true @@ -75,11 +75,163 @@ If connections to peers seem to have been lost, just press the button again to r - - - Press here to have Tomahawk post a tweet + + + Qt::Vertical - + + QSizePolicy::Fixed + + + + 20 + 10 + + + + + + + + + + + 0 + 0 + + + + Use this button to send a normal, public tweet: + + + + + + + + + + + + + + + 0 + 0 + + + + Press here to post a public tweet + + + + + + + + + + + Use this button to send a public @mention to the user you enter: + + + + + + + + + + + + + + 0 + 0 + + + + + 250 + 0 + + + + e.g. @tomahawkplayer + + + + + + + + 0 + 0 + + + + Press here to post a public @mention + + + + + + + + + + + + + Use this button to send a private, direct message to the user you enter: + + + + + + + + + + + + + + 0 + 0 + + + + + 250 + 0 + + + + e.g. tomahawkplayer + + + + + + + + 0 + 0 + + + + Press here to post a private message + + + + + + + + + + From 26d4da4612562d88de4cc3a6a7cfda6b0cb859f0 Mon Sep 17 00:00:00 2001 From: Jeff Mitchell Date: Sun, 27 Mar 2011 00:48:16 -0400 Subject: [PATCH 168/329] Don't just look at a user's latest tweet -- look at the latest tweet that matches our regex. Alternately for DMs look at latest matching regex or matching TOMAHAWKPEER format. Also some consolidation. --- src/sip/twitter/twitter.cpp | 147 +++++++++++++++--------------------- 1 file changed, 59 insertions(+), 88 deletions(-) diff --git a/src/sip/twitter/twitter.cpp b/src/sip/twitter/twitter.cpp index 0bd5c69e7..50541b367 100644 --- a/src/sip/twitter/twitter.cpp +++ b/src/sip/twitter/twitter.cpp @@ -33,6 +33,7 @@ #include #include +static QString s_gotTomahawkRegex = QString( "^(@[a-zA-Z0-9]+ )?(Got Tomahawk\\?) (\\{[a-fA-F0-9\\-]+\\}) (.*)$" ); TwitterPlugin::TwitterPlugin() : SipPlugin() @@ -295,81 +296,6 @@ TwitterPlugin::connectTimerFired() } } -void -TwitterPlugin::friendsTimelineStatuses( const QList< QTweetStatus > &statuses ) -{ - qDebug() << Q_FUNC_INFO; - QRegExp regex( QString( "^(@[a-zA-Z0-9]+ )?(Got Tomahawk\\?) (\\{[a-fA-F0-9\\-]+\\}) (.*)$" ), Qt::CaseSensitive, QRegExp::RegExp2 ); - QString myScreenName = TomahawkSettings::instance()->twitterScreenName(); - - QHash< QString, QTweetStatus > latestHash; - foreach ( QTweetStatus status, statuses ) - { - if ( !latestHash.contains( status.user().screenName() ) ) - latestHash[status.user().screenName()] = status; - else - { - if ( status.id() > latestHash[status.user().screenName()].id() ) - latestHash[status.user().screenName()] = status; - } - } - - foreach( QTweetStatus status, latestHash.values() ) - { - if ( status.id() > m_cachedFriendsSinceId ) - m_cachedFriendsSinceId = status.id(); - - if ( regex.exactMatch( status.text() ) ) - { - qDebug() << "TwitterPlugin found an exact tweet from friend " << status.user().screenName(); - if ( status.text().startsWith( '@' ) && regex.captureCount() >= 2 && regex.cap( 1 ) != QString( '@' + myScreenName ) ) - { - qDebug() << "TwitterPlugin skipping tweet because it's directed @someone that isn't us"; - continue; - } - - QString node; - for ( int i = 0; i < regex.captureCount(); ++i ) - { - if ( regex.cap( i ) == QString( "Got Tomahawk?" ) ) - { - QString nodeCap = regex.cap( i + 1 ); - nodeCap.chop( 1 ); - node = nodeCap.mid( 1 ); - } - } - if ( node.isEmpty() ) - { - qDebug() << "TwitterPlugin could not parse node out of the tweet"; - continue; - } - else - qDebug() << "TwitterPlugin parsed node " << node << " out of the tweet"; - - if ( status.user().screenName() == myScreenName && node == Database::instance()->dbid() ) - { - qDebug() << "My screen name and my dbid found; ignoring"; - continue; - } - - QHash< QString, QVariant > peerData; - if( m_cachedPeers.contains( status.user().screenName() ) ) - { - peerData = m_cachedPeers[status.user().screenName()].toHash(); - //force a re-send of info but no need to re-register - peerData["resend"] = QVariant::fromValue< bool >( true ); - } - peerData["node"] = QVariant::fromValue< QString >( node ); - QMetaObject::invokeMethod( this, "registerOffer", Q_ARG( QString, status.user().screenName() ), QGenericArgument( "QHash< QString, QVariant >", (const void*)&peerData ) ); - } - } - - TomahawkSettings::instance()->setTwitterCachedFriendsSinceId( m_cachedFriendsSinceId ); - - m_finishedFriends = true; - QMetaObject::invokeMethod( this, "pollDirectMessages", Qt::AutoConnection ); -} - void TwitterPlugin::parseGotTomahawk( const QRegExp ®ex, const QString &screenName, const QString &text ) { @@ -417,14 +343,53 @@ TwitterPlugin::parseGotTomahawk( const QRegExp ®ex, const QString &screenName } void -TwitterPlugin::mentionsStatuses( const QList< QTweetStatus > &statuses ) +TwitterPlugin::friendsTimelineStatuses( const QList< QTweetStatus > &statuses ) { qDebug() << Q_FUNC_INFO; - QRegExp regex( QString( "^(@[a-zA-Z0-9]+ )?(Got Tomahawk\\?) (\\{[a-fA-F0-9\\-]+\\}) (.*)$" ), Qt::CaseSensitive, QRegExp::RegExp2 ); + QRegExp regex( s_gotTomahawkRegex, Qt::CaseSensitive, QRegExp::RegExp2 ); + QString myScreenName = TomahawkSettings::instance()->twitterScreenName(); QHash< QString, QTweetStatus > latestHash; foreach ( QTweetStatus status, statuses ) { + if ( !regex.exactMatch( status.text() ) ) + continue; + + if ( !latestHash.contains( status.user().screenName() ) ) + latestHash[status.user().screenName()] = status; + else + { + if ( status.id() > latestHash[status.user().screenName()].id() ) + latestHash[status.user().screenName()] = status; + } + } + + foreach( QTweetStatus status, latestHash.values() ) + { + if ( status.id() > m_cachedFriendsSinceId ) + m_cachedFriendsSinceId = status.id(); + + parseGotTomahawk( regex, status.user().screenName(), status.text() ); + } + + TomahawkSettings::instance()->setTwitterCachedFriendsSinceId( m_cachedFriendsSinceId ); + + m_finishedFriends = true; + QMetaObject::invokeMethod( this, "pollDirectMessages", Qt::AutoConnection ); +} + +void +TwitterPlugin::mentionsStatuses( const QList< QTweetStatus > &statuses ) +{ + qDebug() << Q_FUNC_INFO; + QRegExp regex( s_gotTomahawkRegex, Qt::CaseSensitive, QRegExp::RegExp2 ); + + QHash< QString, QTweetStatus > latestHash; + foreach ( QTweetStatus status, statuses ) + { + if ( !regex.exactMatch( status.text() ) ) + continue; + if ( !latestHash.contains( status.user().screenName() ) ) latestHash[status.user().screenName()] = status; else @@ -439,8 +404,7 @@ TwitterPlugin::mentionsStatuses( const QList< QTweetStatus > &statuses ) if ( status.id() > m_cachedMentionsSinceId ) m_cachedMentionsSinceId = status.id(); - if ( regex.exactMatch( status.text() ) ) - parseGotTomahawk( regex, status.user().screenName(), status.text() ); + parseGotTomahawk( regex, status.user().screenName(), status.text() ); } TomahawkSettings::instance()->setTwitterCachedMentionsSinceId( m_cachedMentionsSinceId ); @@ -475,12 +439,26 @@ TwitterPlugin::directMessages( const QList< QTweetDMStatus > &messages ) { qDebug() << Q_FUNC_INFO; - QRegExp regex( QString( "^(@[a-zA-Z0-9]+ )?(Got Tomahawk\\?) (\\{[a-fA-F0-9\\-]+\\}) (.*)$" ), Qt::CaseSensitive, QRegExp::RegExp2 ); + QRegExp regex( s_gotTomahawkRegex, Qt::CaseSensitive, QRegExp::RegExp2 ); QString myScreenName = TomahawkSettings::instance()->twitterScreenName(); QHash< QString, QTweetDMStatus > latestHash; foreach ( QTweetDMStatus status, messages ) { + if ( !regex.exactMatch( status.text() ) ) + { + QStringList splitList = status.text().split(':'); + if ( splitList.length() != 5 ) + continue; + if ( splitList[0] != "TOMAHAWKPEER" ) + continue; + if ( !splitList[1].startsWith( "Host=" ) || !splitList[2].startsWith( "Port=" ) || !splitList[3].startsWith( "Node=" ) || !splitList[4].startsWith( "PKey=" ) ) + continue; + int port = splitList[2].mid( 5 ).toInt(); + if ( port == 0 ) + continue; + } + if ( !latestHash.contains( status.senderScreenName() ) ) latestHash[status.senderScreenName()] = status; else @@ -504,15 +482,8 @@ TwitterPlugin::directMessages( const QList< QTweetDMStatus > &messages ) qDebug() << "TwitterPlugin found " << splitList.length() << " parts to the message; the parts are:"; foreach( QString part, splitList ) qDebug() << part; - if ( splitList.length() != 5 ) - continue; - if ( splitList[0] != "TOMAHAWKPEER" ) - continue; - if ( !splitList[1].startsWith( "Host=" ) || !splitList[2].startsWith( "Port=" ) || !splitList[3].startsWith( "Node=" ) || !splitList[4].startsWith( "PKey=" ) ) - continue; + //validity is checked above int port = splitList[2].mid( 5 ).toInt(); - if ( port == 0 ) - continue; QString host = splitList[1].mid( 5 ); QString node = splitList[3].mid( 5 ); QString pkey = splitList[4].mid( 5 ); From 6bde5655cd6cfdfd9637534d8fe4de6662f798db Mon Sep 17 00:00:00 2001 From: Dominik Schmidt Date: Sun, 27 Mar 2011 19:11:12 +0200 Subject: [PATCH 169/329] Hopefully fixed jabber auth completely now --- src/sip/jabber/jabber_p.cpp | 47 ++++++++++++++++++++----------------- src/sip/jabber/jabber_p.h | 2 +- 2 files changed, 26 insertions(+), 23 deletions(-) diff --git a/src/sip/jabber/jabber_p.cpp b/src/sip/jabber/jabber_p.cpp index 9e28ed034..4425b1a93 100644 --- a/src/sip/jabber/jabber_p.cpp +++ b/src/sip/jabber/jabber_p.cpp @@ -614,8 +614,7 @@ Jabber_p::handleSubscriptionRequest( const JID& jid, const std::string& /*msg*/ // check if the requester is already on the roster RosterItem *item = m_client->rosterManager()->getRosterItem(jid); - if(item) qDebug() << "subscription status:" << static_cast( item->subscription() ); - + if(item) qDebug() << Q_FUNC_INFO << jid.bare().c_str() << "subscription status:" << static_cast( item->subscription() ); if(item && ( item->subscription() == gloox::S10nNoneOut || // Contact and user are not subscribed to each other, and user has sent contact a subscription request but contact has not replied yet. @@ -624,34 +623,32 @@ Jabber_p::handleSubscriptionRequest( const JID& jid, const std::string& /*msg*/ ) ) { - qDebug() << Q_FUNC_INFO << jid.bare().c_str() << "Already on the roster so we assume ack'ing subscription request is okay..."; + qDebug() << Q_FUNC_INFO << jid.bare().c_str() << "already on the roster so we assume ack'ing subscription request is okay..."; + // ack the request m_client->rosterManager()->ackSubscriptionRequest( jid, true ); + + // return anything, the result is ignored return false; } - if( !m_subscriptionConfirmBoxes.value(jid).isNull() ) - { - qDebug() << Q_FUNC_INFO << jid.bare().c_str() << " confirmBox already open" ; - - // the user decides with the already open box, so we can return false here - m_client->rosterManager()->ackSubscriptionRequest( jid, false ); - return false; - } + // we don't have to check for an already open check box because gloox doesnt call this method until a former request was ack'ed + qDebug() << Q_FUNC_INFO << jid.bare().c_str() << "open subscription request box"; // preparing the confirm box for the user - QWeakPointer confirmBox = QWeakPointer( new QMessageBox( + QMessageBox *confirmBox = new QMessageBox( QMessageBox::Question, tr("Friend Request in Jabber"), QString(tr("Do you want to be friends with %1?")).arg(QLatin1String(jid.bare().c_str())), QMessageBox::Ok | QMessageBox::Cancel, - 0) ); + 0 + ); // add confirmBox to m_subscriptionConfirmBoxes m_subscriptionConfirmBoxes.insert( jid, confirmBox ); // display the box and wait for the answer - confirmBox.data()->open( this, SLOT( onSubscriptionRequestConfirmed( int ) ) ); + confirmBox->open( this, SLOT( onSubscriptionRequestConfirmed( int ) ) ); return false; } @@ -659,19 +656,22 @@ Jabber_p::handleSubscriptionRequest( const JID& jid, const std::string& /*msg*/ void Jabber_p::onSubscriptionRequestConfirmed(int result) { - qDebug() << Q_FUNC_INFO; + qDebug() << Q_FUNC_INFO << result; - QList< QWeakPointer > confirmBoxes = m_subscriptionConfirmBoxes.values(); + QList< QMessageBox* > confirmBoxes = m_subscriptionConfirmBoxes.values(); JID jid; - foreach(QWeakPointer currentBox, confirmBoxes) + foreach(QMessageBox* currentBox, confirmBoxes) { - if( !currentBox.isNull() && currentBox.data() == sender() ) + if( currentBox == sender() ) { jid = m_subscriptionConfirmBoxes.key( currentBox ); } } + qDebug() << Q_FUNC_INFO << "box confirmed for" << jid.bare().c_str(); + // we got an answer, deleting the box + m_subscriptionConfirmBoxes.remove( jid ); sender()->deleteLater(); QMessageBox::StandardButton allowSubscription = static_cast( result ); @@ -683,13 +683,16 @@ Jabber_p::onSubscriptionRequestConfirmed(int result) groups.push_back( "Tomahawk" ); m_client->rosterManager()->subscribe( jid, "", groups, "" ); + // ack the request m_client->rosterManager()->ackSubscriptionRequest( jid, true ); - return; } + else + { + qDebug() << Q_FUNC_INFO << jid.bare().c_str() << "declined by user"; - qDebug() << Q_FUNC_INFO << jid.bare().c_str() << "declined by user"; - m_client->rosterManager()->ackSubscriptionRequest( jid, false ); - return; + // decl the request + m_client->rosterManager()->ackSubscriptionRequest( jid, false ); + } } bool diff --git a/src/sip/jabber/jabber_p.h b/src/sip/jabber/jabber_p.h index 9a9ddee0f..4f7b1e8b0 100644 --- a/src/sip/jabber/jabber_p.h +++ b/src/sip/jabber/jabber_p.h @@ -166,7 +166,7 @@ private: QSharedPointer m_vcardManager; QString m_server; QScopedPointer m_notifier; - QHash > m_subscriptionConfirmBoxes; + QHash m_subscriptionConfirmBoxes; }; #endif // JABBER_H From 719255e4c9fd1628f769968c3b5efeaf7f0549b5 Mon Sep 17 00:00:00 2001 From: Jeff Mitchell Date: Sun, 27 Mar 2011 13:56:49 -0400 Subject: [PATCH 170/329] Revamp twitter config widget again -- use a drop down box for message type. Also display name of saved user. --- src/sip/twitter/twitterconfigwidget.cpp | 107 +++++++--------- src/sip/twitter/twitterconfigwidget.h | 6 +- src/sip/twitter/twitterconfigwidget.ui | 161 +++++++----------------- 3 files changed, 95 insertions(+), 179 deletions(-) diff --git a/src/sip/twitter/twitterconfigwidget.cpp b/src/sip/twitter/twitterconfigwidget.cpp index 15e38c56d..85660318a 100644 --- a/src/sip/twitter/twitterconfigwidget.cpp +++ b/src/sip/twitter/twitterconfigwidget.cpp @@ -30,22 +30,24 @@ #include -TwitterConfigWidget::TwitterConfigWidget(SipPlugin* plugin, QWidget *parent) : - QWidget(parent), - ui(new Ui::TwitterConfigWidget), - m_plugin(plugin) +TwitterConfigWidget::TwitterConfigWidget( SipPlugin* plugin, QWidget *parent ) : + QWidget( parent ), + ui( new Ui::TwitterConfigWidget ), + m_plugin( plugin ) { - ui->setupUi(this); + ui->setupUi( this ); - connect(ui->twitterAuthenticateButton, SIGNAL(pressed()), - this, SLOT(authDeauthTwitter())); - connect(ui->twitterTweetGotTomahawkButton, SIGNAL(pressed()), - this, SLOT(startPostGlobalGotTomahawkStatus())); - connect(ui->twitterUserTweetButton, SIGNAL(pressed()), - this, SLOT(startPostUserGotTomahawkStatus())); - connect(ui->twitterDirectTweetButton, SIGNAL(pressed()), - this, SLOT(startPostDirectGotTomahawkStatus())); + connect( ui->twitterAuthenticateButton, SIGNAL( pressed() ), + this, SLOT( authDeauthTwitter() ) ); + connect( ui->twitterTweetGotTomahawkButton, SIGNAL( pressed() ), + this, SLOT( startPostGotTomahawkStatus() ) ); + connect( ui->twitterTweetComboBox, SIGNAL( currentIndexChanged( int ) ), + this, SLOT( tweetComboBoxIndexChanged( int ) ) ); + ui->twitterTweetComboBox->setCurrentIndex( 0 ); + ui->twitterUserTweetLineEdit->setReadOnly( true ); + ui->twitterUserTweetLineEdit->setEnabled( false ); + TomahawkSettings* s = TomahawkSettings::instance(); if ( s->twitterOAuthToken().isEmpty() || s->twitterOAuthTokenSecret().isEmpty() || s->twitterScreenName().isEmpty() ) { @@ -53,29 +55,21 @@ TwitterConfigWidget::TwitterConfigWidget(SipPlugin* plugin, QWidget *parent) : ui->twitterAuthenticateButton->setText( "Authenticate" ); ui->twitterInstructionsInfoLabel->setVisible( false ); ui->twitterGlobalTweetLabel->setVisible( false ); - ui->twitterUserTweetLabel->setVisible( false ); - ui->twitterDirectTweetLabel->setVisible( false ); ui->twitterTweetGotTomahawkButton->setVisible( false ); - ui->twitterUserTweetButton->setVisible( false ); ui->twitterUserTweetLineEdit->setVisible( false ); - ui->twitterDirectTweetButton->setVisible( false ); - ui->twitterDirectTweetLineEdit->setVisible( false ); - + ui->twitterTweetComboBox->setVisible( false ); + emit twitterAuthed( false ); } else { - ui->twitterStatusLabel->setText("Status: Credentials saved"); + ui->twitterStatusLabel->setText("Status: Credentials saved for " + s->twitterScreenName() ); ui->twitterAuthenticateButton->setText( "De-authenticate" ); ui->twitterInstructionsInfoLabel->setVisible( true ); ui->twitterGlobalTweetLabel->setVisible( true ); - ui->twitterUserTweetLabel->setVisible( true ); - ui->twitterDirectTweetLabel->setVisible( true ); ui->twitterTweetGotTomahawkButton->setVisible( true ); - ui->twitterUserTweetButton->setVisible( true ); ui->twitterUserTweetLineEdit->setVisible( true ); - ui->twitterDirectTweetButton->setVisible( true ); - ui->twitterDirectTweetLineEdit->setVisible( true ); + ui->twitterTweetComboBox->setVisible( true ); emit twitterAuthed( true ); } @@ -130,17 +124,13 @@ TwitterConfigWidget::authenticateVerifyReply( const QTweetUser &user ) s->setTwitterCachedFriendsSinceId( 0 ); s->setTwitterCachedMentionsSinceId( 0 ); - ui->twitterStatusLabel->setText("Status: Credentials saved"); + ui->twitterStatusLabel->setText("Status: Credentials saved for " + s->twitterScreenName() ); ui->twitterAuthenticateButton->setText( "De-authenticate" ); ui->twitterInstructionsInfoLabel->setVisible( true ); ui->twitterGlobalTweetLabel->setVisible( true ); - ui->twitterUserTweetLabel->setVisible( true ); - ui->twitterDirectTweetLabel->setVisible( true ); ui->twitterTweetGotTomahawkButton->setVisible( true ); - ui->twitterUserTweetButton->setVisible( true ); ui->twitterUserTweetLineEdit->setVisible( true ); - ui->twitterDirectTweetButton->setVisible( true ); - ui->twitterDirectTweetLineEdit->setVisible( true ); + ui->twitterTweetComboBox->setVisible( true ); m_plugin->connectPlugin( false ); @@ -170,51 +160,44 @@ TwitterConfigWidget::deauthenticateTwitter() ui->twitterAuthenticateButton->setText( "Authenticate" ); ui->twitterInstructionsInfoLabel->setVisible( false ); ui->twitterGlobalTweetLabel->setVisible( false ); - ui->twitterUserTweetLabel->setVisible( false ); - ui->twitterDirectTweetLabel->setVisible( false ); ui->twitterTweetGotTomahawkButton->setVisible( false ); - ui->twitterUserTweetButton->setVisible( false ); ui->twitterUserTweetLineEdit->setVisible( false ); - ui->twitterDirectTweetButton->setVisible( false ); - ui->twitterDirectTweetLineEdit->setVisible( false ); + ui->twitterTweetComboBox->setVisible( false ); emit twitterAuthed( false ); } void -TwitterConfigWidget::startPostGlobalGotTomahawkStatus() +TwitterConfigWidget::tweetComboBoxIndexChanged( int index ) { - m_postGTtype = "global"; - startPostGotTomahawkStatus(); -} - -void -TwitterConfigWidget::startPostUserGotTomahawkStatus() -{ - if ( ui->twitterUserTweetLineEdit->text().isEmpty() || ui->twitterUserTweetLineEdit->text() == "@" ) + if( ui->twitterTweetComboBox->currentText() == "Global Tweet" ) { - QMessageBox::critical( 0, QString("Tweetin' Error"), QString("You cannot leave the user name empty when sending a mention.") ); - return; + ui->twitterUserTweetLineEdit->setReadOnly( true ); + ui->twitterUserTweetLineEdit->setEnabled( false ); } - m_postGTtype = "user"; - startPostGotTomahawkStatus(); -} - -void -TwitterConfigWidget::startPostDirectGotTomahawkStatus() -{ - if ( ui->twitterDirectTweetLineEdit->text().isEmpty() || ui->twitterDirectTweetLineEdit->text() == "@" ) + else { - QMessageBox::critical( 0, QString("Tweetin' Error"), QString("You cannot leave the user name empty when sending a direct message.") ); - return; + ui->twitterUserTweetLineEdit->setReadOnly( false ); + ui->twitterUserTweetLineEdit->setEnabled( true ); } - m_postGTtype = "direct"; - startPostGotTomahawkStatus(); + + if( ui->twitterTweetComboBox->currentText() == "Direct Message" ) + ui->twitterTweetGotTomahawkButton->setText( "Send Message!" ); + else + ui->twitterTweetGotTomahawkButton->setText( "Tweet!" ); } void TwitterConfigWidget::startPostGotTomahawkStatus() { + m_postGTtype = ui->twitterTweetComboBox->currentText(); + + if ( m_postGTtype != "Global Tweet" && ( ui->twitterUserTweetLineEdit->text().isEmpty() || ui->twitterUserTweetLineEdit->text() == "@" ) ) + { + QMessageBox::critical( 0, QString("Tweetin' Error"), QString("You must enter a user name for this type of tweet.") ); + return; + } + qDebug() << "Posting Got Tomahawk status"; TomahawkSettings* s = TomahawkSettings::instance(); if ( s->twitterOAuthToken().isEmpty() || s->twitterOAuthTokenSecret().isEmpty() || s->twitterScreenName().isEmpty() ) @@ -247,14 +230,14 @@ TwitterConfigWidget::postGotTomahawkStatusAuthVerifyReply( const QTweetUser &use twitAuth->setNetworkAccessManager( TomahawkUtils::nam() ); twitAuth->setOAuthToken( s->twitterOAuthToken().toLatin1() ); twitAuth->setOAuthTokenSecret( s->twitterOAuthTokenSecret().toLatin1() ); - if ( m_postGTtype != "direct" ) + if ( m_postGTtype != "Direct Message" ) { QTweetStatusUpdate *statUpdate = new QTweetStatusUpdate( twitAuth, this ); connect( statUpdate, SIGNAL( postedStatus(const QTweetStatus &) ), SLOT( postGotTomahawkStatusUpdateReply(const QTweetStatus &) ) ); connect( statUpdate, SIGNAL( error(QTweetNetBase::ErrorCode, const QString&) ), SLOT( postGotTomahawkStatusUpdateError(QTweetNetBase::ErrorCode, const QString &) ) ); QString uuid = QUuid::createUuid(); QString message = QString( "Got Tomahawk? {" ) + Database::instance()->dbid() + QString( "} (" ) + uuid.mid( 1, 8 ) + QString( ")" ) + QString( " http://gettomahawk.com" ); - if ( m_postGTtype == "user" ) + if ( m_postGTtype == "@Mention" ) { QString user = ui->twitterUserTweetLineEdit->text(); if ( user.startsWith( "@" ) ) @@ -270,7 +253,7 @@ TwitterConfigWidget::postGotTomahawkStatusAuthVerifyReply( const QTweetUser &use connect( statUpdate, SIGNAL( error(QTweetNetBase::ErrorCode, const QString&) ), SLOT( postGotTomahawkStatusUpdateError(QTweetNetBase::ErrorCode, const QString &) ) ); QString uuid = QUuid::createUuid(); QString message = QString( "Got Tomahawk? {" ) + Database::instance()->dbid() + QString( "} (" ) + uuid.mid( 1, 8 ) + QString( ")" ) + QString( " http://gettomahawk.com" ); - QString user = ui->twitterDirectTweetLineEdit->text(); + QString user = ui->twitterUserTweetLineEdit->text(); if ( user.startsWith( "@" ) ) user.remove( 0, 1 ); statUpdate->post( user, message ); diff --git a/src/sip/twitter/twitterconfigwidget.h b/src/sip/twitter/twitterconfigwidget.h index 3d18db321..85fabce90 100644 --- a/src/sip/twitter/twitterconfigwidget.h +++ b/src/sip/twitter/twitterconfigwidget.h @@ -38,7 +38,7 @@ class TwitterConfigWidget : public QWidget Q_OBJECT public: - explicit TwitterConfigWidget(SipPlugin* plugin = 0, QWidget *parent = 0); + explicit TwitterConfigWidget( SipPlugin* plugin = 0, QWidget *parent = 0 ); ~TwitterConfigWidget(); signals: @@ -46,9 +46,6 @@ signals: private slots: void authDeauthTwitter(); - void startPostGlobalGotTomahawkStatus(); - void startPostUserGotTomahawkStatus(); - void startPostDirectGotTomahawkStatus(); void startPostGotTomahawkStatus(); void authenticateVerifyReply( const QTweetUser &user ); void authenticateVerifyError( QTweetNetBase::ErrorCode code, const QString &errorMsg ); @@ -56,6 +53,7 @@ private slots: void postGotTomahawkStatusUpdateReply( const QTweetStatus &status ); void postGotTomahawkDirectMessageReply( const QTweetDMStatus &status ); void postGotTomahawkStatusUpdateError( QTweetNetBase::ErrorCode, const QString &errorMsg ); + void tweetComboBoxIndexChanged( int index ); private: void authenticateTwitter(); diff --git a/src/sip/twitter/twitterconfigwidget.ui b/src/sip/twitter/twitterconfigwidget.ui index 52c99105b..1bb728fd0 100644 --- a/src/sip/twitter/twitterconfigwidget.ui +++ b/src/sip/twitter/twitterconfigwidget.ui @@ -101,133 +101,68 @@ If connections to peers seem to have been lost, just press the appropriate butto - Use this button to send a normal, public tweet: + Select the kind of tweet you would like, then press the button to post it: - + + + + 0 + 0 + + - + + Global Tweet + - - - - 0 - 0 - - - - Press here to post a public tweet - - + + @Mention + - + + + Direct Message + + + - - - - - - Use this button to send a public @mention to the user you enter: - - - - - - - - - - - - - 0 - 0 - - - - - 250 - 0 - - - - e.g. @tomahawkplayer - - - - - - - - 0 - 0 - - - - Press here to post a public @mention - - - - - - + + + + 0 + 0 + + + + + 250 + 0 + + + + e.g. @tomahawkplayer + + - - - - - - Use this button to send a private, direct message to the user you enter: - - - - - - - - - - - - - 0 - 0 - - - - - 250 - 0 - - - - e.g. tomahawkplayer - - - - - - - - 0 - 0 - - - - Press here to post a private message - - - - - - + + + + 0 + 0 + + + + Tweet! + + From c4302e5f4984b2c3a0b16628a021f2cd2e053116 Mon Sep 17 00:00:00 2001 From: Jeff Mitchell Date: Sun, 27 Mar 2011 16:09:56 -0400 Subject: [PATCH 171/329] Show legal indemnity warning, and link to the legal page on the web site. Only displays the first time you go online (unless you don't accept it, of course). --- src/libtomahawk/tomahawksettings.cpp | 11 +++++++++++ src/libtomahawk/tomahawksettings.h | 3 +++ src/sip/SipHandler.cpp | 15 +++++++++++++++ 3 files changed, 29 insertions(+) diff --git a/src/libtomahawk/tomahawksettings.cpp b/src/libtomahawk/tomahawksettings.cpp index 34609d5ec..a4008388b 100644 --- a/src/libtomahawk/tomahawksettings.cpp +++ b/src/libtomahawk/tomahawksettings.cpp @@ -89,6 +89,17 @@ TomahawkSettings::hasScannerPath() const return contains( "scannerpath" ); } +void +TomahawkSettings::setAcceptedLegalWarning( bool accept ) +{ + setValue( "acceptedLegalWarning", accept ); +} + +bool +TomahawkSettings::acceptedLegalWarning() const +{ + return value( "acceptedLegalWarning", false ).toBool(); +} bool TomahawkSettings::httpEnabled() const diff --git a/src/libtomahawk/tomahawksettings.h b/src/libtomahawk/tomahawksettings.h index cb7fa9bef..f7f5e2922 100644 --- a/src/libtomahawk/tomahawksettings.h +++ b/src/libtomahawk/tomahawksettings.h @@ -45,6 +45,9 @@ public: void setScannerPath( const QString& path ); bool hasScannerPath() const; + bool acceptedLegalWarning() const; + void setAcceptedLegalWarning( bool accept ); + /// UI settings QByteArray mainWindowGeometry() const; void setMainWindowGeometry( const QByteArray& geom ); diff --git a/src/sip/SipHandler.cpp b/src/sip/SipHandler.cpp index 3b3327082..d09fe9d7e 100644 --- a/src/sip/SipHandler.cpp +++ b/src/sip/SipHandler.cpp @@ -28,6 +28,7 @@ #include "network/controlconnection.h" #include "sourcelist.h" #include "tomahawksettings.h" +#include "tomahawk/tomahawkapp.h" #include "config.h" @@ -168,6 +169,20 @@ SipHandler::pluginLoaded( const QString& name ) const void SipHandler::connectPlugins( bool startup, const QString &pluginName ) { +#ifndef TOMAHAWK_HEADLESS + if ( !TomahawkSettings::instance()->acceptedLegalWarning() ) + { + int result = QMessageBox::question( + TomahawkApp::instance()->mainWindow(), "Legal Warning", + "By pressing OK below, you agree that your use of Tomahawk will be in accordance with any applicable laws, including copyright and intellectual property laws, in effect in your country of residence, and indemify the Tomahawk developers and project from liability should you choose to break those laws.\n\nFor more information, please see http://gettomahawk.com/legal", + "I Do Not Agree", "I Agree" + ); + if ( result != 1 ) + return; + else + TomahawkSettings::instance()->setAcceptedLegalWarning( true ); + } +#endif foreach( SipPlugin* sip, m_plugins ) { if ( pluginName.isEmpty() || ( !pluginName.isEmpty() && sip->name() == pluginName ) ) From 250202a095e8ba6c2c00efa136d53c26625a5394 Mon Sep 17 00:00:00 2001 From: Dominik Schmidt Date: Sun, 27 Mar 2011 23:56:57 +0200 Subject: [PATCH 172/329] Added fixed-jabber-auth to changelog --- ChangeLog | 2 ++ 1 file changed, 2 insertions(+) diff --git a/ChangeLog b/ChangeLog index e2d364e02..47bb36838 100644 --- a/ChangeLog +++ b/ChangeLog @@ -1,2 +1,4 @@ +Version 0.0.2: + * Fixed Jabber auth: Freshly added contacts never showed up. Version 0.0.1: * First public release. From 87fa7e0f50747f9d222606023126bdd2d423fffb Mon Sep 17 00:00:00 2001 From: Leo Franchi Date: Sun, 27 Mar 2011 17:58:00 -0400 Subject: [PATCH 173/329] Add to changelog --- ChangeLog | 3 +++ 1 file changed, 3 insertions(+) diff --git a/ChangeLog b/ChangeLog index e2d364e02..dfc17ddbd 100644 --- a/ChangeLog +++ b/ChangeLog @@ -1,2 +1,5 @@ +Version 0.0.2: + * Don't let long playlist or summary names force a large Tomahawk window. + Version 0.0.1: * First public release. From b378f934eca56d7e6c732464d8e83da9b58a5426 Mon Sep 17 00:00:00 2001 From: Dominik Schmidt Date: Mon, 28 Mar 2011 00:19:15 +0200 Subject: [PATCH 174/329] Add changelog message in stable too, i'm sorry :p --- ChangeLog | 1 + 1 file changed, 1 insertion(+) diff --git a/ChangeLog b/ChangeLog index dfc17ddbd..8fb23b96a 100644 --- a/ChangeLog +++ b/ChangeLog @@ -1,5 +1,6 @@ Version 0.0.2: * Don't let long playlist or summary names force a large Tomahawk window. + * Fixed Jabber auth: Freshly added contacts never showed up. Version 0.0.1: * First public release. From 63550ce71214653806aca335ba19afa98e5d2f7b Mon Sep 17 00:00:00 2001 From: Jeff Mitchell Date: Sun, 27 Mar 2011 18:54:53 -0400 Subject: [PATCH 175/329] Don't attempt to reconnect over and over with twitter if the peer hasn't changed status --- src/sip/twitter/twitter.cpp | 15 ++++++++++++--- src/sip/twitter/twitter.h | 1 + 2 files changed, 13 insertions(+), 3 deletions(-) diff --git a/src/sip/twitter/twitter.cpp b/src/sip/twitter/twitter.cpp index 50541b367..ba8b3f1ef 100644 --- a/src/sip/twitter/twitter.cpp +++ b/src/sip/twitter/twitter.cpp @@ -168,6 +168,7 @@ TwitterPlugin::disconnectPlugin() m_twitterAuth.data()->deleteLater(); m_cachedPeers.empty(); + m_attemptedConnects.empty(); delete m_twitterAuth.data(); m_isOnline = false; } @@ -583,14 +584,16 @@ TwitterPlugin::registerOffer( const QString &screenName, const QHash< QString, Q qDebug() << "TwitterPlugin did not send offer because external address is " << Servent::instance()->externalAddress() << " and external port is " << Servent::instance()->externalPort(); } - if ( m_isOnline && _peerData.contains( "host" ) && _peerData.contains( "port" ) && _peerData.contains( "pkey" ) ) - QMetaObject::invokeMethod( this, "makeConnection", Q_ARG( QString, screenName ), QGenericArgument( "QHash< QString, QVariant >", (const void*)&_peerData ) ); - if ( peersChanged ) { m_cachedPeers[screenName] = QVariant::fromValue< QHash< QString, QVariant > >( _peerData ); TomahawkSettings::instance()->setTwitterCachedPeers( m_cachedPeers ); + m_attemptedConnects[screenName] = false; } + + if ( m_isOnline && _peerData.contains( "host" ) && _peerData.contains( "port" ) && _peerData.contains( "pkey" ) ) + QMetaObject::invokeMethod( this, "makeConnection", Q_ARG( QString, screenName ), QGenericArgument( "QHash< QString, QVariant >", (const void*)&_peerData ) ); + } void @@ -611,6 +614,11 @@ void TwitterPlugin::makeConnection( const QString &screenName, const QHash< QString, QVariant > &peerData ) { qDebug() << Q_FUNC_INFO; + if ( m_attemptedConnects.contains( screenName ) && m_attemptedConnects[screenName] ) + { + qDebug() << "Already attempted to connect to this peer with no change in their status, not trying again for now"; + return; + } if ( !peerData.contains( "host" ) || !peerData.contains( "port" ) || !peerData.contains( "pkey" ) || !peerData.contains( "node" ) ) { qDebug() << "TwitterPlugin could not find host and/or port and/or pkey for peer " << screenName; @@ -623,6 +631,7 @@ TwitterPlugin::makeConnection( const QString &screenName, const QHash< QString, peerData["pkey"].toString(), friendlyName, peerData["node"].toString() ); + m_attemptedConnects[screenName] = true; } void diff --git a/src/sip/twitter/twitter.h b/src/sip/twitter/twitter.h index 839ec88c3..b0dc213b3 100644 --- a/src/sip/twitter/twitter.h +++ b/src/sip/twitter/twitter.h @@ -108,6 +108,7 @@ private: qint64 m_cachedMentionsSinceId; qint64 m_cachedDirectMessagesSinceId; QHash< QString, QVariant > m_cachedPeers; + QHash< QString, bool > m_attemptedConnects; QSet m_keyCache; bool m_finishedFriends; bool m_finishedMentions; From 21e6fb8c09e4da988706710bef2d8c274b5ba770 Mon Sep 17 00:00:00 2001 From: Jeff Mitchell Date: Sun, 27 Mar 2011 19:00:52 -0400 Subject: [PATCH 176/329] Update ChangeLog --- ChangeLog | 16 +++++++++++++--- 1 file changed, 13 insertions(+), 3 deletions(-) diff --git a/ChangeLog b/ChangeLog index 8fb23b96a..696509ace 100644 --- a/ChangeLog +++ b/ChangeLog @@ -1,6 +1,16 @@ Version 0.0.2: - * Don't let long playlist or summary names force a large Tomahawk window. - * Fixed Jabber auth: Freshly added contacts never showed up. + * Don't attempt to connect to unavailable Twitter peers over and over. + * Find Twitter peers if the peer's Got Tomahawk? tweet is not their latest + tweet. + * Got Tomahawk? tweets can now be sent directly to specific users or in + private direct messages. + * Display a helpful message when someone sends a normal instant message to + the Tomahawk XMPP presence. + * Incompatible change: Twitter SIP protocol has changed slightly. 0.0.1 + clients will not be able to talk to newer clients. + * Hopefully fix crashes during Twitter authentication. + * Don't let long playlist or summary names force a large Tomahawk window. + * Fixed Jabber auth: Freshly added contacts never showed up. Version 0.0.1: - * First public release. + * First public release. From 608b90867a97586a91e13cd63f958c6c97513697 Mon Sep 17 00:00:00 2001 From: Jeff Mitchell Date: Sun, 27 Mar 2011 19:10:30 -0400 Subject: [PATCH 177/329] Don't rescan the local collection if the settings dialog is closed with success but the path hasn't changed --- ChangeLog | 2 ++ src/scanmanager.cpp | 11 +++++++++-- src/scanmanager.h | 1 + 3 files changed, 12 insertions(+), 2 deletions(-) diff --git a/ChangeLog b/ChangeLog index 696509ace..57ebcf0d8 100644 --- a/ChangeLog +++ b/ChangeLog @@ -1,4 +1,6 @@ Version 0.0.2: + * Don't run a rescan of the local collection if the settings dialog is + closed successfully but the path hasn't changed. * Don't attempt to connect to unavailable Twitter peers over and over. * Find Twitter peers if the peer's Got Tomahawk? tweet is not their latest tweet. diff --git a/src/scanmanager.cpp b/src/scanmanager.cpp index 613d91d4c..ebbe0eabb 100644 --- a/src/scanmanager.cpp +++ b/src/scanmanager.cpp @@ -44,6 +44,9 @@ ScanManager::ScanManager( QObject* parent ) s_instance = this; connect( TomahawkSettings::instance(), SIGNAL( changed() ), SLOT( onSettingsChanged() ) ); + + if ( TomahawkSettings::instance()->hasScannerPath() ) + m_currScannerPath = TomahawkSettings::instance()->scannerPath(); } @@ -76,8 +79,12 @@ ScanManager::~ScanManager() void ScanManager::onSettingsChanged() { - if ( TomahawkSettings::instance()->hasScannerPath() ) - runManualScan( TomahawkSettings::instance()->scannerPath() ); + if ( TomahawkSettings::instance()->hasScannerPath() && + m_currScannerPath != TomahawkSettings::instance()->scannerPath() ) + { + m_currScannerPath = TomahawkSettings::instance()->scannerPath(); + runManualScan( m_currScannerPath ); + } } diff --git a/src/scanmanager.h b/src/scanmanager.h index dc139cded..0a115b444 100644 --- a/src/scanmanager.h +++ b/src/scanmanager.h @@ -53,6 +53,7 @@ private: MusicScanner* m_scanner; QThread* m_musicScannerThreadController; + QString m_currScannerPath; }; #endif From 057825bf970dbb9021d2ae46866dcfd49bde532d Mon Sep 17 00:00:00 2001 From: Christian Muehlhaeuser Date: Mon, 28 Mar 2011 03:31:10 +0200 Subject: [PATCH 178/329] * Fixed SIP plugins always reconnecting when the SettingsDialog was closed. --- src/sip/SipHandler.cpp | 28 ++++++++++-------------- src/sip/SipHandler.h | 1 + src/sip/jabber/jabber.cpp | 43 +++++++++++++++++++++++++++++++------ src/sip/jabber/jabber.h | 11 ++++++++-- src/sip/jabber/jabber_p.cpp | 2 +- src/sip/twitter/twitter.cpp | 23 +++++++++----------- src/sip/twitter/twitter.h | 3 +-- src/sip/zeroconf/zeroconf.h | 2 +- 8 files changed, 70 insertions(+), 43 deletions(-) diff --git a/src/sip/SipHandler.cpp b/src/sip/SipHandler.cpp index d09fe9d7e..7d77b5fc7 100644 --- a/src/sip/SipHandler.cpp +++ b/src/sip/SipHandler.cpp @@ -28,7 +28,6 @@ #include "network/controlconnection.h" #include "sourcelist.h" #include "tomahawksettings.h" -#include "tomahawk/tomahawkapp.h" #include "config.h" @@ -59,8 +58,7 @@ SipHandler::plugins() const void SipHandler::onSettingsChanged() { - disconnectPlugins(); - connectPlugins(); + checkSettings(); } @@ -166,23 +164,19 @@ SipHandler::pluginLoaded( const QString& name ) const } +void +SipHandler::checkSettings() +{ + foreach( SipPlugin* sip, m_plugins ) + { + sip->checkSettings(); + } +} + + void SipHandler::connectPlugins( bool startup, const QString &pluginName ) { -#ifndef TOMAHAWK_HEADLESS - if ( !TomahawkSettings::instance()->acceptedLegalWarning() ) - { - int result = QMessageBox::question( - TomahawkApp::instance()->mainWindow(), "Legal Warning", - "By pressing OK below, you agree that your use of Tomahawk will be in accordance with any applicable laws, including copyright and intellectual property laws, in effect in your country of residence, and indemify the Tomahawk developers and project from liability should you choose to break those laws.\n\nFor more information, please see http://gettomahawk.com/legal", - "I Do Not Agree", "I Agree" - ); - if ( result != 1 ) - return; - else - TomahawkSettings::instance()->setAcceptedLegalWarning( true ); - } -#endif foreach( SipPlugin* sip, m_plugins ) { if ( pluginName.isEmpty() || ( !pluginName.isEmpty() && sip->name() == pluginName ) ) diff --git a/src/sip/SipHandler.h b/src/sip/SipHandler.h index f127dcc16..0d3dcfa63 100644 --- a/src/sip/SipHandler.h +++ b/src/sip/SipHandler.h @@ -39,6 +39,7 @@ public: public slots: void addContact( const QString& id ) { qDebug() << Q_FUNC_INFO << id; } + void checkSettings(); void connectPlugins( bool startup = false, const QString &pluginName = QString() ); void disconnectPlugins( const QString &pluginName = QString() ); void toggleConnect(); diff --git a/src/sip/jabber/jabber.cpp b/src/sip/jabber/jabber.cpp index 761335d5f..05a49141e 100644 --- a/src/sip/jabber/jabber.cpp +++ b/src/sip/jabber/jabber.cpp @@ -76,12 +76,14 @@ JabberPlugin::connectPlugin( bool startup ) if ( startup && !TomahawkSettings::instance()->jabberAutoConnect() ) return false; - QString jid = TomahawkSettings::instance()->jabberUsername(); - QString server = TomahawkSettings::instance()->jabberServer(); - QString password = TomahawkSettings::instance()->jabberPassword(); - unsigned int port = TomahawkSettings::instance()->jabberPort(); + m_currentUsername = TomahawkSettings::instance()->jabberUsername(); + m_currentPassword = TomahawkSettings::instance()->jabberPassword(); + m_currentServer = TomahawkSettings::instance()->jabberServer(); + m_currentPort = TomahawkSettings::instance()->jabberPort(); - QStringList splitJid = jid.split( '@', QString::SkipEmptyParts ); + QString server = m_currentServer; + + QStringList splitJid = m_currentUsername.split( '@', QString::SkipEmptyParts ); if ( splitJid.size() < 2 ) { qDebug() << "JID did not have an @ in it, could not find a server part"; @@ -91,14 +93,14 @@ JabberPlugin::connectPlugin( bool startup ) if ( server.isEmpty() ) server = splitJid[1]; - if ( port < 1 || port > 65535 || jid.isEmpty() || password.isEmpty() ) + if ( m_currentPort < 1 || m_currentPort > 65535 || m_currentUsername.isEmpty() || m_currentPassword.isEmpty() ) { qDebug() << "Jabber credentials look wrong, not connecting"; return false; } delete p; - p = new Jabber_p( jid, password, server, port ); + p = new Jabber_p( m_currentUsername, m_currentPassword, server, m_currentPort ); QObject::connect( p, SIGNAL( peerOnline( QString ) ), SIGNAL( peerOnline( QString ) ) ); QObject::connect( p, SIGNAL( peerOffline( QString ) ), SIGNAL( peerOffline( QString ) ) ); @@ -174,4 +176,31 @@ JabberPlugin::showAddFriendDialog() addContact( id ); } + +void +JabberPlugin::checkSettings() +{ + bool reconnect = false; + + if ( m_currentUsername != TomahawkSettings::instance()->jabberUsername() ) + reconnect = true; + if ( m_currentPassword != TomahawkSettings::instance()->jabberPassword() ) + reconnect = true; + if ( m_currentServer != TomahawkSettings::instance()->jabberServer() ) + reconnect = true; + if ( m_currentPort != TomahawkSettings::instance()->jabberPort() ) + reconnect = true; + + m_currentUsername = TomahawkSettings::instance()->jabberUsername(); + m_currentPassword = TomahawkSettings::instance()->jabberPassword(); + m_currentServer = TomahawkSettings::instance()->jabberServer(); + m_currentPort = TomahawkSettings::instance()->jabberPort(); + + if ( reconnect && ( p || TomahawkSettings::instance()->jabberAutoConnect() ) ) + { + disconnectPlugin(); + connectPlugin( false ); + } +} + Q_EXPORT_PLUGIN2( sip, JabberPlugin ) diff --git a/src/sip/jabber/jabber.h b/src/sip/jabber/jabber.h index c702fed71..a5bd18a9b 100644 --- a/src/sip/jabber/jabber.h +++ b/src/sip/jabber/jabber.h @@ -51,7 +51,7 @@ public slots: void disconnectPlugin() { onDisconnected(); - + if ( p ) p->disconnect(); @@ -59,6 +59,8 @@ public slots: p = 0; } + void checkSettings(); + void sendMsg( const QString& to, const QString& msg ) { if ( p ) @@ -82,11 +84,16 @@ private slots: void showAddFriendDialog(); void onConnected(); void onDisconnected(); - + private: Jabber_p* p; QMenu* m_menu; QAction* m_addFriendAction; + + QString m_currentServer; + QString m_currentUsername; + QString m_currentPassword; + unsigned int m_currentPort; }; #endif diff --git a/src/sip/jabber/jabber_p.cpp b/src/sip/jabber/jabber_p.cpp index 4425b1a93..8513ef144 100644 --- a/src/sip/jabber/jabber_p.cpp +++ b/src/sip/jabber/jabber_p.cpp @@ -27,8 +27,8 @@ #include #include #include -#include #include +#include using namespace gloox; using namespace std; diff --git a/src/sip/twitter/twitter.cpp b/src/sip/twitter/twitter.cpp index ba8b3f1ef..d7e0d8464 100644 --- a/src/sip/twitter/twitter.cpp +++ b/src/sip/twitter/twitter.cpp @@ -168,7 +168,6 @@ TwitterPlugin::disconnectPlugin() m_twitterAuth.data()->deleteLater(); m_cachedPeers.empty(); - m_attemptedConnects.empty(); delete m_twitterAuth.data(); m_isOnline = false; } @@ -584,16 +583,14 @@ TwitterPlugin::registerOffer( const QString &screenName, const QHash< QString, Q qDebug() << "TwitterPlugin did not send offer because external address is " << Servent::instance()->externalAddress() << " and external port is " << Servent::instance()->externalPort(); } + if ( m_isOnline && _peerData.contains( "host" ) && _peerData.contains( "port" ) && _peerData.contains( "pkey" ) ) + QMetaObject::invokeMethod( this, "makeConnection", Q_ARG( QString, screenName ), QGenericArgument( "QHash< QString, QVariant >", (const void*)&_peerData ) ); + if ( peersChanged ) { m_cachedPeers[screenName] = QVariant::fromValue< QHash< QString, QVariant > >( _peerData ); TomahawkSettings::instance()->setTwitterCachedPeers( m_cachedPeers ); - m_attemptedConnects[screenName] = false; } - - if ( m_isOnline && _peerData.contains( "host" ) && _peerData.contains( "port" ) && _peerData.contains( "pkey" ) ) - QMetaObject::invokeMethod( this, "makeConnection", Q_ARG( QString, screenName ), QGenericArgument( "QHash< QString, QVariant >", (const void*)&_peerData ) ); - } void @@ -614,11 +611,6 @@ void TwitterPlugin::makeConnection( const QString &screenName, const QHash< QString, QVariant > &peerData ) { qDebug() << Q_FUNC_INFO; - if ( m_attemptedConnects.contains( screenName ) && m_attemptedConnects[screenName] ) - { - qDebug() << "Already attempted to connect to this peer with no change in their status, not trying again for now"; - return; - } if ( !peerData.contains( "host" ) || !peerData.contains( "port" ) || !peerData.contains( "pkey" ) || !peerData.contains( "node" ) ) { qDebug() << "TwitterPlugin could not find host and/or port and/or pkey for peer " << screenName; @@ -631,7 +623,6 @@ TwitterPlugin::makeConnection( const QString &screenName, const QHash< QString, peerData["pkey"].toString(), friendlyName, peerData["node"].toString() ); - m_attemptedConnects[screenName] = true; } void @@ -639,7 +630,6 @@ TwitterPlugin::directMessagePosted( const QTweetDMStatus& message ) { qDebug() << Q_FUNC_INFO; qDebug() << "TwitterPlugin sent message to " << message.recipientScreenName() << " containing: " << message.text(); - } void @@ -656,4 +646,11 @@ TwitterPlugin::directMessageDestroyed( const QTweetDMStatus& message ) qDebug() << "TwitterPlugin destroyed message " << message.text(); } +void +TwitterPlugin::checkSettings() +{ + disconnectPlugin(); + connectPlugin( false ); +} + Q_EXPORT_PLUGIN2( sip, TwitterPlugin ) diff --git a/src/sip/twitter/twitter.h b/src/sip/twitter/twitter.h index b0dc213b3..c0da00d7f 100644 --- a/src/sip/twitter/twitter.h +++ b/src/sip/twitter/twitter.h @@ -59,8 +59,8 @@ public: public slots: virtual bool connectPlugin( bool startup ); - void disconnectPlugin(); + void checkSettings(); void sendMsg( const QString& to, const QString& msg ) { @@ -108,7 +108,6 @@ private: qint64 m_cachedMentionsSinceId; qint64 m_cachedDirectMessagesSinceId; QHash< QString, QVariant > m_cachedPeers; - QHash< QString, bool > m_attemptedConnects; QSet m_keyCache; bool m_finishedFriends; bool m_finishedMentions; diff --git a/src/sip/zeroconf/zeroconf.h b/src/sip/zeroconf/zeroconf.h index 8c04fa507..d538f9a67 100644 --- a/src/sip/zeroconf/zeroconf.h +++ b/src/sip/zeroconf/zeroconf.h @@ -52,8 +52,8 @@ public: public slots: virtual bool connectPlugin( bool startup ); - void disconnectPlugin(); + void checkSettings() {} void sendMsg( const QString& to, const QString& msg ) { From 0bf3a1b8e792c0bf729f0ec352481d0d69021cf1 Mon Sep 17 00:00:00 2001 From: Christian Muehlhaeuser Date: Mon, 28 Mar 2011 03:31:53 +0200 Subject: [PATCH 179/329] * Forgot to add one file. --- src/libtomahawk/sip/SipPlugin.h | 1 + 1 file changed, 1 insertion(+) diff --git a/src/libtomahawk/sip/SipPlugin.h b/src/libtomahawk/sip/SipPlugin.h index 4c2b17f1e..47af2fcf3 100644 --- a/src/libtomahawk/sip/SipPlugin.h +++ b/src/libtomahawk/sip/SipPlugin.h @@ -44,6 +44,7 @@ public: public slots: virtual bool connectPlugin( bool startup = false ) = 0; virtual void disconnectPlugin() = 0; + virtual void checkSettings() = 0; virtual void addContact( const QString &jid, const QString& msg = QString() ) = 0; virtual void sendMsg( const QString& to, const QString& msg ) = 0; From 21f49bbfcb012feee0164720460629077ddaab0b Mon Sep 17 00:00:00 2001 From: Christian Muehlhaeuser Date: Mon, 28 Mar 2011 04:01:43 +0200 Subject: [PATCH 180/329] * Updated changelog. --- ChangeLog | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/ChangeLog b/ChangeLog index 57ebcf0d8..45f0b1d5c 100644 --- a/ChangeLog +++ b/ChangeLog @@ -1,4 +1,6 @@ Version 0.0.2: + * Don't reconnect to Jabber if the settings dialog is closed successfully + but the Jabber settings haven't changed. * Don't run a rescan of the local collection if the settings dialog is closed successfully but the path hasn't changed. * Don't attempt to connect to unavailable Twitter peers over and over. @@ -10,9 +12,9 @@ Version 0.0.2: the Tomahawk XMPP presence. * Incompatible change: Twitter SIP protocol has changed slightly. 0.0.1 clients will not be able to talk to newer clients. - * Hopefully fix crashes during Twitter authentication. + * Hopefully fixed crashes during Twitter authentication. * Don't let long playlist or summary names force a large Tomahawk window. - * Fixed Jabber auth: Freshly added contacts never showed up. + * Tomahawk now asks you to authorize new contacts. Version 0.0.1: * First public release. From ecd8b302c44e424d50b912a87abcd4d0ec40e98f Mon Sep 17 00:00:00 2001 From: Leo Franchi Date: Sun, 27 Mar 2011 22:06:55 -0400 Subject: [PATCH 181/329] build liblastfm2 statically on non-win32, and don't build fingerprinter. got multiple reports of tomahawk not finding liblastfm2 due to rpath or other issues, and we are building our o ther 3rdparty deps statically like this. --- thirdparty/liblastfm2/CMakeLists.txt | 2 +- thirdparty/liblastfm2/src/CMakeLists.txt | 29 ++++++++++++++++-------- 2 files changed, 21 insertions(+), 10 deletions(-) diff --git a/thirdparty/liblastfm2/CMakeLists.txt b/thirdparty/liblastfm2/CMakeLists.txt index e8b020b78..b0f71bd5d 100644 --- a/thirdparty/liblastfm2/CMakeLists.txt +++ b/thirdparty/liblastfm2/CMakeLists.txt @@ -17,7 +17,7 @@ include(${QT_USE_FILE}) add_subdirectory(src) # Optionally build the fingerprint library -option(BUILD_FINGERPRINT "Build the lastfm-fingerprint library" ON) +option(BUILD_FINGERPRINT "Build the lastfm-fingerprint library" OFF) find_package(LibSamplerate) find_package(LibFFTW3) diff --git a/thirdparty/liblastfm2/src/CMakeLists.txt b/thirdparty/liblastfm2/src/CMakeLists.txt index a9e3344cb..a9cd2622e 100644 --- a/thirdparty/liblastfm2/src/CMakeLists.txt +++ b/thirdparty/liblastfm2/src/CMakeLists.txt @@ -84,10 +84,18 @@ endif(WIN32) qt4_wrap_cpp(MOC_SOURCES ${MOC_HEADERS}) -add_library(tomahawk_lastfm2 SHARED - ${SOURCES} - ${MOC_SOURCES} -) +IF( WIN32 ) + add_library(tomahawk_lastfm2 SHARED + ${SOURCES} + ${MOC_SOURCES} + ) +ELSE() + add_definitions(-fPIC) + add_library(tomahawk_lastfm2 STATIC + ${SOURCES} + ${MOC_SOURCES} + ) +ENDIF() target_link_libraries(tomahawk_lastfm2 ${QT_LIBRARIES} @@ -103,8 +111,11 @@ if(APPLE) ) endif(APPLE) -install(TARGETS tomahawk_lastfm2 - RUNTIME DESTINATION bin - LIBRARY DESTINATION lib${LIB_SUFFIX} - ARCHIVE DESTINATION lib${LIB_SUFFIX} -) + +IF( WIN32 ) + install(TARGETS tomahawk_lastfm2 + RUNTIME DESTINATION bin + LIBRARY DESTINATION lib${LIB_SUFFIX} + ARCHIVE DESTINATION lib${LIB_SUFFIX} + ) +ENDIF() From e590aa7739be2b7678ce0f17968198f19ca0ec2a Mon Sep 17 00:00:00 2001 From: Jeff Mitchell Date: Sun, 27 Mar 2011 21:47:57 -0400 Subject: [PATCH 182/329] Whitespacing --- include/tomahawk/infosystem.h | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/include/tomahawk/infosystem.h b/include/tomahawk/infosystem.h index 80c400301..7bc098a97 100644 --- a/include/tomahawk/infosystem.h +++ b/include/tomahawk/infosystem.h @@ -101,9 +101,10 @@ class InfoPlugin : public QObject public: InfoPlugin(QObject *parent) :QObject(parent) - { - qDebug() << Q_FUNC_INFO; - } + { + qDebug() << Q_FUNC_INFO; + } + ~InfoPlugin() { qDebug() << Q_FUNC_INFO; From 1cd969af7f51d43d5243efc55c1dad0b9d58dcd8 Mon Sep 17 00:00:00 2001 From: Jeff Mitchell Date: Sun, 27 Mar 2011 22:37:36 -0400 Subject: [PATCH 183/329] Initial baby steps towards the info system cache --- include/tomahawk/infosystem.h | 75 +++++++++++++++--------------- src/CMakeLists.txt | 3 ++ src/infosystem/infosystem.cpp | 56 +++++++++++++++++++++- src/infosystem/infosystemcache.cpp | 0 src/infosystem/infosystemcache.h | 53 +++++++++++++++++++++ 5 files changed, 149 insertions(+), 38 deletions(-) create mode 100644 src/infosystem/infosystemcache.cpp create mode 100644 src/infosystem/infosystemcache.h diff --git a/include/tomahawk/infosystem.h b/include/tomahawk/infosystem.h index 7bc098a97..877345409 100644 --- a/include/tomahawk/infosystem.h +++ b/include/tomahawk/infosystem.h @@ -21,17 +21,19 @@ #include #include -#include -#include -#include -#include -#include - +#include +#include +#include +#include +#include +#include namespace Tomahawk { namespace InfoSystem { +class InfoSystemCache; + enum InfoType { InfoTrackID, InfoTrackArtist, @@ -91,30 +93,30 @@ enum InfoType { typedef QMap< InfoType, QVariant > InfoMap; typedef QMap< QString, QMap< QString, QString > > InfoGenericMap; -typedef QHash InfoCustomDataHash; -typedef QHash MusixMatchHash; +typedef QHash< QString, QVariant > InfoCustomDataHash; +typedef QHash< QString, QString > MusixMatchHash; class InfoPlugin : public QObject { Q_OBJECT public: - InfoPlugin(QObject *parent) - :QObject(parent) - { - qDebug() << Q_FUNC_INFO; - } + InfoPlugin( QObject *parent ); - ~InfoPlugin() + virtual ~InfoPlugin() { qDebug() << Q_FUNC_INFO; } - virtual void getInfo(const QString &caller, const InfoType type, const QVariant &data, Tomahawk::InfoSystem::InfoCustomDataHash customData) = 0; + virtual void getInfo( const QString &caller, const InfoType type, const QVariant &data, Tomahawk::InfoSystem::InfoCustomDataHash customData ) = 0; signals: - void info(QString caller, Tomahawk::InfoSystem::InfoType type, QVariant input, QVariant output, Tomahawk::InfoSystem::InfoCustomDataHash customData); - void finished(QString, Tomahawk::InfoSystem::InfoType); + void info( QString caller, Tomahawk::InfoSystem::InfoType type, QVariant input, QVariant output, Tomahawk::InfoSystem::InfoCustomDataHash customData ); + void getCachedInfo( QHash< QString, QString > criteria, QString caller, Tomahawk::InfoSystem::InfoType type, QVariant input, QVariant output, Tomahawk::InfoSystem::InfoCustomDataHash customData ); + void finished( QString, Tomahawk::InfoSystem::InfoType ); + +//public slots: + //void notInCacheSlot( QString caller, Tomahawk::InfoSystem::InfoType type, QVariant input, QVariant output, Tomahawk::InfoSystem::InfoCustomDataHash customData ) = 0; protected: InfoType m_type; @@ -127,46 +129,45 @@ class InfoSystem : public QObject Q_OBJECT public: + + InfoSystem( QObject *parent ); + ~InfoSystem(); + void registerInfoTypes( const InfoPluginPtr &plugin, const QSet< InfoType > &types ); - InfoSystem(QObject *parent); - ~InfoSystem() - { - qDebug() << Q_FUNC_INFO; - } + void getInfo( const QString &caller, const InfoType type, const QVariant &data, InfoCustomDataHash customData ); + void getInfo( const QString &caller, const InfoMap &input, InfoCustomDataHash customData ); - void registerInfoTypes(const InfoPluginPtr &plugin, const QSet< InfoType > &types); - - void getInfo(const QString &caller, const InfoType type, const QVariant &data, InfoCustomDataHash customData); - void getInfo(const QString &caller, const InfoMap &input, InfoCustomDataHash customData); + InfoSystemCache* getCache() { return m_cache; } signals: - void info(QString caller, Tomahawk::InfoSystem::InfoType, QVariant input, QVariant output, Tomahawk::InfoSystem::InfoCustomDataHash customData); - void finished(QString target); + void info( QString caller, Tomahawk::InfoSystem::InfoType, QVariant input, QVariant output, Tomahawk::InfoSystem::InfoCustomDataHash customData ); + void finished( QString target ); public slots: - void infoSlot(QString target, Tomahawk::InfoSystem::InfoType type, QVariant input, QVariant output, Tomahawk::InfoSystem::InfoCustomDataHash customData); - void finishedSlot(QString target,Tomahawk::InfoSystem::InfoType type); + void infoSlot( QString target, Tomahawk::InfoSystem::InfoType type, QVariant input, QVariant output, Tomahawk::InfoSystem::InfoCustomDataHash customData ); + void finishedSlot( QString target,Tomahawk::InfoSystem::InfoType type); private: - - QLinkedList< InfoPluginPtr > determineOrderedMatches(const InfoType type) const; + QLinkedList< InfoPluginPtr > determineOrderedMatches( const InfoType type ) const; - QMap< InfoType, QLinkedList > m_infoMap; + QMap< InfoType, QLinkedList< InfoPluginPtr > > m_infoMap; // For now, statically instantiate plugins; this is just somewhere to keep them - QLinkedList m_plugins; + QLinkedList< InfoPluginPtr > m_plugins; QHash< QString, QHash< Tomahawk::InfoSystem::InfoType, int > > m_dataTracker; + InfoSystemCache* m_cache; + QThread* m_infoSystemCacheThreadController; }; } } -Q_DECLARE_METATYPE(Tomahawk::InfoSystem::InfoGenericMap) -Q_DECLARE_METATYPE(Tomahawk::InfoSystem::InfoCustomDataHash); -Q_DECLARE_METATYPE(Tomahawk::InfoSystem::MusixMatchHash) +Q_DECLARE_METATYPE( Tomahawk::InfoSystem::InfoGenericMap ) +Q_DECLARE_METATYPE( Tomahawk::InfoSystem::InfoCustomDataHash ); +Q_DECLARE_METATYPE( Tomahawk::InfoSystem::MusixMatchHash ) #endif // TOMAHAWK_INFOSYSTEM_H diff --git a/src/CMakeLists.txt b/src/CMakeLists.txt index 271c84211..259756b05 100644 --- a/src/CMakeLists.txt +++ b/src/CMakeLists.txt @@ -34,6 +34,7 @@ ENDIF() SET( tomahawkSources ${tomahawkSources} sip/SipHandler.cpp + infosystem/infosystemcache.cpp infosystem/infosystem.cpp infosystem/infoplugins/echonestplugin.cpp infosystem/infoplugins/lastfmplugin.cpp @@ -77,6 +78,7 @@ SET( tomahawkHeaders ${tomahawkHeaders} sip/SipHandler.h + infosystem/infosystemcache.h infosystem/infoplugins/echonestplugin.h infosystem/infoplugins/lastfmplugin.h infosystem/infoplugins/musixmatchplugin.h @@ -136,6 +138,7 @@ INCLUDE_DIRECTORIES( utils libtomahawk libtomahawk/utils + infosystem mac ${THIRDPARTY_DIR}/alsa-playback diff --git a/src/infosystem/infosystem.cpp b/src/infosystem/infosystem.cpp index 893c78162..dd8862582 100644 --- a/src/infosystem/infosystem.cpp +++ b/src/infosystem/infosystem.cpp @@ -16,20 +16,44 @@ * along with Tomahawk. If not, see . */ +#include + #include "tomahawk/infosystem.h" +#include "tomahawkutils.h" +#include "infosystemcache.h" #include "infoplugins/echonestplugin.h" #include "infoplugins/musixmatchplugin.h" #include "infoplugins/lastfmplugin.h" using namespace Tomahawk::InfoSystem; +InfoPlugin::InfoPlugin(QObject *parent) + :QObject( parent ) + { + qDebug() << Q_FUNC_INFO; + InfoSystem *system = qobject_cast< InfoSystem* >( parent ); + if( system ) + QObject::connect( system->getCache(), + SIGNAL( notInCache( QString, Tomahawk::InfoSystem::InfoType, QVariant, QVariant, Tomahawk::InfoSystem::InfoCustomDataHash ) ), + this, + SLOT( notInCacheSlot( QString, Tomahawk::InfoSystem::InfoType, QVariant, QVariant, Tomahawk::InfoSystem::InfoCustomDataHash ) ) + ); + } + + InfoSystem::InfoSystem(QObject *parent) - : QObject( parent ) + : QObject(parent) { qDebug() << Q_FUNC_INFO; qRegisterMetaType > >("Tomahawk::InfoSystem::InfoGenericMap"); qRegisterMetaType >("Tomahawk::InfoSystem::InfoCustomDataHash"); qRegisterMetaType >("Tomahawk::InfoSystem::MusixMatchHash"); + + m_infoSystemCacheThreadController = new QThread( this ); + m_cache = new Tomahawk::InfoSystem::InfoSystemCache(); + m_cache->moveToThread( m_infoSystemCacheThreadController ); + m_infoSystemCacheThreadController->start( QThread::IdlePriority ); + InfoPluginPtr enptr(new EchoNestPlugin(this)); m_plugins.append(enptr); InfoPluginPtr mmptr(new MusixMatchPlugin(this)); @@ -38,6 +62,36 @@ InfoSystem::InfoSystem(QObject *parent) m_plugins.append(lfmptr); } +InfoSystem::~InfoSystem() +{ + qDebug() << Q_FUNC_INFO; + Q_FOREACH( InfoPluginPtr plugin, m_plugins ) + { + if( plugin ) + delete plugin.data(); + } + + if( m_infoSystemCacheThreadController ) + { + m_infoSystemCacheThreadController->quit(); + + while( !m_infoSystemCacheThreadController->isFinished() ) + { + QCoreApplication::processEvents( QEventLoop::AllEvents, 200 ); + TomahawkUtils::Sleep::msleep( 100 ); + } + + if( m_cache ) + { + delete m_cache; + m_cache = 0; + } + + delete m_infoSystemCacheThreadController; + m_infoSystemCacheThreadController = 0; + } +} + void InfoSystem::registerInfoTypes(const InfoPluginPtr &plugin, const QSet< InfoType >& types) { qDebug() << Q_FUNC_INFO; diff --git a/src/infosystem/infosystemcache.cpp b/src/infosystem/infosystemcache.cpp new file mode 100644 index 000000000..e69de29bb diff --git a/src/infosystem/infosystemcache.h b/src/infosystem/infosystemcache.h new file mode 100644 index 000000000..97990b7a1 --- /dev/null +++ b/src/infosystem/infosystemcache.h @@ -0,0 +1,53 @@ +/* === This file is part of Tomahawk Player - === + * + * Copyright 2010-2011, Christian Muehlhaeuser + * + * Tomahawk is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * Tomahawk is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with Tomahawk. If not, see . + */ + +#ifndef TOMAHAWK_INFOSYSTEMCACHE_H +#define TOMAHAWK_INFOSYSTEMCACHE_H + +#include +#include + +namespace Tomahawk +{ + +namespace InfoSystem +{ + +class InfoSystemCache : public QObject +{ +Q_OBJECT + +public: + InfoSystemCache( QObject *parent = 0 ) + : QObject( parent ) + { + qDebug() << Q_FUNC_INFO; + } + + virtual ~InfoSystemCache() + { + qDebug() << Q_FUNC_INFO; + } + +}; + +} //namespace InfoSystem + +} //namespace Tomahawk + +#endif //TOMAHAWK_INFOSYSTEMCACHE_H \ No newline at end of file From 3f1274b90a158f4cb56887121e01a926b52a9175 Mon Sep 17 00:00:00 2001 From: Jeff Mitchell Date: Sun, 27 Mar 2011 22:45:14 -0400 Subject: [PATCH 184/329] Recovering from faulty merge... Revert "* Forgot to add one file." This reverts commit 0bf3a1b8e792c0bf729f0ec352481d0d69021cf1. --- src/libtomahawk/sip/SipPlugin.h | 1 - 1 file changed, 1 deletion(-) diff --git a/src/libtomahawk/sip/SipPlugin.h b/src/libtomahawk/sip/SipPlugin.h index 47af2fcf3..4c2b17f1e 100644 --- a/src/libtomahawk/sip/SipPlugin.h +++ b/src/libtomahawk/sip/SipPlugin.h @@ -44,7 +44,6 @@ public: public slots: virtual bool connectPlugin( bool startup = false ) = 0; virtual void disconnectPlugin() = 0; - virtual void checkSettings() = 0; virtual void addContact( const QString &jid, const QString& msg = QString() ) = 0; virtual void sendMsg( const QString& to, const QString& msg ) = 0; From 5a0807974017c2e06f8c78f3a9c5b8140130eaf8 Mon Sep 17 00:00:00 2001 From: Jeff Mitchell Date: Sun, 27 Mar 2011 22:45:31 -0400 Subject: [PATCH 185/329] Recovering from faulty merge... Revert "* Fixed SIP plugins always reconnecting when the SettingsDialog was closed." This reverts commit 057825bf970dbb9021d2ae46866dcfd49bde532d. --- src/sip/SipHandler.cpp | 28 ++++++++++++++---------- src/sip/SipHandler.h | 1 - src/sip/jabber/jabber.cpp | 43 ++++++------------------------------- src/sip/jabber/jabber.h | 11 ++-------- src/sip/jabber/jabber_p.cpp | 2 +- src/sip/twitter/twitter.cpp | 23 +++++++++++--------- src/sip/twitter/twitter.h | 3 ++- src/sip/zeroconf/zeroconf.h | 2 +- 8 files changed, 43 insertions(+), 70 deletions(-) diff --git a/src/sip/SipHandler.cpp b/src/sip/SipHandler.cpp index 7d77b5fc7..d09fe9d7e 100644 --- a/src/sip/SipHandler.cpp +++ b/src/sip/SipHandler.cpp @@ -28,6 +28,7 @@ #include "network/controlconnection.h" #include "sourcelist.h" #include "tomahawksettings.h" +#include "tomahawk/tomahawkapp.h" #include "config.h" @@ -58,7 +59,8 @@ SipHandler::plugins() const void SipHandler::onSettingsChanged() { - checkSettings(); + disconnectPlugins(); + connectPlugins(); } @@ -164,19 +166,23 @@ SipHandler::pluginLoaded( const QString& name ) const } -void -SipHandler::checkSettings() -{ - foreach( SipPlugin* sip, m_plugins ) - { - sip->checkSettings(); - } -} - - void SipHandler::connectPlugins( bool startup, const QString &pluginName ) { +#ifndef TOMAHAWK_HEADLESS + if ( !TomahawkSettings::instance()->acceptedLegalWarning() ) + { + int result = QMessageBox::question( + TomahawkApp::instance()->mainWindow(), "Legal Warning", + "By pressing OK below, you agree that your use of Tomahawk will be in accordance with any applicable laws, including copyright and intellectual property laws, in effect in your country of residence, and indemify the Tomahawk developers and project from liability should you choose to break those laws.\n\nFor more information, please see http://gettomahawk.com/legal", + "I Do Not Agree", "I Agree" + ); + if ( result != 1 ) + return; + else + TomahawkSettings::instance()->setAcceptedLegalWarning( true ); + } +#endif foreach( SipPlugin* sip, m_plugins ) { if ( pluginName.isEmpty() || ( !pluginName.isEmpty() && sip->name() == pluginName ) ) diff --git a/src/sip/SipHandler.h b/src/sip/SipHandler.h index 0d3dcfa63..f127dcc16 100644 --- a/src/sip/SipHandler.h +++ b/src/sip/SipHandler.h @@ -39,7 +39,6 @@ public: public slots: void addContact( const QString& id ) { qDebug() << Q_FUNC_INFO << id; } - void checkSettings(); void connectPlugins( bool startup = false, const QString &pluginName = QString() ); void disconnectPlugins( const QString &pluginName = QString() ); void toggleConnect(); diff --git a/src/sip/jabber/jabber.cpp b/src/sip/jabber/jabber.cpp index 05a49141e..761335d5f 100644 --- a/src/sip/jabber/jabber.cpp +++ b/src/sip/jabber/jabber.cpp @@ -76,14 +76,12 @@ JabberPlugin::connectPlugin( bool startup ) if ( startup && !TomahawkSettings::instance()->jabberAutoConnect() ) return false; - m_currentUsername = TomahawkSettings::instance()->jabberUsername(); - m_currentPassword = TomahawkSettings::instance()->jabberPassword(); - m_currentServer = TomahawkSettings::instance()->jabberServer(); - m_currentPort = TomahawkSettings::instance()->jabberPort(); + QString jid = TomahawkSettings::instance()->jabberUsername(); + QString server = TomahawkSettings::instance()->jabberServer(); + QString password = TomahawkSettings::instance()->jabberPassword(); + unsigned int port = TomahawkSettings::instance()->jabberPort(); - QString server = m_currentServer; - - QStringList splitJid = m_currentUsername.split( '@', QString::SkipEmptyParts ); + QStringList splitJid = jid.split( '@', QString::SkipEmptyParts ); if ( splitJid.size() < 2 ) { qDebug() << "JID did not have an @ in it, could not find a server part"; @@ -93,14 +91,14 @@ JabberPlugin::connectPlugin( bool startup ) if ( server.isEmpty() ) server = splitJid[1]; - if ( m_currentPort < 1 || m_currentPort > 65535 || m_currentUsername.isEmpty() || m_currentPassword.isEmpty() ) + if ( port < 1 || port > 65535 || jid.isEmpty() || password.isEmpty() ) { qDebug() << "Jabber credentials look wrong, not connecting"; return false; } delete p; - p = new Jabber_p( m_currentUsername, m_currentPassword, server, m_currentPort ); + p = new Jabber_p( jid, password, server, port ); QObject::connect( p, SIGNAL( peerOnline( QString ) ), SIGNAL( peerOnline( QString ) ) ); QObject::connect( p, SIGNAL( peerOffline( QString ) ), SIGNAL( peerOffline( QString ) ) ); @@ -176,31 +174,4 @@ JabberPlugin::showAddFriendDialog() addContact( id ); } - -void -JabberPlugin::checkSettings() -{ - bool reconnect = false; - - if ( m_currentUsername != TomahawkSettings::instance()->jabberUsername() ) - reconnect = true; - if ( m_currentPassword != TomahawkSettings::instance()->jabberPassword() ) - reconnect = true; - if ( m_currentServer != TomahawkSettings::instance()->jabberServer() ) - reconnect = true; - if ( m_currentPort != TomahawkSettings::instance()->jabberPort() ) - reconnect = true; - - m_currentUsername = TomahawkSettings::instance()->jabberUsername(); - m_currentPassword = TomahawkSettings::instance()->jabberPassword(); - m_currentServer = TomahawkSettings::instance()->jabberServer(); - m_currentPort = TomahawkSettings::instance()->jabberPort(); - - if ( reconnect && ( p || TomahawkSettings::instance()->jabberAutoConnect() ) ) - { - disconnectPlugin(); - connectPlugin( false ); - } -} - Q_EXPORT_PLUGIN2( sip, JabberPlugin ) diff --git a/src/sip/jabber/jabber.h b/src/sip/jabber/jabber.h index a5bd18a9b..c702fed71 100644 --- a/src/sip/jabber/jabber.h +++ b/src/sip/jabber/jabber.h @@ -51,7 +51,7 @@ public slots: void disconnectPlugin() { onDisconnected(); - + if ( p ) p->disconnect(); @@ -59,8 +59,6 @@ public slots: p = 0; } - void checkSettings(); - void sendMsg( const QString& to, const QString& msg ) { if ( p ) @@ -84,16 +82,11 @@ private slots: void showAddFriendDialog(); void onConnected(); void onDisconnected(); - + private: Jabber_p* p; QMenu* m_menu; QAction* m_addFriendAction; - - QString m_currentServer; - QString m_currentUsername; - QString m_currentPassword; - unsigned int m_currentPort; }; #endif diff --git a/src/sip/jabber/jabber_p.cpp b/src/sip/jabber/jabber_p.cpp index 8513ef144..4425b1a93 100644 --- a/src/sip/jabber/jabber_p.cpp +++ b/src/sip/jabber/jabber_p.cpp @@ -27,8 +27,8 @@ #include #include #include -#include #include +#include using namespace gloox; using namespace std; diff --git a/src/sip/twitter/twitter.cpp b/src/sip/twitter/twitter.cpp index d7e0d8464..ba8b3f1ef 100644 --- a/src/sip/twitter/twitter.cpp +++ b/src/sip/twitter/twitter.cpp @@ -168,6 +168,7 @@ TwitterPlugin::disconnectPlugin() m_twitterAuth.data()->deleteLater(); m_cachedPeers.empty(); + m_attemptedConnects.empty(); delete m_twitterAuth.data(); m_isOnline = false; } @@ -583,14 +584,16 @@ TwitterPlugin::registerOffer( const QString &screenName, const QHash< QString, Q qDebug() << "TwitterPlugin did not send offer because external address is " << Servent::instance()->externalAddress() << " and external port is " << Servent::instance()->externalPort(); } - if ( m_isOnline && _peerData.contains( "host" ) && _peerData.contains( "port" ) && _peerData.contains( "pkey" ) ) - QMetaObject::invokeMethod( this, "makeConnection", Q_ARG( QString, screenName ), QGenericArgument( "QHash< QString, QVariant >", (const void*)&_peerData ) ); - if ( peersChanged ) { m_cachedPeers[screenName] = QVariant::fromValue< QHash< QString, QVariant > >( _peerData ); TomahawkSettings::instance()->setTwitterCachedPeers( m_cachedPeers ); + m_attemptedConnects[screenName] = false; } + + if ( m_isOnline && _peerData.contains( "host" ) && _peerData.contains( "port" ) && _peerData.contains( "pkey" ) ) + QMetaObject::invokeMethod( this, "makeConnection", Q_ARG( QString, screenName ), QGenericArgument( "QHash< QString, QVariant >", (const void*)&_peerData ) ); + } void @@ -611,6 +614,11 @@ void TwitterPlugin::makeConnection( const QString &screenName, const QHash< QString, QVariant > &peerData ) { qDebug() << Q_FUNC_INFO; + if ( m_attemptedConnects.contains( screenName ) && m_attemptedConnects[screenName] ) + { + qDebug() << "Already attempted to connect to this peer with no change in their status, not trying again for now"; + return; + } if ( !peerData.contains( "host" ) || !peerData.contains( "port" ) || !peerData.contains( "pkey" ) || !peerData.contains( "node" ) ) { qDebug() << "TwitterPlugin could not find host and/or port and/or pkey for peer " << screenName; @@ -623,6 +631,7 @@ TwitterPlugin::makeConnection( const QString &screenName, const QHash< QString, peerData["pkey"].toString(), friendlyName, peerData["node"].toString() ); + m_attemptedConnects[screenName] = true; } void @@ -630,6 +639,7 @@ TwitterPlugin::directMessagePosted( const QTweetDMStatus& message ) { qDebug() << Q_FUNC_INFO; qDebug() << "TwitterPlugin sent message to " << message.recipientScreenName() << " containing: " << message.text(); + } void @@ -646,11 +656,4 @@ TwitterPlugin::directMessageDestroyed( const QTweetDMStatus& message ) qDebug() << "TwitterPlugin destroyed message " << message.text(); } -void -TwitterPlugin::checkSettings() -{ - disconnectPlugin(); - connectPlugin( false ); -} - Q_EXPORT_PLUGIN2( sip, TwitterPlugin ) diff --git a/src/sip/twitter/twitter.h b/src/sip/twitter/twitter.h index c0da00d7f..b0dc213b3 100644 --- a/src/sip/twitter/twitter.h +++ b/src/sip/twitter/twitter.h @@ -59,8 +59,8 @@ public: public slots: virtual bool connectPlugin( bool startup ); + void disconnectPlugin(); - void checkSettings(); void sendMsg( const QString& to, const QString& msg ) { @@ -108,6 +108,7 @@ private: qint64 m_cachedMentionsSinceId; qint64 m_cachedDirectMessagesSinceId; QHash< QString, QVariant > m_cachedPeers; + QHash< QString, bool > m_attemptedConnects; QSet m_keyCache; bool m_finishedFriends; bool m_finishedMentions; diff --git a/src/sip/zeroconf/zeroconf.h b/src/sip/zeroconf/zeroconf.h index d538f9a67..8c04fa507 100644 --- a/src/sip/zeroconf/zeroconf.h +++ b/src/sip/zeroconf/zeroconf.h @@ -52,8 +52,8 @@ public: public slots: virtual bool connectPlugin( bool startup ); + void disconnectPlugin(); - void checkSettings() {} void sendMsg( const QString& to, const QString& msg ) { From 6a2c2b147d3a15eb6a42b6b1f8e0471ca90034eb Mon Sep 17 00:00:00 2001 From: Christian Muehlhaeuser Date: Mon, 28 Mar 2011 05:01:28 +0200 Subject: [PATCH 186/329] * Merged back my fixes. --- src/libtomahawk/sip/SipPlugin.h | 1 + src/sip/SipHandler.cpp | 13 +++++++++-- src/sip/SipHandler.h | 1 + src/sip/jabber/jabber.cpp | 41 +++++++++++++++++++++++++++------ src/sip/jabber/jabber.h | 7 ++++++ src/sip/twitter/twitter.cpp | 7 ++++++ src/sip/twitter/twitter.h | 2 +- src/sip/zeroconf/zeroconf.h | 2 +- 8 files changed, 63 insertions(+), 11 deletions(-) diff --git a/src/libtomahawk/sip/SipPlugin.h b/src/libtomahawk/sip/SipPlugin.h index 4c2b17f1e..47af2fcf3 100644 --- a/src/libtomahawk/sip/SipPlugin.h +++ b/src/libtomahawk/sip/SipPlugin.h @@ -44,6 +44,7 @@ public: public slots: virtual bool connectPlugin( bool startup = false ) = 0; virtual void disconnectPlugin() = 0; + virtual void checkSettings() = 0; virtual void addContact( const QString &jid, const QString& msg = QString() ) = 0; virtual void sendMsg( const QString& to, const QString& msg ) = 0; diff --git a/src/sip/SipHandler.cpp b/src/sip/SipHandler.cpp index d09fe9d7e..a8284a93e 100644 --- a/src/sip/SipHandler.cpp +++ b/src/sip/SipHandler.cpp @@ -59,8 +59,7 @@ SipHandler::plugins() const void SipHandler::onSettingsChanged() { - disconnectPlugins(); - connectPlugins(); + checkSettings(); } @@ -166,6 +165,16 @@ SipHandler::pluginLoaded( const QString& name ) const } +void +SipHandler::checkSettings() +{ + foreach( SipPlugin* sip, m_plugins ) + { + sip->checkSettings(); + } +} + + void SipHandler::connectPlugins( bool startup, const QString &pluginName ) { diff --git a/src/sip/SipHandler.h b/src/sip/SipHandler.h index f127dcc16..0d3dcfa63 100644 --- a/src/sip/SipHandler.h +++ b/src/sip/SipHandler.h @@ -39,6 +39,7 @@ public: public slots: void addContact( const QString& id ) { qDebug() << Q_FUNC_INFO << id; } + void checkSettings(); void connectPlugins( bool startup = false, const QString &pluginName = QString() ); void disconnectPlugins( const QString &pluginName = QString() ); void toggleConnect(); diff --git a/src/sip/jabber/jabber.cpp b/src/sip/jabber/jabber.cpp index 761335d5f..ce62909c4 100644 --- a/src/sip/jabber/jabber.cpp +++ b/src/sip/jabber/jabber.cpp @@ -76,12 +76,13 @@ JabberPlugin::connectPlugin( bool startup ) if ( startup && !TomahawkSettings::instance()->jabberAutoConnect() ) return false; - QString jid = TomahawkSettings::instance()->jabberUsername(); - QString server = TomahawkSettings::instance()->jabberServer(); - QString password = TomahawkSettings::instance()->jabberPassword(); - unsigned int port = TomahawkSettings::instance()->jabberPort(); + m_currentUsername = TomahawkSettings::instance()->jabberUsername(); + m_currentServer = TomahawkSettings::instance()->jabberServer(); + m_currentPassword = TomahawkSettings::instance()->jabberPassword(); + m_currentPort = TomahawkSettings::instance()->jabberPort(); + QString server = m_currentServer; - QStringList splitJid = jid.split( '@', QString::SkipEmptyParts ); + QStringList splitJid = m_currentUsername.split( '@', QString::SkipEmptyParts ); if ( splitJid.size() < 2 ) { qDebug() << "JID did not have an @ in it, could not find a server part"; @@ -91,14 +92,14 @@ JabberPlugin::connectPlugin( bool startup ) if ( server.isEmpty() ) server = splitJid[1]; - if ( port < 1 || port > 65535 || jid.isEmpty() || password.isEmpty() ) + if ( m_currentPort < 1 || m_currentPort > 65535 || m_currentUsername.isEmpty() || m_currentPassword.isEmpty() ) { qDebug() << "Jabber credentials look wrong, not connecting"; return false; } delete p; - p = new Jabber_p( jid, password, server, port ); + p = new Jabber_p( m_currentUsername, m_currentPassword, server, m_currentPort ); QObject::connect( p, SIGNAL( peerOnline( QString ) ), SIGNAL( peerOnline( QString ) ) ); QObject::connect( p, SIGNAL( peerOffline( QString ) ), SIGNAL( peerOffline( QString ) ) ); @@ -174,4 +175,30 @@ JabberPlugin::showAddFriendDialog() addContact( id ); } + +void +JabberPlugin::checkSettings() +{ + bool reconnect = false; + if ( m_currentUsername != TomahawkSettings::instance()->jabberUsername() ) + reconnect = true; + if ( m_currentPassword != TomahawkSettings::instance()->jabberPassword() ) + reconnect = true; + if ( m_currentServer != TomahawkSettings::instance()->jabberServer() ) + reconnect = true; + if ( m_currentPort != TomahawkSettings::instance()->jabberPort() ) + reconnect = true; + + m_currentUsername = TomahawkSettings::instance()->jabberUsername(); + m_currentPassword = TomahawkSettings::instance()->jabberPassword(); + m_currentServer = TomahawkSettings::instance()->jabberServer(); + m_currentPort = TomahawkSettings::instance()->jabberPort(); + + if ( reconnect && ( p || TomahawkSettings::instance()->jabberAutoConnect() ) ) + { + disconnectPlugin(); + connectPlugin( false ); + } +} + Q_EXPORT_PLUGIN2( sip, JabberPlugin ) diff --git a/src/sip/jabber/jabber.h b/src/sip/jabber/jabber.h index c702fed71..b3d868896 100644 --- a/src/sip/jabber/jabber.h +++ b/src/sip/jabber/jabber.h @@ -59,6 +59,8 @@ public slots: p = 0; } + void checkSettings(); + void sendMsg( const QString& to, const QString& msg ) { if ( p ) @@ -87,6 +89,11 @@ private: Jabber_p* p; QMenu* m_menu; QAction* m_addFriendAction; + + QString m_currentUsername; + QString m_currentPassword; + QString m_currentServer; + unsigned int m_currentPort; }; #endif diff --git a/src/sip/twitter/twitter.cpp b/src/sip/twitter/twitter.cpp index ba8b3f1ef..843de4505 100644 --- a/src/sip/twitter/twitter.cpp +++ b/src/sip/twitter/twitter.cpp @@ -656,4 +656,11 @@ TwitterPlugin::directMessageDestroyed( const QTweetDMStatus& message ) qDebug() << "TwitterPlugin destroyed message " << message.text(); } +void +TwitterPlugin::checkSettings() +{ + disconnectPlugin(); + connectPlugin( false ); +} + Q_EXPORT_PLUGIN2( sip, TwitterPlugin ) diff --git a/src/sip/twitter/twitter.h b/src/sip/twitter/twitter.h index b0dc213b3..4e8a98a31 100644 --- a/src/sip/twitter/twitter.h +++ b/src/sip/twitter/twitter.h @@ -59,8 +59,8 @@ public: public slots: virtual bool connectPlugin( bool startup ); - void disconnectPlugin(); + void checkSettings(); void sendMsg( const QString& to, const QString& msg ) { diff --git a/src/sip/zeroconf/zeroconf.h b/src/sip/zeroconf/zeroconf.h index 8c04fa507..d538f9a67 100644 --- a/src/sip/zeroconf/zeroconf.h +++ b/src/sip/zeroconf/zeroconf.h @@ -52,8 +52,8 @@ public: public slots: virtual bool connectPlugin( bool startup ); - void disconnectPlugin(); + void checkSettings() {} void sendMsg( const QString& to, const QString& msg ) { From 6c49b2683e6254db487a3995bb84c0f51c78889d Mon Sep 17 00:00:00 2001 From: Christian Muehlhaeuser Date: Mon, 28 Mar 2011 05:27:55 +0200 Subject: [PATCH 187/329] * Changed win32-sparkle behaviour: It'll now look for beta versions if you run Tomahawk with --debug. --- src/tomahawkwindow.cpp | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/src/tomahawkwindow.cpp b/src/tomahawkwindow.cpp index cc6ea8584..868f1609b 100644 --- a/src/tomahawkwindow.cpp +++ b/src/tomahawkwindow.cpp @@ -152,11 +152,12 @@ TomahawkWindow::TomahawkWindow( QWidget* parent ) connect(checkForUpdates, SIGNAL( triggered( bool ) ), SLOT( checkForUpdates() ) ); #elif defined( WIN32 ) QUrl updaterUrl; - #ifdef DEBUG_BUILD + + if ( qApp->arguments().contains( "--debug" ) ) updaterUrl.setUrl( "http://download.tomahawk-player.org/sparklewin-debug" ); - #else + else updaterUrl.setUrl( "http://download.tomahawk-player.org/sparklewin" ); - #endif + qtsparkle::Updater* updater = new qtsparkle::Updater( updaterUrl, this ); updater->SetNetworkAccessManager( TomahawkUtils::nam() ); updater->SetVersion( VERSION ); From 4d23dea89109de2cb0770b0242f6d1c56e1a5ee1 Mon Sep 17 00:00:00 2001 From: Christian Muehlhaeuser Date: Mon, 28 Mar 2011 06:52:57 +0200 Subject: [PATCH 188/329] * Updated OS X sparkle templates. --- admin/mac/Info.plist | 4 ++-- admin/mac/sparkle-beta.rss | 12 ++++++++++-- admin/mac/sparkle.rss | 8 ++++++++ 3 files changed, 20 insertions(+), 4 deletions(-) diff --git a/admin/mac/Info.plist b/admin/mac/Info.plist index e14ff442a..9acc67a0c 100644 --- a/admin/mac/Info.plist +++ b/admin/mac/Info.plist @@ -13,9 +13,9 @@ CFBundlePackageType APPL CFBundleVersion - 0.0.1.0 + 0.0.2.0 CFBundleShortVersionString - 0.0.1 + 0.0.2 CFBundleSignature tomahawk CFBundleIconFile diff --git a/admin/mac/sparkle-beta.rss b/admin/mac/sparkle-beta.rss index e49379f98..124746590 100755 --- a/admin/mac/sparkle-beta.rss +++ b/admin/mac/sparkle-beta.rss @@ -8,10 +8,18 @@ Version 0.0.1 (Tomahawk Player Beta - It Lives?) - https://github.com/tomahawk-player/tomahawk/raw/stable/ChangeLog + https://github.com/tomahawk-player/tomahawk/raw/0.0.1/ChangeLog Fri, 25 Mar 2011 00:00:01 +0100 - + + + + Version 0.0.2 (Tomahawk Player Beta - It Lives?) + + https://github.com/tomahawk-player/tomahawk/raw/stable/ChangeLog + + Mon, 28 Mar 2011 06:13:01 +0100 + diff --git a/admin/mac/sparkle.rss b/admin/mac/sparkle.rss index 9fa00ed8e..7e98e4568 100755 --- a/admin/mac/sparkle.rss +++ b/admin/mac/sparkle.rss @@ -13,5 +13,13 @@ Fri, 25 Mar 2011 00:00:01 +0100
+ + Version 0.0.2 (Tomahawk Player - It Lives!) + + https://github.com/tomahawk-player/tomahawk/raw/stable/ChangeLog + + Mon, 28 Mar 2011 06:13:01 +0100 + + From e24aea480ebc0aefc2dc96864a13260e905c020d Mon Sep 17 00:00:00 2001 From: Christian Muehlhaeuser Date: Mon, 28 Mar 2011 07:13:20 +0200 Subject: [PATCH 189/329] * Cleaned up Jabber auth. --- src/sip/jabber/jabber_p.cpp | 25 ++++++++++++------------- src/sip/jabber/jabber_p.h | 2 +- 2 files changed, 13 insertions(+), 14 deletions(-) diff --git a/src/sip/jabber/jabber_p.cpp b/src/sip/jabber/jabber_p.cpp index 4425b1a93..462296682 100644 --- a/src/sip/jabber/jabber_p.cpp +++ b/src/sip/jabber/jabber_p.cpp @@ -638,9 +638,9 @@ Jabber_p::handleSubscriptionRequest( const JID& jid, const std::string& /*msg*/ // preparing the confirm box for the user QMessageBox *confirmBox = new QMessageBox( QMessageBox::Question, - tr("Friend Request in Jabber"), - QString(tr("Do you want to be friends with %1?")).arg(QLatin1String(jid.bare().c_str())), - QMessageBox::Ok | QMessageBox::Cancel, + tr( "Authorize User" ), + QString( tr( "Do you want to grant %1 access to your Collection?" ) ).arg( QLatin1String( jid.bare().c_str() ) ), + QMessageBox::Yes | QMessageBox::No, 0 ); @@ -653,14 +653,16 @@ Jabber_p::handleSubscriptionRequest( const JID& jid, const std::string& /*msg*/ return false; } + void -Jabber_p::onSubscriptionRequestConfirmed(int result) +Jabber_p::onSubscriptionRequestConfirmed( int result ) { qDebug() << Q_FUNC_INFO << result; QList< QMessageBox* > confirmBoxes = m_subscriptionConfirmBoxes.values(); JID jid; - foreach(QMessageBox* currentBox, confirmBoxes) + + foreach( QMessageBox* currentBox, confirmBoxes ) { if( currentBox == sender() ) { @@ -674,27 +676,24 @@ Jabber_p::onSubscriptionRequestConfirmed(int result) m_subscriptionConfirmBoxes.remove( jid ); sender()->deleteLater(); - QMessageBox::StandardButton allowSubscription = static_cast( result ); + QMessageBox::StandardButton allowSubscription = static_cast( result ); - if(allowSubscription == QMessageBox::Ok) + if ( allowSubscription == QMessageBox::Yes ) { qDebug() << Q_FUNC_INFO << jid.bare().c_str() << "accepted by user, adding to roster"; StringList groups; groups.push_back( "Tomahawk" ); m_client->rosterManager()->subscribe( jid, "", groups, "" ); - - // ack the request - m_client->rosterManager()->ackSubscriptionRequest( jid, true ); } else { qDebug() << Q_FUNC_INFO << jid.bare().c_str() << "declined by user"; - - // decl the request - m_client->rosterManager()->ackSubscriptionRequest( jid, false ); } + + m_client->rosterManager()->ackSubscriptionRequest( jid, allowSubscription == QMessageBox::Yes ); } + bool Jabber_p::handleUnsubscriptionRequest( const JID& jid, const std::string& /*msg*/ ) { diff --git a/src/sip/jabber/jabber_p.h b/src/sip/jabber/jabber_p.h index 4f7b1e8b0..3a120fbac 100644 --- a/src/sip/jabber/jabber_p.h +++ b/src/sip/jabber/jabber_p.h @@ -154,7 +154,7 @@ public slots: private slots: void doJabberRecv(); - void onSubscriptionRequestConfirmed(int result); + void onSubscriptionRequestConfirmed( int result ); private: bool presenceMeansOnline( gloox::Presence::PresenceType p ); From f198cf51cc1ebaa33477d5bbe1b012d195a22f76 Mon Sep 17 00:00:00 2001 From: Jeff Mitchell Date: Mon, 28 Mar 2011 11:12:28 -0400 Subject: [PATCH 190/329] Properly use the configured port number --- ChangeLog | 4 ++++ src/libtomahawk/network/servent.h | 4 +--- src/tomahawkapp.cpp | 5 +++-- 3 files changed, 8 insertions(+), 5 deletions(-) diff --git a/ChangeLog b/ChangeLog index 45f0b1d5c..89a441c90 100644 --- a/ChangeLog +++ b/ChangeLog @@ -1,3 +1,7 @@ +Version 0.0.3: + * Properly honor the chosen port number if a static host and port are + marked as preferred. + Version 0.0.2: * Don't reconnect to Jabber if the settings dialog is closed successfully but the Jabber settings haven't changed. diff --git a/src/libtomahawk/network/servent.h b/src/libtomahawk/network/servent.h index f88581445..21ef28d27 100644 --- a/src/libtomahawk/network/servent.h +++ b/src/libtomahawk/network/servent.h @@ -19,8 +19,6 @@ #ifndef SERVENT_H #define SERVENT_H -// port for servent to listen on -#define DEFAULT_LISTEN_PORT 50210 // time before new connection terminates if no auth received #define AUTH_TIMEOUT 180000 @@ -91,7 +89,7 @@ public: explicit Servent( QObject* parent = 0 ); virtual ~Servent(); - bool startListening( QHostAddress ha, bool upnp = false, int port = DEFAULT_LISTEN_PORT ); + bool startListening( QHostAddress ha, bool upnp, int port ); int port() const { return m_port; } diff --git a/src/tomahawkapp.cpp b/src/tomahawkapp.cpp index eb178ede0..7a94f11a0 100644 --- a/src/tomahawkapp.cpp +++ b/src/tomahawkapp.cpp @@ -463,8 +463,9 @@ TomahawkApp::initLocalCollection() void TomahawkApp::startServent() { - bool upnp = !arguments().contains( "--noupnp" ) && TomahawkSettings::instance()->value( "network/upnp", true ).toBool(); - if ( !Servent::instance()->startListening( QHostAddress( QHostAddress::Any ), upnp ) ) + bool upnp = !arguments().contains( "--noupnp" ) && TomahawkSettings::instance()->value( "network/upnp", true ).toBool() && !TomahawkSettings::instance()->preferStaticHostPort(); + int port = TomahawkSettings::instance()->externalPort(); + if ( !Servent::instance()->startListening( QHostAddress( QHostAddress::Any ), upnp, port ) ) { qDebug() << "Failed to start listening with servent"; exit( 1 ); From dd61f4a3ccbabe428c28fab314dc713be7abb712 Mon Sep 17 00:00:00 2001 From: Jeff Mitchell Date: Mon, 28 Mar 2011 12:00:11 -0400 Subject: [PATCH 191/329] For sanity don't use "using namespace" in jabber_p.cpp and properly use namespaces. Google style guide doesn't let you do the former for very good reason. --- src/sip/jabber/jabber_p.cpp | 143 ++++++++++++++++++------------------ 1 file changed, 70 insertions(+), 73 deletions(-) diff --git a/src/sip/jabber/jabber_p.cpp b/src/sip/jabber/jabber_p.cpp index 462296682..c5836005c 100644 --- a/src/sip/jabber/jabber_p.cpp +++ b/src/sip/jabber/jabber_p.cpp @@ -30,9 +30,6 @@ #include #include -using namespace gloox; -using namespace std; - #define TOMAHAWK_CAP_NODE_NAME QString::fromAscii("http://tomahawk-player.org/") Jabber_p::Jabber_p( const QString& jid, const QString& password, const QString& server, const int port ) @@ -42,17 +39,17 @@ Jabber_p::Jabber_p( const QString& jid, const QString& password, const QString& qDebug() << Q_FUNC_INFO; qsrand( QTime( 0, 0, 0 ).secsTo( QTime::currentTime() ) ); - m_presences[Presence::Available] = "available"; - m_presences[Presence::Chat] = "chat"; - m_presences[Presence::Away] = "away"; - m_presences[Presence::DND] = "dnd"; - m_presences[Presence::XA] = "xa"; - m_presences[Presence::Unavailable] = "unavailable"; - m_presences[Presence::Probe] = "probe"; - m_presences[Presence::Error] = "error"; - m_presences[Presence::Invalid] = "invalid"; + m_presences[gloox::Presence::Available] = "available"; + m_presences[gloox::Presence::Chat] = "chat"; + m_presences[gloox::Presence::Away] = "away"; + m_presences[gloox::Presence::DND] = "dnd"; + m_presences[gloox::Presence::XA] = "xa"; + m_presences[gloox::Presence::Unavailable] = "unavailable"; + m_presences[gloox::Presence::Probe] = "probe"; + m_presences[gloox::Presence::Error] = "error"; + m_presences[gloox::Presence::Invalid] = "invalid"; - m_jid = JID( jid.toStdString() ); + m_jid = gloox::JID( jid.toStdString() ); if( m_jid.resource().find( "tomahawk" ) == std::string::npos ) { @@ -63,10 +60,10 @@ Jabber_p::Jabber_p( const QString& jid, const QString& password, const QString& qDebug() << "Our JID set to:" << m_jid.full().c_str(); m_client = QSharedPointer( new gloox::Client( m_jid, password.toStdString(), port ) ); - const Capabilities *caps = m_client->presence().capabilities(); + const gloox::Capabilities *caps = m_client->presence().capabilities(); if( caps ) { - const_cast(caps)->setNode(TOMAHAWK_CAP_NODE_NAME.toStdString()); + const_cast(caps)->setNode(TOMAHAWK_CAP_NODE_NAME.toStdString()); } m_server = server; } @@ -152,7 +149,7 @@ Jabber_p::go() m_client->registerPresenceHandler( this ); m_client->registerConnectionListener( this ); m_client->rosterManager()->registerRosterListener( this, false ); // false means async - m_client->logInstance().registerLogHandler( LogLevelWarning, LogAreaAll, this ); + m_client->logInstance().registerLogHandler( gloox::LogLevelWarning, gloox::LogAreaAll, this ); m_client->registerMessageHandler( this ); /* @@ -162,7 +159,7 @@ Jabber_p::go() m_client->disco()->addFeature( "tomahawk:player" ); */ - m_client->setPresence( Presence::XA, -127, "Tomahawk available" ); + m_client->setPresence( gloox::Presence::XA, -127, "Tomahawk available" ); // m_client->connect(); // return; @@ -172,7 +169,7 @@ Jabber_p::go() qDebug() << "Connecting to the XMPP server..."; if( m_client->connect( false ) ) { - int sock = static_cast( m_client->connectionImpl() )->socket(); + int sock = static_cast( m_client->connectionImpl() )->socket(); m_notifier.reset( new QSocketNotifier( sock, QSocketNotifier::Read ) ); connect( m_notifier.data(), SIGNAL( activated(int) ), SLOT( doJabberRecv() )); } @@ -187,8 +184,8 @@ Jabber_p::doJabberRecv() if ( m_client.isNull() ) return; - ConnectionError ce = m_client->recv( 100 ); - if ( ce != ConnNoError ) + gloox::ConnectionError ce = m_client->recv( 100 ); + if ( ce != gloox::ConnNoError ) { qDebug() << "Jabber_p::Recv failed, disconnected"; } @@ -225,7 +222,7 @@ Jabber_p::sendMsg( const QString& to, const QString& msg ) return; qDebug() << Q_FUNC_INFO << to << msg; - Message m( Message::Chat, JID(to.toStdString()), msg.toStdString(), "" ); + gloox::Message m( gloox::Message::Chat, gloox::JID(to.toStdString()), msg.toStdString(), "" ); m_client->send( m ); // assuming this is threadsafe } @@ -249,7 +246,7 @@ Jabber_p::broadcastMsg( const QString &msg ) std::string msg_s = msg.toStdString(); foreach( const QString& jidstr, m_peers.keys() ) { - Message m(Message::Chat, JID(jidstr.toStdString()), msg_s, ""); + gloox::Message m(gloox::Message::Chat, gloox::JID(jidstr.toStdString()), msg_s, ""); m_client->send( m ); } } @@ -268,7 +265,7 @@ Jabber_p::addContact( const QString& jid, const QString& msg ) return; } - handleSubscription(JID(jid.toStdString()), msg.toStdString()); + handleSubscription(gloox::JID(jid.toStdString()), msg.toStdString()); return; } @@ -293,7 +290,7 @@ Jabber_p::onConnect() void -Jabber_p::onDisconnect( ConnectionError e ) +Jabber_p::onDisconnect( gloox::ConnectionError e ) { qDebug() << "Jabber Disconnected"; QString error; @@ -301,16 +298,16 @@ Jabber_p::onDisconnect( ConnectionError e ) switch( e ) { - case AuthErrorUndefined: + case gloox::AuthErrorUndefined: error = " No error occurred, or error condition is unknown"; break; - case SaslAborted: + case gloox::SaslAborted: error = "The receiving entity acknowledges an <abort/> element sent " "by the initiating entity; sent in reply to the <abort/> element."; break; - case SaslIncorrectEncoding: + case gloox::SaslIncorrectEncoding: error = "The data provided by the initiating entity could not be processed " "because the [BASE64] encoding is incorrect (e.g., because the encoding " "does not adhere to the definition in Section 3 of [BASE64]); sent in " @@ -318,7 +315,7 @@ Jabber_p::onDisconnect( ConnectionError e ) "initial response data."; break; - case SaslInvalidAuthzid: + case gloox::SaslInvalidAuthzid: error = "The authzid provided by the initiating entity is invalid, either " "because it is incorrectly formatted or because the initiating entity " "does not have permissions to authorize that ID; sent in reply to a " @@ -326,56 +323,56 @@ Jabber_p::onDisconnect( ConnectionError e ) "response data."; break; - case SaslInvalidMechanism: + case gloox::SaslInvalidMechanism: error = "The initiating entity did not provide a mechanism or requested a " "mechanism that is not supported by the receiving entity; sent in reply " "to an <auth/> element."; break; - case SaslMalformedRequest: + case gloox::SaslMalformedRequest: error = "The request is malformed (e.g., the <auth/> element includes " "an initial response but the mechanism does not allow that); sent in " "reply to an <abort/>, <auth/>, <challenge/>, or " "<response/> element."; break; - case SaslMechanismTooWeak: + case gloox::SaslMechanismTooWeak: error = "The mechanism requested by the initiating entity is weaker than " "server policy permits for that initiating entity; sent in reply to a " "<response/> element or an <auth/> element with initial " "response data."; break; - case SaslNotAuthorized: + case gloox::SaslNotAuthorized: error = "The authentication failed because the initiating entity did not " "provide valid credentials (this includes but is not limited to the " "case of an unknown username); sent in reply to a <response/> " "element or an <auth/> element with initial response data. "; break; - case SaslTemporaryAuthFailure: + case gloox::SaslTemporaryAuthFailure: error = "The authentication failed because of a temporary error condition " "within the receiving entity; sent in reply to an <auth/> element " "or <response/> element."; break; - case NonSaslConflict: + case gloox::NonSaslConflict: error = "XEP-0078: Resource Conflict"; break; - case NonSaslNotAcceptable: + case gloox::NonSaslNotAcceptable: error = "XEP-0078: Required Information Not Provided"; break; - case NonSaslNotAuthorized: + case gloox::NonSaslNotAuthorized: error = "XEP-0078: Incorrect Credentials"; break; - case ConnAuthenticationFailed: + case gloox::ConnAuthenticationFailed: error = "Authentication failed"; break; - case ConnNoSupportedAuth: + case gloox::ConnNoSupportedAuth: error = "No supported auth mechanism"; break; @@ -396,7 +393,7 @@ Jabber_p::onDisconnect( ConnectionError e ) bool -Jabber_p::onTLSConnect( const CertInfo& info ) +Jabber_p::onTLSConnect( const gloox::CertInfo& info ) { qDebug() << Q_FUNC_INFO << "Status" << info.status @@ -406,8 +403,8 @@ Jabber_p::onTLSConnect( const CertInfo& info ) << "mac" << info.mac.c_str() << "cipher" << info.cipher.c_str() << "compression" << info.compression.c_str() - << "from" << ctime( (const time_t*)&info.date_from ) - << "to" << ctime( (const time_t*)&info.date_to ) + << "from" << std::ctime( (const time_t*)&info.date_from ) + << "to" << std::ctime( (const time_t*)&info.date_to ) ; //onConnect(); @@ -416,7 +413,7 @@ Jabber_p::onTLSConnect( const CertInfo& info ) void -Jabber_p::handleMessage( const Message& m, MessageSession * /*session*/ ) +Jabber_p::handleMessage( const gloox::Message& m, gloox::MessageSession * /*session*/ ) { QString from = QString::fromStdString( m.from().full() ); QString msg = QString::fromStdString( m.body() ); @@ -446,7 +443,7 @@ Jabber_p::handleMessage( const Message& m, MessageSession * /*session*/ ) void -Jabber_p::handleLog( LogLevel level, LogArea area, const std::string& message ) +Jabber_p::handleLog( gloox::LogLevel level, gloox::LogArea area, const std::string& message ) { qDebug() << Q_FUNC_INFO << "level:" << level @@ -458,49 +455,49 @@ Jabber_p::handleLog( LogLevel level, LogArea area, const std::string& message ) /// ROSTER STUFF // {{{ void -Jabber_p::onResourceBindError( ResourceBindError error ) +Jabber_p::onResourceBindError( gloox::ResourceBindError error ) { qDebug() << Q_FUNC_INFO; } void -Jabber_p::onSessionCreateError( SessionCreateError error ) +Jabber_p::onSessionCreateError( gloox::SessionCreateError error ) { qDebug() << Q_FUNC_INFO; } void -Jabber_p::handleItemSubscribed( const JID& jid ) +Jabber_p::handleItemSubscribed( const gloox::JID& jid ) { qDebug() << Q_FUNC_INFO << jid.full().c_str(); } void -Jabber_p::handleItemAdded( const JID& jid ) +Jabber_p::handleItemAdded( const gloox::JID& jid ) { qDebug() << Q_FUNC_INFO << jid.full().c_str(); } void -Jabber_p::handleItemUnsubscribed( const JID& jid ) +Jabber_p::handleItemUnsubscribed( const gloox::JID& jid ) { qDebug() << Q_FUNC_INFO << jid.full().c_str(); } void -Jabber_p::handleItemRemoved( const JID& jid ) +Jabber_p::handleItemRemoved( const gloox::JID& jid ) { qDebug() << Q_FUNC_INFO << jid.full().c_str(); } void -Jabber_p::handleItemUpdated( const JID& jid ) +Jabber_p::handleItemUpdated( const gloox::JID& jid ) { qDebug() << Q_FUNC_INFO << jid.full().c_str(); } @@ -508,14 +505,14 @@ Jabber_p::handleItemUpdated( const JID& jid ) void -Jabber_p::handleRoster( const Roster& roster ) +Jabber_p::handleRoster( const gloox::Roster& roster ) { // qDebug() << Q_FUNC_INFO; - Roster::const_iterator it = roster.begin(); +gloox::Roster::const_iterator it = roster.begin(); for ( ; it != roster.end(); ++it ) { - if ( (*it).second->subscription() != S10nBoth ) continue; + if ( (*it).second->subscription() != gloox::S10nBoth ) continue; qDebug() << (*it).second->jid().c_str() << (*it).second->name().c_str(); //printf("JID: %s\n", (*it).second->jid().c_str()); } @@ -527,7 +524,7 @@ Jabber_p::handleRoster( const Roster& roster ) void -Jabber_p::handleRosterError( const IQ& /*iq*/ ) +Jabber_p::handleRosterError( const gloox::IQ& /*iq*/ ) { qDebug() << Q_FUNC_INFO; } @@ -536,7 +533,7 @@ Jabber_p::handleRosterError( const IQ& /*iq*/ ) void Jabber_p::handlePresence( const gloox::Presence& presence ) { - JID jid = presence.from(); + gloox::JID jid = presence.from(); QString fulljid( jid.full().c_str() ); qDebug() << "* handleRosterPresence" << fulljid << presence.subtype(); @@ -596,11 +593,11 @@ Jabber_p::handlePresence( const gloox::Presence& presence ) bool -Jabber_p::handleSubscription( const JID& jid, const std::string& /*msg*/ ) +Jabber_p::handleSubscription( const gloox::JID& jid, const std::string& /*msg*/ ) { qDebug() << Q_FUNC_INFO << jid.bare().c_str(); - StringList groups; + gloox::StringList groups; groups.push_back( "Tomahawk" ); m_client->rosterManager()->subscribe( jid, "", groups, "" ); return true; @@ -608,12 +605,12 @@ Jabber_p::handleSubscription( const JID& jid, const std::string& /*msg*/ ) bool -Jabber_p::handleSubscriptionRequest( const JID& jid, const std::string& /*msg*/ ) +Jabber_p::handleSubscriptionRequest( const gloox::JID& jid, const std::string& /*msg*/ ) { qDebug() << Q_FUNC_INFO << jid.bare().c_str(); // check if the requester is already on the roster - RosterItem *item = m_client->rosterManager()->getRosterItem(jid); + gloox::RosterItem *item = m_client->rosterManager()->getRosterItem(jid); if(item) qDebug() << Q_FUNC_INFO << jid.bare().c_str() << "subscription status:" << static_cast( item->subscription() ); if(item && ( @@ -660,7 +657,7 @@ Jabber_p::onSubscriptionRequestConfirmed( int result ) qDebug() << Q_FUNC_INFO << result; QList< QMessageBox* > confirmBoxes = m_subscriptionConfirmBoxes.values(); - JID jid; + gloox::JID jid; foreach( QMessageBox* currentBox, confirmBoxes ) { @@ -681,7 +678,7 @@ Jabber_p::onSubscriptionRequestConfirmed( int result ) if ( allowSubscription == QMessageBox::Yes ) { qDebug() << Q_FUNC_INFO << jid.bare().c_str() << "accepted by user, adding to roster"; - StringList groups; + gloox::StringList groups; groups.push_back( "Tomahawk" ); m_client->rosterManager()->subscribe( jid, "", groups, "" ); } @@ -695,7 +692,7 @@ Jabber_p::onSubscriptionRequestConfirmed( int result ) bool -Jabber_p::handleUnsubscriptionRequest( const JID& jid, const std::string& /*msg*/ ) +Jabber_p::handleUnsubscriptionRequest( const gloox::JID& jid, const std::string& /*msg*/ ) { qDebug() << Q_FUNC_INFO << jid.bare().c_str(); return true; @@ -703,7 +700,7 @@ Jabber_p::handleUnsubscriptionRequest( const JID& jid, const std::string& /*msg* void -Jabber_p::handleNonrosterPresence( const Presence& presence ) +Jabber_p::handleNonrosterPresence( const gloox::Presence& presence ) { qDebug() << Q_FUNC_INFO << presence.from().full().c_str(); } @@ -711,14 +708,14 @@ Jabber_p::handleNonrosterPresence( const Presence& presence ) void -Jabber_p::handleVCard( const JID& jid, const VCard* vcard ) +Jabber_p::handleVCard( const gloox::JID& jid, const gloox::VCard* vcard ) { qDebug() << "VCARD RECEIVED!" << jid.bare().c_str(); } void -Jabber_p::handleVCardResult( VCardContext context, const JID& jid, StanzaError se ) +Jabber_p::handleVCardResult( gloox::VCardHandler::VCardContext context, const gloox::JID& jid, gloox::StanzaError se ) { qDebug() << "VCARD RESULT RECEIVED!" << jid.bare().c_str(); } @@ -726,14 +723,14 @@ Jabber_p::handleVCardResult( VCardContext context, const JID& jid, StanzaError s /// DISCO STUFF void -Jabber_p::handleDiscoInfo( const JID& from, const Disco::Info& info, int context) +Jabber_p::handleDiscoInfo( const gloox::JID& from, const gloox::Disco::Info& info, int context ) { QString jidstr( from.full().c_str() ); //qDebug() << "DISCOinfo" << jidstr; if ( info.hasFeature("tomahawk:player") ) { qDebug() << "Peer online and DISCOed ok:" << jidstr; - m_peers.insert( jidstr, Presence::XA ); + m_peers.insert( jidstr, gloox::Presence::XA ); emit peerOnline( jidstr ); } else @@ -744,14 +741,14 @@ Jabber_p::handleDiscoInfo( const JID& from, const Disco::Info& info, int context void -Jabber_p::handleDiscoItems( const JID& /*iq*/, const Disco::Items&, int /*context*/ ) +Jabber_p::handleDiscoItems( const gloox::JID& /*iq*/, const gloox::Disco::Items&, int /*context*/ ) { qDebug() << Q_FUNC_INFO; } void -Jabber_p::handleDiscoError( const JID& j, const Error* e, int /*context*/ ) +Jabber_p::handleDiscoError( const gloox::JID& j, const gloox::Error* e, int /*context*/ ) { qDebug() << Q_FUNC_INFO << j.full().c_str() << e->text().c_str() << e->type(); } @@ -759,13 +756,13 @@ Jabber_p::handleDiscoError( const JID& j, const Error* e, int /*context*/ ) bool -Jabber_p::presenceMeansOnline( Presence::PresenceType p ) +Jabber_p::presenceMeansOnline( gloox::Presence::PresenceType p ) { switch(p) { - case Presence::Invalid: - case Presence::Unavailable: - case Presence::Error: + case gloox::Presence::Invalid: + case gloox::Presence::Unavailable: + case gloox::Presence::Error: return false; break; default: From 1e3fe13a5bc6b54bdcd7205bbf8c0456cb7377c8 Mon Sep 17 00:00:00 2001 From: Leo Franchi Date: Mon, 28 Mar 2011 12:45:22 -0400 Subject: [PATCH 192/329] update sparkle rss from server and fix changelog to 0.0.2 --- admin/mac/sparkle-beta.rss | 4 ++-- admin/mac/sparkle.rss | 4 ++-- admin/win/sparklewin-beta.rss | 10 +++++++++- admin/win/sparklewin.rss | 10 +++++++++- 4 files changed, 22 insertions(+), 6 deletions(-) diff --git a/admin/mac/sparkle-beta.rss b/admin/mac/sparkle-beta.rss index 124746590..c0ee02303 100755 --- a/admin/mac/sparkle-beta.rss +++ b/admin/mac/sparkle-beta.rss @@ -16,10 +16,10 @@ Version 0.0.2 (Tomahawk Player Beta - It Lives?) - https://github.com/tomahawk-player/tomahawk/raw/stable/ChangeLog + https://github.com/tomahawk-player/tomahawk/raw/0.0.2/ChangeLog Mon, 28 Mar 2011 06:13:01 +0100 - + diff --git a/admin/mac/sparkle.rss b/admin/mac/sparkle.rss index 7e98e4568..fa6bfafcc 100755 --- a/admin/mac/sparkle.rss +++ b/admin/mac/sparkle.rss @@ -16,10 +16,10 @@ Version 0.0.2 (Tomahawk Player - It Lives!) - https://github.com/tomahawk-player/tomahawk/raw/stable/ChangeLog + https://github.com/tomahawk-player/tomahawk/raw/0.0.2/ChangeLog Mon, 28 Mar 2011 06:13:01 +0100 - + diff --git a/admin/win/sparklewin-beta.rss b/admin/win/sparklewin-beta.rss index 14d33d797..335303fdd 100644 --- a/admin/win/sparklewin-beta.rss +++ b/admin/win/sparklewin-beta.rss @@ -8,10 +8,18 @@ Version 0.0.1 (Tomahawk Player Beta - It Lives?) - https://github.com/tomahawk-player/tomahawk/raw/stable/ChangeLog + https://github.com/tomahawk-player/tomahawk/raw/0.0.1/ChangeLog Fri, 25 Mar 2011 00:00:01 +0100 + + Version 0.0.2 (Tomahawk Player Beta - It Lives?) + + https://github.com/tomahawk-player/tomahawk/raw/0.0.2/ChangeLog + + Mon, 28 Mar 2011 05:00:02 +0100 + + diff --git a/admin/win/sparklewin.rss b/admin/win/sparklewin.rss index aa9f8f4fa..9986a74ad 100644 --- a/admin/win/sparklewin.rss +++ b/admin/win/sparklewin.rss @@ -11,7 +11,15 @@ https://github.com/tomahawk-player/tomahawk/raw/0.0.1/ChangeLog Fri, 25 Mar 2011 00:00:01 +0100 - + +
+ + Version 0.0.2 (Tomahawk Player - It Lives!) + + https://github.com/tomahawk-player/tomahawk/raw/0.0.2/ChangeLog + + Mon, 28 Mar 2011 05:00:02 +0100 + From 1dd0f26fffd959395d789f6593cf8d557e59fb85 Mon Sep 17 00:00:00 2001 From: Jeff Mitchell Date: Mon, 28 Mar 2011 13:31:00 -0400 Subject: [PATCH 193/329] Remove MusixMatchHash --- include/tomahawk/infosystem.h | 2 -- src/infosystem/infoplugins/musixmatchplugin.cpp | 16 ++++++++-------- src/infosystem/infosystem.cpp | 1 - src/xmppbot/xmppbot.cpp | 16 ++++++++-------- 4 files changed, 16 insertions(+), 19 deletions(-) diff --git a/include/tomahawk/infosystem.h b/include/tomahawk/infosystem.h index 877345409..93a9b1745 100644 --- a/include/tomahawk/infosystem.h +++ b/include/tomahawk/infosystem.h @@ -94,7 +94,6 @@ enum InfoType { typedef QMap< InfoType, QVariant > InfoMap; typedef QMap< QString, QMap< QString, QString > > InfoGenericMap; typedef QHash< QString, QVariant > InfoCustomDataHash; -typedef QHash< QString, QString > MusixMatchHash; class InfoPlugin : public QObject { @@ -168,6 +167,5 @@ private: Q_DECLARE_METATYPE( Tomahawk::InfoSystem::InfoGenericMap ) Q_DECLARE_METATYPE( Tomahawk::InfoSystem::InfoCustomDataHash ); -Q_DECLARE_METATYPE( Tomahawk::InfoSystem::MusixMatchHash ) #endif // TOMAHAWK_INFOSYSTEM_H diff --git a/src/infosystem/infoplugins/musixmatchplugin.cpp b/src/infosystem/infoplugins/musixmatchplugin.cpp index 08795c713..0ee319f78 100644 --- a/src/infosystem/infoplugins/musixmatchplugin.cpp +++ b/src/infosystem/infoplugins/musixmatchplugin.cpp @@ -45,11 +45,11 @@ MusixMatchPlugin::~MusixMatchPlugin() void MusixMatchPlugin::getInfo(const QString &caller, const InfoType type, const QVariant& data, Tomahawk::InfoSystem::InfoCustomDataHash customData) { qDebug() << Q_FUNC_INFO; - if( !isValidTrackData(caller, data, customData) || !data.canConvert()) + if( !isValidTrackData(caller, data, customData) || !data.canConvert()) return; - Tomahawk::InfoSystem::MusixMatchHash hash = data.value(); - QString artist = hash["artistName"]; - QString track = hash["trackName"]; + Tomahawk::InfoSystem::InfoCustomDataHash hash = data.value(); + QString artist = hash["artistName"].toString(); + QString track = hash["trackName"].toString(); if( artist.isEmpty() || track.isEmpty() ) { emit info(caller, Tomahawk::InfoSystem::InfoTrackLyrics, data, QVariant(), customData); @@ -73,22 +73,22 @@ void MusixMatchPlugin::getInfo(const QString &caller, const InfoType type, const bool MusixMatchPlugin::isValidTrackData(const QString &caller, const QVariant& data, Tomahawk::InfoSystem::InfoCustomDataHash &customData) { qDebug() << Q_FUNC_INFO; - if (data.isNull() || !data.isValid() || !data.canConvert()) + if (data.isNull() || !data.isValid() || !data.canConvert()) { emit info(caller, Tomahawk::InfoSystem::InfoTrackLyrics, data, QVariant(), customData); emit finished(caller, Tomahawk::InfoSystem::InfoTrackLyrics); qDebug() << "MusixMatchPlugin::isValidTrackData: Data null, invalid, or can't convert"; return false; } - MusixMatchHash hash = data.value(); - if (hash["trackName"].isEmpty() ) + InfoCustomDataHash hash = data.value(); + if (hash["trackName"].toString().isEmpty() ) { emit info(caller, Tomahawk::InfoSystem::InfoTrackLyrics, data, QVariant(), customData); emit finished(caller, Tomahawk::InfoSystem::InfoTrackLyrics); qDebug() << "MusixMatchPlugin::isValidTrackData: Track name is empty"; return false; } - if (hash["artistName"].isEmpty() ) + if (hash["artistName"].toString().isEmpty() ) { emit info(caller, Tomahawk::InfoSystem::InfoTrackLyrics, data, QVariant(), customData); emit finished(caller, Tomahawk::InfoSystem::InfoTrackLyrics); diff --git a/src/infosystem/infosystem.cpp b/src/infosystem/infosystem.cpp index dd8862582..e4a6a6c2a 100644 --- a/src/infosystem/infosystem.cpp +++ b/src/infosystem/infosystem.cpp @@ -47,7 +47,6 @@ InfoSystem::InfoSystem(QObject *parent) qDebug() << Q_FUNC_INFO; qRegisterMetaType > >("Tomahawk::InfoSystem::InfoGenericMap"); qRegisterMetaType >("Tomahawk::InfoSystem::InfoCustomDataHash"); - qRegisterMetaType >("Tomahawk::InfoSystem::MusixMatchHash"); m_infoSystemCacheThreadController = new QThread( this ); m_cache = new Tomahawk::InfoSystem::InfoSystemCache(); diff --git a/src/xmppbot/xmppbot.cpp b/src/xmppbot/xmppbot.cpp index d4f026d35..e6be8a57c 100644 --- a/src/xmppbot/xmppbot.cpp +++ b/src/xmppbot/xmppbot.cpp @@ -215,10 +215,10 @@ void XMPPBot::handleMessage(const Message& msg, MessageSession* session) infoMap[InfoArtistFamiliarity] = m_currTrack.data()->artist()->name(); if (token == "lyrics") { - MusixMatchHash myhash; - myhash["trackName"] = m_currTrack.data()->track(); - myhash["artistName"] = m_currTrack.data()->artist()->name(); - infoMap[InfoTrackLyrics] = QVariant::fromValue(myhash); + InfoCustomDataHash myhash; + myhash["trackName"] = QVariant::fromValue(m_currTrack.data()->track()); + myhash["artistName"] = QVariant::fromValue(m_currTrack.data()->artist()->name()); + infoMap[InfoTrackLyrics] = QVariant::fromValue(myhash); } } @@ -364,15 +364,15 @@ void XMPPBot::infoReturnedSlot(QString caller, Tomahawk::InfoSystem::InfoType ty { qDebug() << "Lyrics requested"; if (!output.canConvert() || - !input.canConvert() + !input.canConvert() ) { qDebug() << "Variants failed to be valid"; break; } - MusixMatchHash inHash = input.value(); - QString artist = inHash["artistName"]; - QString track = inHash["trackName"]; + InfoCustomDataHash inHash = input.value(); + QString artist = inHash["artistName"].toString(); + QString track = inHash["trackName"].toString(); QString lyrics = output.toString(); qDebug() << "lyrics = " << lyrics; m_currReturnMessage += QString("\nLyrics for \"%1\" by %2:\n\n%3\n").arg(track).arg(artist).arg(lyrics); From ae5fa0c33ad89a7a14c4ac31b67d3ed46929dcfd Mon Sep 17 00:00:00 2001 From: Jeff Mitchell Date: Mon, 28 Mar 2011 16:41:22 -0400 Subject: [PATCH 194/329] Initial work on ACL system. Also makes much of Tomahawk handle a QStringList for collection directories. --- include/tomahawk/infosystem.h | 2 +- src/libtomahawk/CMakeLists.txt | 2 + src/libtomahawk/aclsystem.cpp | 72 ++++++++++++++++++++++++++++ src/libtomahawk/aclsystem.h | 54 +++++++++++++++++++++ src/libtomahawk/tomahawksettings.cpp | 8 ++-- src/libtomahawk/tomahawksettings.h | 4 +- src/musicscanner.cpp | 11 +++-- src/musicscanner.h | 4 +- src/scanmanager.cpp | 3 +- src/scanmanager.h | 5 +- src/settingsdialog.cpp | 5 +- 11 files changed, 152 insertions(+), 18 deletions(-) create mode 100644 src/libtomahawk/aclsystem.cpp create mode 100644 src/libtomahawk/aclsystem.h diff --git a/include/tomahawk/infosystem.h b/include/tomahawk/infosystem.h index 93a9b1745..30c43f5d2 100644 --- a/include/tomahawk/infosystem.h +++ b/include/tomahawk/infosystem.h @@ -165,7 +165,7 @@ private: } -Q_DECLARE_METATYPE( Tomahawk::InfoSystem::InfoGenericMap ) +Q_DECLARE_METATYPE( Tomahawk::InfoSystem::InfoGenericMap ); Q_DECLARE_METATYPE( Tomahawk::InfoSystem::InfoCustomDataHash ); #endif // TOMAHAWK_INFOSYSTEM_H diff --git a/src/libtomahawk/CMakeLists.txt b/src/libtomahawk/CMakeLists.txt index f45c391a3..b2e54fcc7 100644 --- a/src/libtomahawk/CMakeLists.txt +++ b/src/libtomahawk/CMakeLists.txt @@ -16,6 +16,7 @@ set( libSources sourcelist.cpp pipeline.cpp + aclsystem.cpp artist.cpp album.cpp collection.cpp @@ -155,6 +156,7 @@ set( libHeaders pipeline.h functimeout.h + aclsystem.h collection.h query.h resolver.h diff --git a/src/libtomahawk/aclsystem.cpp b/src/libtomahawk/aclsystem.cpp new file mode 100644 index 000000000..73ceff6ae --- /dev/null +++ b/src/libtomahawk/aclsystem.cpp @@ -0,0 +1,72 @@ +/* === This file is part of Tomahawk Player - === + * + * Copyright 2010-2011, Christian Muehlhaeuser + * + * Tomahawk is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * Tomahawk is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with Tomahawk. If not, see . + */ + +#include "aclsystem.h" + +#include + +#include + +ACLSystem::ACLSystem( QObject* parent ) + : QObject( parent ), + m_saveTimer( this ) +{ + //TODO: read from settings file into cache + m_saveTimer.setSingleShot( false ); + m_saveTimer.setInterval( 60000 ); + connect( &m_saveTimer, SIGNAL( timeout() ), this, SLOT( saveTimerFired() ) ); +} + +ACLSystem::~ACLSystem() +{ + //TODO: save from cache into settings file +} + +void +ACLSystem::authorize( const QString& dbid, const QString& path, ACLType type ) +{ + TomahawkSettings *s = TomahawkSettings::instance(); + if ( !s->scannerPath().contains( path ) ) + { + qDebug() << "path selected is not in our scanner path!"; + return; + } + QHash< QString, ACLType > peerHash; + if ( m_cache.contains( "dbid" ) ) + peerHash = m_cache["dbid"]; + peerHash[path] = type; +} + +bool +ACLSystem::isAuthorized( const QString& dbid, const QString& path ) +{ + if ( !m_cache.contains( "dbid" ) ) + return false; + + QHash< QString, ACLType > peerHash = m_cache["dbid"]; + if ( !peerHash.contains( path ) ) + return false; + + return peerHash[path] == ACLSystem::Allow; +} + +void +ACLSystem::saveTimerFired() +{ + //TODO: save from cache into settings file +} \ No newline at end of file diff --git a/src/libtomahawk/aclsystem.h b/src/libtomahawk/aclsystem.h new file mode 100644 index 000000000..8a6cf9d33 --- /dev/null +++ b/src/libtomahawk/aclsystem.h @@ -0,0 +1,54 @@ +/* === This file is part of Tomahawk Player - === + * + * Copyright 2010-2011, Christian Muehlhaeuser + * + * Tomahawk is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * Tomahawk is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with Tomahawk. If not, see . + */ + +#ifndef TOMAHAWK_ACLSYSTEM_H +#define TOMAHAWK_ACLSYSTEM_H + +#include +#include +#include +#include + +#include "dllmacro.h" + +class DLLEXPORT ACLSystem : public QObject +{ + Q_OBJECT + + enum ACLType { + Allow, + Deny + }; + +public: + + ACLSystem( QObject *parent = 0 ); + ~ACLSystem(); + + bool isAuthorized( const QString &dbid, const QString &path ); + void authorize( const QString &dbid, const QString &path, ACLType type ); + +private slots: + void saveTimerFired(); + +private: + QHash< QString, QHash< QString, ACLType> > m_cache; + QTimer m_saveTimer; +}; + +#endif // TOMAHAWK_ACLSYSTEM_H diff --git a/src/libtomahawk/tomahawksettings.cpp b/src/libtomahawk/tomahawksettings.cpp index a4008388b..fe307bd20 100644 --- a/src/libtomahawk/tomahawksettings.cpp +++ b/src/libtomahawk/tomahawksettings.cpp @@ -65,19 +65,19 @@ TomahawkSettings::~TomahawkSettings() } -QString +QStringList TomahawkSettings::scannerPath() const { #ifndef TOMAHAWK_HEADLESS - return value( "scannerpath", QDesktopServices::storageLocation( QDesktopServices::MusicLocation ) ).toString(); + return value( "scannerpath", QDesktopServices::storageLocation( QDesktopServices::MusicLocation ) ).toStringList(); #else - return value( "scannerpath", "" ).toString(); + return value( "scannerpath", "" ).toStringList(); #endif } void -TomahawkSettings::setScannerPath( const QString& path ) +TomahawkSettings::setScannerPath( const QStringList& path ) { setValue( "scannerpath", path ); } diff --git a/src/libtomahawk/tomahawksettings.h b/src/libtomahawk/tomahawksettings.h index f7f5e2922..fc1995049 100644 --- a/src/libtomahawk/tomahawksettings.h +++ b/src/libtomahawk/tomahawksettings.h @@ -41,8 +41,8 @@ public: void applyChanges() { emit changed(); } /// General settings - QString scannerPath() const; /// QDesktopServices::MusicLocation by default - void setScannerPath( const QString& path ); + QStringList scannerPath() const; /// QDesktopServices::MusicLocation by default + void setScannerPath( const QStringList& path ); bool hasScannerPath() const; bool acceptedLegalWarning() const; diff --git a/src/musicscanner.cpp b/src/musicscanner.cpp index 220baa1a7..a97171416 100644 --- a/src/musicscanner.cpp +++ b/src/musicscanner.cpp @@ -70,9 +70,9 @@ DirLister::scanDir( QDir dir, int depth ) } -MusicScanner::MusicScanner( const QString& dir, quint32 bs ) +MusicScanner::MusicScanner( const QStringList& dirs, quint32 bs ) : QObject() - , m_dir( dir ) + , m_dirs( dirs ) , m_batchsize( bs ) , m_dirLister( 0 ) , m_dirListerThreadController( 0 ) @@ -122,7 +122,8 @@ MusicScanner::startScan() m_skippedFiles.clear(); // trigger the scan once we've loaded old mtimes for dirs below our path - DatabaseCommand_DirMtimes* cmd = new DatabaseCommand_DirMtimes( m_dir ); + //FIXME: MULTIPLECOLLECTIONDIRS + DatabaseCommand_DirMtimes* cmd = new DatabaseCommand_DirMtimes( m_dirs.first() ); connect( cmd, SIGNAL( done( QMap ) ), SLOT( setMtimes( QMap ) ) ); connect( cmd, SIGNAL( done( QMap ) ), @@ -148,7 +149,9 @@ MusicScanner::scan() SLOT( commitBatch( QVariantList ) ), Qt::DirectConnection ); m_dirListerThreadController = new QThread( this ); - m_dirLister = new DirLister( QDir( m_dir, 0 ), m_dirmtimes ); + + //FIXME: MULTIPLECOLLECTIONDIRS + m_dirLister = new DirLister( QDir( m_dirs.first(), 0 ), m_dirmtimes ); m_dirLister->moveToThread( m_dirListerThreadController ); connect( m_dirLister, SIGNAL( fileToScan( QFileInfo ) ), diff --git a/src/musicscanner.h b/src/musicscanner.h index 8fbca2739..c4fc5bb5b 100644 --- a/src/musicscanner.h +++ b/src/musicscanner.h @@ -69,7 +69,7 @@ class MusicScanner : public QObject Q_OBJECT public: - MusicScanner( const QString& dir, quint32 bs = 0 ); + MusicScanner( const QStringList& dirs, quint32 bs = 0 ); ~MusicScanner(); signals: @@ -92,7 +92,7 @@ private slots: void commitBatch( const QVariantList& ); private: - QString m_dir; + QStringList m_dirs; QMap m_ext2mime; // eg: mp3 -> audio/mpeg unsigned int m_scanned; unsigned int m_skipped; diff --git a/src/scanmanager.cpp b/src/scanmanager.cpp index ebbe0eabb..017dc8171 100644 --- a/src/scanmanager.cpp +++ b/src/scanmanager.cpp @@ -89,9 +89,10 @@ ScanManager::onSettingsChanged() void -ScanManager::runManualScan( const QString& path ) +ScanManager::runManualScan( const QStringList& path ) { qDebug() << Q_FUNC_INFO; + if ( !m_musicScannerThreadController && !m_scanner ) //still running if these are not zero { m_musicScannerThreadController = new QThread( this ); diff --git a/src/scanmanager.h b/src/scanmanager.h index 0a115b444..a20d9990f 100644 --- a/src/scanmanager.h +++ b/src/scanmanager.h @@ -20,6 +20,7 @@ #define SCANMANAGER_H #include +#include #include "dllmacro.h" @@ -36,7 +37,7 @@ public: explicit ScanManager( QObject* parent = 0 ); virtual ~ScanManager(); - void runManualScan( const QString& path ); + void runManualScan( const QStringList& path ); signals: void finished(); @@ -53,7 +54,7 @@ private: MusicScanner* m_scanner; QThread* m_musicScannerThreadController; - QString m_currScannerPath; + QStringList m_currScannerPath; }; #endif diff --git a/src/settingsdialog.cpp b/src/settingsdialog.cpp index 8baadd6e9..bafcb3ba3 100644 --- a/src/settingsdialog.cpp +++ b/src/settingsdialog.cpp @@ -86,7 +86,8 @@ SettingsDialog::SettingsDialog( QWidget *parent ) } // MUSIC SCANNER - ui->lineEditMusicPath->setText( s->scannerPath() ); + //FIXME: MULTIPLECOLLECTIONDIRS + ui->lineEditMusicPath->setText( s->scannerPath().first() ); // LAST FM ui->checkBoxEnableLastfm->setChecked( s->scrobblingEnabled() ); @@ -133,7 +134,7 @@ SettingsDialog::~SettingsDialog() s->setExternalHostname( ui->staticHostName->text() ); s->setExternalPort( ui->staticPort->value() ); - s->setScannerPath( ui->lineEditMusicPath->text() ); + s->setScannerPath( QStringList( ui->lineEditMusicPath->text() ) ); s->setScrobblingEnabled( ui->checkBoxEnableLastfm->isChecked() ); s->setLastFmUsername( ui->lineEditLastfmUsername->text() ); From 896d6ad9f42ae61e3043c0d196cad3ba7db41f1c Mon Sep 17 00:00:00 2001 From: Jeff Mitchell Date: Mon, 28 Mar 2011 17:25:43 -0400 Subject: [PATCH 195/329] Clean up port logic --- src/libtomahawk/network/servent.cpp | 27 ++++++++++++++++++++------- src/libtomahawk/tomahawksettings.cpp | 6 ++++++ src/libtomahawk/tomahawksettings.h | 1 + 3 files changed, 27 insertions(+), 7 deletions(-) diff --git a/src/libtomahawk/network/servent.cpp b/src/libtomahawk/network/servent.cpp index 495e903de..0c0d8285c 100644 --- a/src/libtomahawk/network/servent.cpp +++ b/src/libtomahawk/network/servent.cpp @@ -92,17 +92,30 @@ bool Servent::startListening( QHostAddress ha, bool upnp, int port ) { m_port = port; - // try listening on one port higher as well, to aid debugging - // and let you run 2 instances easily - if( !listen( ha, m_port ) && !listen( ha, ++m_port ) ) + int defPort = TomahawkSettings::instance()->defaultPort(); + // Listen on both the selected port and, if not the same, the default port -- the latter sometimes necessary for zeroconf + // TODO: only listen on both when zeroconf sip is enabled + // TODO: use a real zeroconf system instead of a simple UDP broadcast? + if( !listen( ha, m_port ) ) { - qDebug() << "Failed to listen on port" << m_port; - qDebug() << "Error string is " << errorString(); - return false; + bool defPortAlso = false; + if( m_port != defPort ) + defPortAlso = listen( ha, defPort ); + if( !defPortAlso ) + { + qDebug() << "Failed to listen on both port " << m_port << " and port " << defPort; + qDebug() << "Error string is " << errorString(); + return false; + } + else + qDebug() << "Servent listening on port " << defPort << " servent thread:" << thread(); } else { - qDebug() << "Servent listening on port" << m_port << " servent thread:" << thread(); + bool defPortAlso = listen( ha, defPort ); + qDebug() << "Servent listening on port " << m_port << " servent thread:" << thread(); + if( defPortAlso ) + qDebug() << "Servent also listening on port " << defPort << " servent thread:" << thread(); } // --lanhack means to advertise your LAN IP over jabber as if it were externallyVisible diff --git a/src/libtomahawk/tomahawksettings.cpp b/src/libtomahawk/tomahawksettings.cpp index a4008388b..6af02b76f 100644 --- a/src/libtomahawk/tomahawksettings.cpp +++ b/src/libtomahawk/tomahawksettings.cpp @@ -377,6 +377,12 @@ TomahawkSettings::setExternalHostname(const QString& externalHostname) setValue( "network/external-hostname", externalHostname ); } +int +TomahawkSettings::defaultPort() const +{ + return 50210; +} + int TomahawkSettings::externalPort() const { diff --git a/src/libtomahawk/tomahawksettings.h b/src/libtomahawk/tomahawksettings.h index f7f5e2922..b836317fc 100644 --- a/src/libtomahawk/tomahawksettings.h +++ b/src/libtomahawk/tomahawksettings.h @@ -95,6 +95,7 @@ public: QString externalHostname() const; void setExternalHostname( const QString& externalHostname ); + int defaultPort() const; int externalPort() const; void setExternalPort( int externalPort ); From 81141e4d509880b6023b0bc068092470d98e02bb Mon Sep 17 00:00:00 2001 From: Jeff Mitchell Date: Mon, 28 Mar 2011 17:30:43 -0400 Subject: [PATCH 196/329] Add a placeholder text for the jabberid field --- src/settingsdialog.ui | 3 +++ 1 file changed, 3 insertions(+) diff --git a/src/settingsdialog.ui b/src/settingsdialog.ui index 3a07d4254..c3f302da8 100644 --- a/src/settingsdialog.ui +++ b/src/settingsdialog.ui @@ -65,6 +65,9 @@ 0 + + e.g. user@example.com + From 8d7f4a193bc0b568da6fe0bebb07425c623113bd Mon Sep 17 00:00:00 2001 From: Jeff Mitchell Date: Mon, 28 Mar 2011 17:59:33 -0400 Subject: [PATCH 197/329] Rename some classes/variables --- src/libtomahawk/CMakeLists.txt | 4 +- src/libtomahawk/network/controlconnection.cpp | 2 +- src/libtomahawk/network/controlconnection.h | 2 - src/libtomahawk/network/servent.cpp | 38 +++++++++---------- src/libtomahawk/network/servent.h | 14 +++---- ...ferconnection.cpp => streamconnection.cpp} | 34 ++++++++--------- ...ransferconnection.h => streamconnection.h} | 14 +++---- src/libtomahawk/source.h | 2 - src/transferview.cpp | 38 +++++++++---------- src/transferview.h | 8 ++-- 10 files changed, 76 insertions(+), 80 deletions(-) rename src/libtomahawk/network/{filetransferconnection.cpp => streamconnection.cpp} (88%) rename src/libtomahawk/network/{filetransferconnection.h => streamconnection.h} (84%) diff --git a/src/libtomahawk/CMakeLists.txt b/src/libtomahawk/CMakeLists.txt index b2e54fcc7..b7f214428 100644 --- a/src/libtomahawk/CMakeLists.txt +++ b/src/libtomahawk/CMakeLists.txt @@ -123,7 +123,7 @@ set( libSources network/bufferiodevice.cpp network/msgprocessor.cpp - network/filetransferconnection.cpp + network/streamconnection.cpp network/dbsyncconnection.cpp network/remotecollection.cpp network/portfwdthread.cpp @@ -218,7 +218,7 @@ set( libHeaders network/bufferiodevice.h network/msgprocessor.h network/remotecollection.h - network/filetransferconnection.h + network/streamconnection.h network/dbsyncconnection.h network/servent.h network/connection.h diff --git a/src/libtomahawk/network/controlconnection.cpp b/src/libtomahawk/network/controlconnection.cpp index 2e122ca96..e13721ea9 100644 --- a/src/libtomahawk/network/controlconnection.cpp +++ b/src/libtomahawk/network/controlconnection.cpp @@ -18,7 +18,7 @@ #include "controlconnection.h" -#include "filetransferconnection.h" +#include "streamconnection.h" #include "database/database.h" #include "database/databasecommand_collectionstats.h" #include "dbsyncconnection.h" diff --git a/src/libtomahawk/network/controlconnection.h b/src/libtomahawk/network/controlconnection.h index 17e060579..d23c91f43 100644 --- a/src/libtomahawk/network/controlconnection.h +++ b/src/libtomahawk/network/controlconnection.h @@ -33,8 +33,6 @@ #include "dllmacro.h" -class FileTransferSession; - class DLLEXPORT ControlConnection : public Connection { Q_OBJECT diff --git a/src/libtomahawk/network/servent.cpp b/src/libtomahawk/network/servent.cpp index 0c0d8285c..107e7895e 100644 --- a/src/libtomahawk/network/servent.cpp +++ b/src/libtomahawk/network/servent.cpp @@ -33,7 +33,7 @@ #include "connection.h" #include "controlconnection.h" #include "database/database.h" -#include "filetransferconnection.h" +#include "streamconnection.h" #include "sourcelist.h" #include "portfwdthread.h" @@ -581,8 +581,8 @@ Servent::claimOffer( ControlConnection* cc, const QString &key, const QHostAddre } QString fid = key.right( key.length() - 17 ); - FileTransferConnection* ftc = new FileTransferConnection( this, cc, fid ); - return ftc; + StreamConnection* sc = new StreamConnection( this, cc, fid ); + return sc; } if( key == "whitelist" ) // LAN IP address, check source IP @@ -606,7 +606,7 @@ Servent::claimOffer( ControlConnection* cc, const QString &key, const QHostAddre QPointer conn = m_offers.value( key ); if( conn.isNull() ) { - // This can happen if it's a filetransferconnection, but the audioengine has + // This can happen if it's a streamconnection, but the audioengine has // already closed the iodevice, causing the connection to be deleted before // the peer connects and provides the first byte qDebug() << Q_FUNC_INFO << "invalid/expired offer:" << key; @@ -652,37 +652,37 @@ Servent::remoteIODeviceFactory( const result_ptr& result ) return sp; ControlConnection* cc = s->controlConnection(); - FileTransferConnection* ftc = new FileTransferConnection( this, cc, fileId, result ); - createParallelConnection( cc, ftc, QString( "FILE_REQUEST_KEY:%1" ).arg( fileId ) ); - return ftc->iodevice(); + StreamConnection* sc = new StreamConnection( this, cc, fileId, result ); + createParallelConnection( cc, sc, QString( "FILE_REQUEST_KEY:%1" ).arg( fileId ) ); + return sc->iodevice(); } void -Servent::registerFileTransferConnection( FileTransferConnection* ftc ) +Servent::registerStreamConnection( StreamConnection* sc ) { - Q_ASSERT( !m_ftsessions.contains( ftc ) ); - qDebug() << "Registering FileTransfer" << m_ftsessions.length() + 1; + Q_ASSERT( !m_scsessions.contains( sc ) ); + qDebug() << "Registering Stream" << m_scsessions.length() + 1; QMutexLocker lock( &m_ftsession_mut ); - m_ftsessions.append( ftc ); + m_scsessions.append( sc ); printCurrentTransfers(); - emit fileTransferStarted( ftc ); + emit streamStarted( sc ); } void -Servent::onFileTransferFinished( FileTransferConnection* ftc ) +Servent::onStreamFinished( StreamConnection* sc ) { - Q_ASSERT( ftc ); - qDebug() << "FileTransfer Finished, unregistering" << ftc->id(); + Q_ASSERT( sc ); + qDebug() << "Stream Finished, unregistering" << sc->id(); QMutexLocker lock( &m_ftsession_mut ); - m_ftsessions.removeAll( ftc ); + m_scsessions.removeAll( sc ); printCurrentTransfers(); - emit fileTransferFinished( ftc ); + emit streamFinished( sc ); } @@ -691,8 +691,8 @@ void Servent::printCurrentTransfers() { int k = 1; - qDebug() << "~~~ Active file transfer connections:" << m_ftsessions.length(); - foreach( FileTransferConnection* i, m_ftsessions ) + qDebug() << "~~~ Active file transfer connections:" << m_scsessions.length(); + foreach( StreamConnection* i, m_scsessions ) { qDebug() << k << ") " << i->id(); } diff --git a/src/libtomahawk/network/servent.h b/src/libtomahawk/network/servent.h index 21ef28d27..2ef98ee7b 100644 --- a/src/libtomahawk/network/servent.h +++ b/src/libtomahawk/network/servent.h @@ -46,7 +46,7 @@ class Connection; class Connector; class ControlConnection; -class FileTransferConnection; +class StreamConnection; class ProxyConnection; class RemoteCollectionConnection; class PortFwdThread; @@ -116,7 +116,7 @@ public: bool connectedToSession( const QString& session ); unsigned int numConnectedPeers() const { return m_controlconnections.length(); } - QList< FileTransferConnection* > fileTransfers() const { return m_ftsessions; } + QList< StreamConnection* > streams() const { return m_scsessions; } QSharedPointer getIODeviceForUrl( const Tomahawk::result_ptr& result ); void registerIODeviceFactory( const QString &proto, boost::function(Tomahawk::result_ptr)> fac ); @@ -124,8 +124,8 @@ public: QSharedPointer httpIODeviceFactory( const Tomahawk::result_ptr& result ); signals: - void fileTransferStarted( FileTransferConnection* ); - void fileTransferFinished( FileTransferConnection* ); + void streamStarted( StreamConnection* ); + void streamFinished( StreamConnection* ); void ready(); protected: @@ -137,8 +137,8 @@ public slots: void socketError( QAbstractSocket::SocketError ); void createParallelConnection( Connection* orig_conn, Connection* new_conn, const QString& key ); - void registerFileTransferConnection( FileTransferConnection* ); - void onFileTransferFinished( FileTransferConnection* ftc ); + void registerStreamConnection( StreamConnection* ); + void onStreamFinished( StreamConnection* sc ); void socketConnected(); void triggerDBSync(); @@ -161,7 +161,7 @@ private: QString m_externalHostname; // currently active file transfers: - QList< FileTransferConnection* > m_ftsessions; + QList< StreamConnection* > m_scsessions; QMutex m_ftsession_mut; QMap< QString,boost::function(Tomahawk::result_ptr)> > m_iofactories; diff --git a/src/libtomahawk/network/filetransferconnection.cpp b/src/libtomahawk/network/streamconnection.cpp similarity index 88% rename from src/libtomahawk/network/filetransferconnection.cpp rename to src/libtomahawk/network/streamconnection.cpp index d357a2f33..224ab7d7f 100644 --- a/src/libtomahawk/network/filetransferconnection.cpp +++ b/src/libtomahawk/network/streamconnection.cpp @@ -16,7 +16,7 @@ * along with Tomahawk. If not, see . */ -#include "filetransferconnection.h" +#include "streamconnection.h" #include @@ -31,7 +31,7 @@ using namespace Tomahawk; -FileTransferConnection::FileTransferConnection( Servent* s, ControlConnection* cc, QString fid, const Tomahawk::result_ptr& result ) +StreamConnection::StreamConnection( Servent* s, ControlConnection* cc, QString fid, const Tomahawk::result_ptr& result ) : Connection( s ) , m_cc( cc ) , m_fid( fid ) @@ -49,7 +49,7 @@ FileTransferConnection::FileTransferConnection( Servent* s, ControlConnection* c m_iodev = QSharedPointer( bio ); // device audio data gets written to m_iodev->open( QIODevice::ReadWrite ); - Servent::instance()->registerFileTransferConnection( this ); + Servent::instance()->registerStreamConnection( this ); // if the audioengine closes the iodev (skip/stop/etc) then kill the connection // immediately to avoid unnecessary network transfer @@ -65,7 +65,7 @@ FileTransferConnection::FileTransferConnection( Servent* s, ControlConnection* c } -FileTransferConnection::FileTransferConnection( Servent* s, ControlConnection* cc, QString fid ) +StreamConnection::StreamConnection( Servent* s, ControlConnection* cc, QString fid ) : Connection( s ) , m_cc( cc ) , m_fid( fid ) @@ -75,13 +75,13 @@ FileTransferConnection::FileTransferConnection( Servent* s, ControlConnection* c , m_allok( false ) , m_transferRate( 0 ) { - Servent::instance()->registerFileTransferConnection( this ); + Servent::instance()->registerStreamConnection( this ); // auto delete when connection closes: connect( this, SIGNAL( finished() ), SLOT( deleteLater() ), Qt::QueuedConnection ); } -FileTransferConnection::~FileTransferConnection() +StreamConnection::~StreamConnection() { qDebug() << Q_FUNC_INFO << "TX/RX:" << bytesSent() << bytesReceived(); if( m_type == RECEIVING && !m_allok ) @@ -95,12 +95,12 @@ FileTransferConnection::~FileTransferConnection() ((BufferIODevice*)m_iodev.data())->inputComplete(); } - Servent::instance()->onFileTransferFinished( this ); + Servent::instance()->onStreamFinished( this ); } QString -FileTransferConnection::id() const +StreamConnection::id() const { return QString( "FTC[%1 %2]" ) .arg( m_type == SENDING ? "TX" : "RX" ) @@ -109,13 +109,13 @@ FileTransferConnection::id() const Tomahawk::source_ptr -FileTransferConnection::source() const +StreamConnection::source() const { return m_source; } void -FileTransferConnection::showStats( qint64 tx, qint64 rx ) +StreamConnection::showStats( qint64 tx, qint64 rx ) { if( tx > 0 || rx > 0 ) { @@ -130,7 +130,7 @@ FileTransferConnection::showStats( qint64 tx, qint64 rx ) void -FileTransferConnection::setup() +StreamConnection::setup() { QList sources = SourceList::instance()->sources(); foreach( const source_ptr& src, sources ) @@ -163,7 +163,7 @@ FileTransferConnection::setup() void -FileTransferConnection::startSending( const Tomahawk::result_ptr& result ) +StreamConnection::startSending( const Tomahawk::result_ptr& result ) { if ( result.isNull() ) { @@ -191,7 +191,7 @@ FileTransferConnection::startSending( const Tomahawk::result_ptr& result ) void -FileTransferConnection::handleMsg( msg_ptr msg ) +StreamConnection::handleMsg( msg_ptr msg ) { Q_ASSERT( msg->is( Msg::RAW ) ); @@ -237,7 +237,7 @@ FileTransferConnection::handleMsg( msg_ptr msg ) Connection* -FileTransferConnection::clone() +StreamConnection::clone() { Q_ASSERT( false ); return 0; @@ -245,9 +245,9 @@ FileTransferConnection::clone() void -FileTransferConnection::sendSome() +StreamConnection::sendSome() { - Q_ASSERT( m_type == FileTransferConnection::SENDING ); + Q_ASSERT( m_type == StreamConnection::SENDING ); QByteArray ba = "data"; ba.append( m_readdev->read( BufferIODevice::blockSize() ) ); @@ -271,7 +271,7 @@ FileTransferConnection::sendSome() void -FileTransferConnection::onBlockRequest( int block ) +StreamConnection::onBlockRequest( int block ) { qDebug() << Q_FUNC_INFO << block; diff --git a/src/libtomahawk/network/filetransferconnection.h b/src/libtomahawk/network/streamconnection.h similarity index 84% rename from src/libtomahawk/network/filetransferconnection.h rename to src/libtomahawk/network/streamconnection.h index 330a6863a..86f0f1e71 100644 --- a/src/libtomahawk/network/filetransferconnection.h +++ b/src/libtomahawk/network/streamconnection.h @@ -16,8 +16,8 @@ * along with Tomahawk. If not, see . */ -#ifndef FILETRANSFERCONNECTION_H -#define FILETRANSFERCONNECTION_H +#ifndef STREAMCONNECTION_H +#define STREAMCONNECTION_H #include #include @@ -31,7 +31,7 @@ class ControlConnection; class BufferIODevice; -class DLLEXPORT FileTransferConnection : public Connection +class DLLEXPORT StreamConnection : public Connection { Q_OBJECT @@ -43,11 +43,11 @@ public: }; // RX: - explicit FileTransferConnection( Servent* s, ControlConnection* cc, QString fid, const Tomahawk::result_ptr& result ); + explicit StreamConnection( Servent* s, ControlConnection* cc, QString fid, const Tomahawk::result_ptr& result ); // TX: - explicit FileTransferConnection( Servent* s, ControlConnection* cc, QString fid ); + explicit StreamConnection( Servent* s, ControlConnection* cc, QString fid ); - virtual ~FileTransferConnection(); + virtual ~StreamConnection(); QString id() const; void setup(); @@ -93,4 +93,4 @@ private: qint64 m_transferRate; }; -#endif // FILETRANSFERCONNECTION_H +#endif // STREAMCONNECTION_H diff --git a/src/libtomahawk/source.h b/src/libtomahawk/source.h index 998804b70..8ab4de2e5 100644 --- a/src/libtomahawk/source.h +++ b/src/libtomahawk/source.h @@ -31,7 +31,6 @@ class DatabaseCommand_LogPlayback; class ControlConnection; -class FileTransferConnection; namespace Tomahawk { @@ -117,7 +116,6 @@ private: QString m_textStatus; ControlConnection* m_cc; - FileTransferConnection* m_ftc; }; }; diff --git a/src/transferview.cpp b/src/transferview.cpp index 8baa3d1f3..3d0e771d1 100644 --- a/src/transferview.cpp +++ b/src/transferview.cpp @@ -24,7 +24,7 @@ #include "tomahawk/tomahawkapp.h" #include "artist.h" #include "source.h" -#include "network/filetransferconnection.h" +#include "network/streamconnection.h" #include "network/servent.h" @@ -39,8 +39,8 @@ TransferView::TransferView( AnimatedSplitter* parent ) layout()->setMargin( 0 ); layout()->addWidget( m_tree ); - connect( Servent::instance(), SIGNAL( fileTransferStarted( FileTransferConnection* ) ), SLOT( fileTransferRegistered( FileTransferConnection* ) ) ); - connect( Servent::instance(), SIGNAL( fileTransferFinished( FileTransferConnection* ) ), SLOT( fileTransferFinished( FileTransferConnection* ) ) ); + connect( Servent::instance(), SIGNAL( streamStarted( StreamConnection* ) ), SLOT( streamRegistered( StreamConnection* ) ) ); + connect( Servent::instance(), SIGNAL( streamFinished( StreamConnection* ) ), SLOT( streamFinished( StreamConnection* ) ) ); QStringList headers; headers << tr( "Peer" ) << tr( "Rate" ) << tr( "Track" ); @@ -61,20 +61,20 @@ TransferView::TransferView( AnimatedSplitter* parent ) void -TransferView::fileTransferRegistered( FileTransferConnection* ftc ) +TransferView::streamRegistered( StreamConnection* sc ) { qDebug() << Q_FUNC_INFO; - connect( ftc, SIGNAL( updated() ), SLOT( onTransferUpdate() ) ); + connect( sc, SIGNAL( updated() ), SLOT( onTransferUpdate() ) ); } void -TransferView::fileTransferFinished( FileTransferConnection* ftc ) +TransferView::streamFinished( StreamConnection* sc ) { - if ( !m_index.contains( ftc ) ) + if ( !m_index.contains( sc ) ) return; - QPersistentModelIndex i = m_index.take( ftc ); + QPersistentModelIndex i = m_index.take( sc ); delete m_tree->invisibleRootItem()->takeChild( i.row() ); if ( m_tree->invisibleRootItem()->childCount() > 0 ) @@ -82,9 +82,9 @@ TransferView::fileTransferFinished( FileTransferConnection* ftc ) else emit hideWidget(); -/* if ( m_index.contains( ftc ) ) +/* if ( m_index.contains( sc ) ) { - int i = m_index.value( ftc ); + int i = m_index.value( sc ); m_tree->invisibleRootItem()->child( i )->setText( 1, tr( "Finished" ) ); }*/ } @@ -93,32 +93,32 @@ TransferView::fileTransferFinished( FileTransferConnection* ftc ) void TransferView::onTransferUpdate() { - FileTransferConnection* ftc = (FileTransferConnection*)sender(); -// qDebug() << Q_FUNC_INFO << ftc->track().isNull() << ftc->source().isNull(); + StreamConnection* sc = (StreamConnection*)sender(); +// qDebug() << Q_FUNC_INFO << sc->track().isNull() << sc->source().isNull(); - if ( ftc->track().isNull() || ftc->source().isNull() ) + if ( sc->track().isNull() || sc->source().isNull() ) return; QTreeWidgetItem* ti = 0; - if ( m_index.contains( ftc ) ) + if ( m_index.contains( sc ) ) { - QPersistentModelIndex i = m_index.value( ftc ); + QPersistentModelIndex i = m_index.value( sc ); ti = m_tree->invisibleRootItem()->child( i.row() ); } else { ti = new QTreeWidgetItem( m_tree ); - m_index.insert( ftc, QPersistentModelIndex( m_tree->model()->index( m_tree->invisibleRootItem()->childCount() - 1, 0 ) ) ); + m_index.insert( sc, QPersistentModelIndex( m_tree->model()->index( m_tree->invisibleRootItem()->childCount() - 1, 0 ) ) ); emit showWidget(); } if ( !ti ) return; - ti->setText( 0, ftc->source()->friendlyName() ); - ti->setText( 1, QString( "%1 kb/s" ).arg( ftc->transferRate() / 1024 ) ); - ti->setText( 2, QString( "%1 - %2" ).arg( ftc->track()->artist()->name() ).arg( ftc->track()->track() ) ); + ti->setText( 0, sc->source()->friendlyName() ); + ti->setText( 1, QString( "%1 kb/s" ).arg( sc->transferRate() / 1024 ) ); + ti->setText( 2, QString( "%1 - %2" ).arg( sc->track()->artist()->name() ).arg( sc->track()->track() ) ); if ( isHidden() ) emit showWidget(); diff --git a/src/transferview.h b/src/transferview.h index d006d047c..560088bbf 100644 --- a/src/transferview.h +++ b/src/transferview.h @@ -25,7 +25,7 @@ #include "typedefs.h" #include "utils/animatedsplitter.h" -class FileTransferConnection; +class StreamConnection; class TransferView : public AnimatedWidget { @@ -43,13 +43,13 @@ public: signals: private slots: - void fileTransferRegistered( FileTransferConnection* ftc ); - void fileTransferFinished( FileTransferConnection* ftc ); + void streamRegistered( StreamConnection* sc ); + void streamFinished( StreamConnection* sc ); void onTransferUpdate(); private: - QHash< FileTransferConnection*, QPersistentModelIndex > m_index; + QHash< StreamConnection*, QPersistentModelIndex > m_index; QTreeWidget* m_tree; AnimatedSplitter* m_parent; }; From 942cc89a2242b20721f191f694bc5f6b9e3cd7da Mon Sep 17 00:00:00 2001 From: Jeff Mitchell Date: Mon, 28 Mar 2011 19:25:25 -0400 Subject: [PATCH 198/329] More work on the ACL system. --- src/libtomahawk/aclsystem.cpp | 70 +++++++++++++++++++++-------- src/libtomahawk/aclsystem.h | 14 +++--- src/libtomahawk/network/servent.cpp | 2 +- 3 files changed, 62 insertions(+), 24 deletions(-) diff --git a/src/libtomahawk/aclsystem.cpp b/src/libtomahawk/aclsystem.cpp index 73ceff6ae..749b28c99 100644 --- a/src/libtomahawk/aclsystem.cpp +++ b/src/libtomahawk/aclsystem.cpp @@ -30,39 +30,73 @@ ACLSystem::ACLSystem( QObject* parent ) m_saveTimer.setSingleShot( false ); m_saveTimer.setInterval( 60000 ); connect( &m_saveTimer, SIGNAL( timeout() ), this, SLOT( saveTimerFired() ) ); + m_saveTimer.start(); } ACLSystem::~ACLSystem() { + m_saveTimer.stop(); //TODO: save from cache into settings file } +ACLSystem::ACL +ACLSystem::isAuthorizedUser(const QString& dbid) const +{ + if( !m_cache.contains( dbid ) ) + return ACLSystem::NotFound; + else + { + QHash< QString, ACL > peerHash = m_cache[dbid]; + if( peerHash.contains( "global" ) ) + return peerHash["global"]; + return ACLSystem::NotFound; + } +} + void -ACLSystem::authorize( const QString& dbid, const QString& path, ACLType type ) +ACLSystem::authorizeUser( const QString& dbid, ACLSystem::ACL globalType ) +{ + if( globalType == ACLSystem::NotFound ) + return; + + QHash< QString, ACL > peerHash; + if( m_cache.contains( dbid ) ) + peerHash = m_cache[dbid]; + + peerHash["global"] = globalType; +} + +ACLSystem::ACL +ACLSystem::isAuthorizedPath( const QString& dbid, const QString& path ) const +{ + if( !m_cache.contains( dbid ) ) + return ACLSystem::NotFound; + + QHash< QString, ACL > peerHash = m_cache[dbid]; + if( !peerHash.contains( path ) ) + { + if( peerHash.contains( "global" ) ) + return peerHash["global"]; + else + return ACLSystem::Deny; + } + return peerHash[path]; +} + +void +ACLSystem::authorizePath( const QString& dbid, const QString& path, ACLSystem::ACL type ) { TomahawkSettings *s = TomahawkSettings::instance(); - if ( !s->scannerPath().contains( path ) ) + if( !s->scannerPath().contains( path ) ) { qDebug() << "path selected is not in our scanner path!"; return; } - QHash< QString, ACLType > peerHash; - if ( m_cache.contains( "dbid" ) ) - peerHash = m_cache["dbid"]; + QHash< QString, ACLSystem::ACL > peerHash; + if ( m_cache.contains( dbid ) ) + peerHash = m_cache[dbid]; peerHash[path] = type; -} - -bool -ACLSystem::isAuthorized( const QString& dbid, const QString& path ) -{ - if ( !m_cache.contains( "dbid" ) ) - return false; - - QHash< QString, ACLType > peerHash = m_cache["dbid"]; - if ( !peerHash.contains( path ) ) - return false; - - return peerHash[path] == ACLSystem::Allow; + m_cache[dbid] = peerHash; } void diff --git a/src/libtomahawk/aclsystem.h b/src/libtomahawk/aclsystem.h index 8a6cf9d33..53de57e25 100644 --- a/src/libtomahawk/aclsystem.h +++ b/src/libtomahawk/aclsystem.h @@ -30,9 +30,10 @@ class DLLEXPORT ACLSystem : public QObject { Q_OBJECT - enum ACLType { + enum ACL { Allow, - Deny + Deny, + NotFound }; public: @@ -40,14 +41,17 @@ public: ACLSystem( QObject *parent = 0 ); ~ACLSystem(); - bool isAuthorized( const QString &dbid, const QString &path ); - void authorize( const QString &dbid, const QString &path, ACLType type ); + ACL isAuthorizedUser( const QString &dbid ) const; + void authorizeUser( const QString &dbid, ACL globalType ); + + ACL isAuthorizedPath( const QString &dbid, const QString &path ) const; + void authorizePath( const QString &dbid, const QString &path, ACL type ); private slots: void saveTimerFired(); private: - QHash< QString, QHash< QString, ACLType> > m_cache; + QHash< QString, QHash< QString, ACL> > m_cache; QTimer m_saveTimer; }; diff --git a/src/libtomahawk/network/servent.cpp b/src/libtomahawk/network/servent.cpp index 107e7895e..6569ce0ae 100644 --- a/src/libtomahawk/network/servent.cpp +++ b/src/libtomahawk/network/servent.cpp @@ -558,7 +558,7 @@ Servent::claimOffer( ControlConnection* cc, const QString &key, const QHostAddre { bool noauth = qApp->arguments().contains( "--noauth" ); - // magic key for file transfers: + // magic key for stream connections: if( key.startsWith( "FILE_REQUEST_KEY:" ) ) { // check if the source IP matches an existing, authenticated connection From 6a605789e1f8abe697856ad464863409b2501ed0 Mon Sep 17 00:00:00 2001 From: Jeffrey Finkelstein Date: Tue, 29 Mar 2011 07:41:04 +0800 Subject: [PATCH 199/329] fixed typo - changed "indemify" to "indemnify" --- src/sip/SipHandler.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/sip/SipHandler.cpp b/src/sip/SipHandler.cpp index a8284a93e..b07eb796c 100644 --- a/src/sip/SipHandler.cpp +++ b/src/sip/SipHandler.cpp @@ -183,7 +183,7 @@ SipHandler::connectPlugins( bool startup, const QString &pluginName ) { int result = QMessageBox::question( TomahawkApp::instance()->mainWindow(), "Legal Warning", - "By pressing OK below, you agree that your use of Tomahawk will be in accordance with any applicable laws, including copyright and intellectual property laws, in effect in your country of residence, and indemify the Tomahawk developers and project from liability should you choose to break those laws.\n\nFor more information, please see http://gettomahawk.com/legal", + "By pressing OK below, you agree that your use of Tomahawk will be in accordance with any applicable laws, including copyright and intellectual property laws, in effect in your country of residence, and indemnify the Tomahawk developers and project from liability should you choose to break those laws.\n\nFor more information, please see http://gettomahawk.com/legal", "I Do Not Agree", "I Agree" ); if ( result != 1 ) From e968298d7d16bee5d4fef4fb09202bafd55ed5af Mon Sep 17 00:00:00 2001 From: Jeff Mitchell Date: Tue, 29 Mar 2011 02:42:46 -0400 Subject: [PATCH 200/329] Coarse-grained ACL support is now implemented. This controls whether someone is allowed to connect to you in a global sense, and remembers the chosen value. For now there is no GUI way to revert this, but it is easily done via Tomahawk.conf --- src/libtomahawk/aclsystem.cpp | 56 ++++++++++++-- src/libtomahawk/aclsystem.h | 26 ++++--- src/libtomahawk/network/controlconnection.cpp | 3 +- src/libtomahawk/network/servent.cpp | 75 +++++++++++++++++-- src/libtomahawk/network/servent.h | 6 +- src/libtomahawk/tomahawksettings.cpp | 13 ++++ src/libtomahawk/tomahawksettings.h | 4 + 7 files changed, 157 insertions(+), 26 deletions(-) diff --git a/src/libtomahawk/aclsystem.cpp b/src/libtomahawk/aclsystem.cpp index 749b28c99..7b7d70e1f 100644 --- a/src/libtomahawk/aclsystem.cpp +++ b/src/libtomahawk/aclsystem.cpp @@ -19,14 +19,42 @@ #include "aclsystem.h" #include +#include +#include #include +ACLSystem* ACLSystem::s_instance = 0; + +ACLSystem* +ACLSystem::instance() +{ + if( !s_instance ) + new ACLSystem(); + return s_instance; +} + + ACLSystem::ACLSystem( QObject* parent ) : QObject( parent ), m_saveTimer( this ) { - //TODO: read from settings file into cache + s_instance = this; + //qRegisterMetaType< QHash< QString, QHash< QString, ACL > > >("ACLSystem::ACLCacheHash"); + + QStringList savedEntries = TomahawkSettings::instance()->aclEntries(); + if( !savedEntries.empty() && savedEntries.size() % 3 == 0 ) + { + int index = 0; + while( index < savedEntries.length() ) + { + if( !m_cache.contains( savedEntries.at( index ) ) ) + m_cache[savedEntries.at( index ) ] = QHash< QString, ACL >(); + m_cache[savedEntries.at( index )][savedEntries.at( index + 1 )] = (ACL)(savedEntries.at( index + 2 ).toInt() ); + index += 3; + } + } + m_saveTimer.setSingleShot( false ); m_saveTimer.setInterval( 60000 ); connect( &m_saveTimer, SIGNAL( timeout() ), this, SLOT( saveTimerFired() ) ); @@ -36,12 +64,16 @@ ACLSystem::ACLSystem( QObject* parent ) ACLSystem::~ACLSystem() { m_saveTimer.stop(); - //TODO: save from cache into settings file + saveTimerFired(); } ACLSystem::ACL -ACLSystem::isAuthorizedUser(const QString& dbid) const +ACLSystem::isAuthorizedUser( const QString& dbid ) { + qDebug() << Q_FUNC_INFO; + QMutexLocker locker( &m_cacheMutex ); + qDebug() << "Current cache keys = " << m_cache.keys(); + qDebug() << "Looking up dbid"; if( !m_cache.contains( dbid ) ) return ACLSystem::NotFound; else @@ -56,19 +88,24 @@ ACLSystem::isAuthorizedUser(const QString& dbid) const void ACLSystem::authorizeUser( const QString& dbid, ACLSystem::ACL globalType ) { + qDebug() << Q_FUNC_INFO; if( globalType == ACLSystem::NotFound ) return; + QMutexLocker locker( &m_cacheMutex ); + QHash< QString, ACL > peerHash; if( m_cache.contains( dbid ) ) peerHash = m_cache[dbid]; - peerHash["global"] = globalType; + m_cache[dbid] = peerHash; } ACLSystem::ACL -ACLSystem::isAuthorizedPath( const QString& dbid, const QString& path ) const +ACLSystem::isAuthorizedPath( const QString& dbid, const QString& path ) { + QMutexLocker locker( &m_cacheMutex ); + if( !m_cache.contains( dbid ) ) return ACLSystem::NotFound; @@ -92,6 +129,7 @@ ACLSystem::authorizePath( const QString& dbid, const QString& path, ACLSystem::A qDebug() << "path selected is not in our scanner path!"; return; } + QMutexLocker locker( &m_cacheMutex ); QHash< QString, ACLSystem::ACL > peerHash; if ( m_cache.contains( dbid ) ) peerHash = m_cache[dbid]; @@ -102,5 +140,11 @@ ACLSystem::authorizePath( const QString& dbid, const QString& path, ACLSystem::A void ACLSystem::saveTimerFired() { - //TODO: save from cache into settings file + QStringList saveCache; + foreach( QString dbid, m_cache.keys() ) + { + foreach( QString path, m_cache[dbid].keys() ) + saveCache << dbid << path << QString::number( (int)(m_cache[dbid][path]) ); + } + TomahawkSettings::instance()->setAclEntries( saveCache ); } \ No newline at end of file diff --git a/src/libtomahawk/aclsystem.h b/src/libtomahawk/aclsystem.h index 53de57e25..8792cfd3a 100644 --- a/src/libtomahawk/aclsystem.h +++ b/src/libtomahawk/aclsystem.h @@ -23,6 +23,7 @@ #include #include #include +#include #include "dllmacro.h" @@ -30,29 +31,34 @@ class DLLEXPORT ACLSystem : public QObject { Q_OBJECT - enum ACL { - Allow, - Deny, - NotFound - }; - public: - + + static ACLSystem* instance(); + + enum ACL { + Allow = 0, + Deny = 1, + NotFound = 2 + }; + ACLSystem( QObject *parent = 0 ); ~ACLSystem(); - ACL isAuthorizedUser( const QString &dbid ) const; + ACL isAuthorizedUser( const QString &dbid ); void authorizeUser( const QString &dbid, ACL globalType ); - ACL isAuthorizedPath( const QString &dbid, const QString &path ) const; + ACL isAuthorizedPath( const QString &dbid, const QString &path ); void authorizePath( const QString &dbid, const QString &path, ACL type ); private slots: void saveTimerFired(); private: - QHash< QString, QHash< QString, ACL> > m_cache; + QHash< QString, QHash< QString, ACL > > m_cache; QTimer m_saveTimer; + QMutex m_cacheMutex; + + static ACLSystem* s_instance; }; #endif // TOMAHAWK_ACLSYSTEM_H diff --git a/src/libtomahawk/network/controlconnection.cpp b/src/libtomahawk/network/controlconnection.cpp index e13721ea9..28fefcec3 100644 --- a/src/libtomahawk/network/controlconnection.cpp +++ b/src/libtomahawk/network/controlconnection.cpp @@ -221,7 +221,8 @@ ControlConnection::handleMsg( msg_ptr msg ) { QString theirkey = m["key"].toString(); QString ourkey = m["offer"].toString(); - servent()->reverseOfferRequest( this, ourkey, theirkey ); + QString theirdbid = m["controlid"].toString(); + servent()->reverseOfferRequest( this, theirdbid, ourkey, theirkey ); } else if( m.value( "method" ).toString() == "dbsync-offer" ) { diff --git a/src/libtomahawk/network/servent.cpp b/src/libtomahawk/network/servent.cpp index 6569ce0ae..c520e4453 100644 --- a/src/libtomahawk/network/servent.cpp +++ b/src/libtomahawk/network/servent.cpp @@ -26,6 +26,7 @@ #include #include #include +#include #include "result.h" #include "source.h" @@ -39,6 +40,8 @@ #include "portfwdthread.h" #include "tomahawksettings.h" #include "utils/tomahawkutils.h" +#include +#include using namespace Tomahawk; @@ -59,6 +62,8 @@ Servent::Servent( QObject* parent ) , m_portfwd( 0 ) { s_instance = this; + + new ACLSystem( this ); setProxy( QNetworkProxy::NoProxy ); @@ -313,8 +318,8 @@ Servent::readyRead() pport = m.value( "port" ).toInt(); nodeid = m.value( "nodeid" ).toString(); controlid = m.value( "controlid" ).toString(); - - qDebug() << m; + + qDebug() << "Incoming connection details: " << m; if( !nodeid.isEmpty() ) // only control connections send nodeid { @@ -344,7 +349,7 @@ Servent::readyRead() if( conntype == "accept-offer" || "push-offer" ) { sock->_msg.clear(); - Connection* conn = claimOffer( cc, key, sock->peerAddress() ); + Connection* conn = claimOffer( cc, nodeid, key, sock->peerAddress() ); if( !conn ) { qDebug() << "claimOffer FAILED, key:" << key; @@ -529,12 +534,12 @@ Servent::connectToPeer( const QString& ha, int port, const QString &key, Connect void -Servent::reverseOfferRequest( ControlConnection* orig_conn, const QString& key, const QString& theirkey ) +Servent::reverseOfferRequest( ControlConnection* orig_conn, const QString &theirdbid, const QString& key, const QString& theirkey ) { Q_ASSERT( this->thread() == QThread::currentThread() ); qDebug() << "Servent::reverseOfferRequest received for" << key; - Connection* new_conn = claimOffer( orig_conn, key ); + Connection* new_conn = claimOffer( orig_conn, theirdbid, key ); if ( !new_conn ) { qDebug() << "claimOffer failed, killing requesting connection out of spite"; @@ -554,8 +559,10 @@ Servent::reverseOfferRequest( ControlConnection* orig_conn, const QString& key, // return the appropriate connection for a given offer key, or NULL if invalid Connection* -Servent::claimOffer( ControlConnection* cc, const QString &key, const QHostAddress peer ) +Servent::claimOffer( ControlConnection* cc, const QString &nodeid, const QString &key, const QHostAddress peer ) { + qDebug() << Q_FUNC_INFO; + bool noauth = qApp->arguments().contains( "--noauth" ); // magic key for stream connections: @@ -613,6 +620,18 @@ Servent::claimOffer( ControlConnection* cc, const QString &key, const QHostAddre return NULL; } + if( !nodeid.isEmpty() ) + { + // If there isn't a nodeid it's not the first connection and will already have been stopped + if( !checkACL( conn, nodeid, true ) ) + { + qDebug() << "Connection not allowed due to ACL"; + return NULL; + } + } + + qDebug() << "ACL has allowed the connection"; + if( conn->onceOnly() ) { m_offers.remove( key ); @@ -637,6 +656,50 @@ Servent::claimOffer( ControlConnection* cc, const QString &key, const QHostAddre } } +bool +Servent::checkACL( const Connection* conn, const QString &nodeid, bool showDialog ) const +{ + qDebug() << "Checking ACLs"; + ACLSystem* aclSystem = ACLSystem::instance(); + ACLSystem::ACL peerStatus = aclSystem->isAuthorizedUser( nodeid ); + if( peerStatus == ACLSystem::Deny ) + return false; + +#ifndef TOMAHAWK_HEADLESS + if( peerStatus == ACLSystem::NotFound ) + { + if( !showDialog ) + return false; + + qDebug() << "ACL for this node not found"; + QMessageBox msgBox; + msgBox.setIcon( QMessageBox::Question ); + msgBox.setText( "Incoming Connection Attempt" ); + msgBox.setInformativeText( QString( "Another Tomahawk instance is attempting to connect to you. Select whether to allow or deny this connection.\n\nPeer name: %1\nPeer ID: %2\n\nRemember: Only allow peers to connect if you have the legal right for them to stream music from you.").arg( conn->name(), nodeid ) ); + QPushButton *denyButton = msgBox.addButton( "Deny", QMessageBox::HelpRole ); + QPushButton *alwaysDenyButton = msgBox.addButton( "Always Deny", QMessageBox::YesRole ); + QPushButton *allowButton = msgBox.addButton( "Allow", QMessageBox::NoRole ); + QPushButton *alwaysAllowButton = msgBox.addButton( "Always Allow", QMessageBox::ActionRole ); + + msgBox.setDefaultButton( denyButton ); + msgBox.setEscapeButton( denyButton ); + + msgBox.exec(); + + if( msgBox.clickedButton() == denyButton ) + return false; + else if( msgBox.clickedButton() == alwaysDenyButton ) + { + aclSystem->authorizeUser( nodeid, ACLSystem::Deny ); + return false; + } + else if( msgBox.clickedButton() == alwaysAllowButton ) + aclSystem->authorizeUser( nodeid, ACLSystem::Allow ); + } +#endif + + return true; +} QSharedPointer Servent::remoteIODeviceFactory( const result_ptr& result ) diff --git a/src/libtomahawk/network/servent.h b/src/libtomahawk/network/servent.h index 2ef98ee7b..4e9c37035 100644 --- a/src/libtomahawk/network/servent.h +++ b/src/libtomahawk/network/servent.h @@ -104,7 +104,7 @@ public: void connectToPeer( const QString& ha, int port, const QString &key, const QString& name = "", const QString& id = "" ); void connectToPeer( const QString& ha, int port, const QString &key, Connection* conn ); - void reverseOfferRequest( ControlConnection* orig_conn, const QString& key, const QString& theirkey ); + void reverseOfferRequest( ControlConnection* orig_conn, const QString &theirdbid, const QString& key, const QString& theirkey ); bool visibleExternally() const { return !m_externalHostname.isNull() || (m_externalPort > 0 && !m_externalAddress.isNull()); } QString externalAddress() const { return !m_externalHostname.isNull() ? m_externalHostname : m_externalAddress.toString(); } @@ -146,11 +146,11 @@ public slots: private slots: void readyRead(); - Connection* claimOffer( ControlConnection* cc, const QString &key, const QHostAddress peer = QHostAddress::Any ); + Connection* claimOffer( ControlConnection* cc, const QString &nodeid, const QString &key, const QHostAddress peer = QHostAddress::Any ); private: void handoverSocket( Connection* conn, QTcpSocketExtra* sock ); - + bool checkACL( const Connection* conn, const QString &nodeid, bool showDialog ) const; void printCurrentTransfers(); QJson::Parser parser; diff --git a/src/libtomahawk/tomahawksettings.cpp b/src/libtomahawk/tomahawksettings.cpp index ce796b8e6..82d04d75c 100644 --- a/src/libtomahawk/tomahawksettings.cpp +++ b/src/libtomahawk/tomahawksettings.cpp @@ -185,6 +185,19 @@ TomahawkSettings::setProxyType( const int type ) } +QStringList +TomahawkSettings::aclEntries() const +{ + return value( "acl/entries", QStringList() ).toStringList(); +} + +void +TomahawkSettings::setAclEntries( const QStringList &entries ) +{ + setValue( "acl/entries", entries ); +} + + QByteArray TomahawkSettings::mainWindowGeometry() const { diff --git a/src/libtomahawk/tomahawksettings.h b/src/libtomahawk/tomahawksettings.h index 2e9303144..cc2b2923f 100644 --- a/src/libtomahawk/tomahawksettings.h +++ b/src/libtomahawk/tomahawksettings.h @@ -114,6 +114,10 @@ public: int proxyType() const; void setProxyType( const int type ); + /// ACL settings + QStringList aclEntries() const; + void setAclEntries( const QStringList &entries ); + /// Last.fm settings bool scrobblingEnabled() const; /// false by default void setScrobblingEnabled( bool enable ); From b70491cc63d4410816ef864ded2225ab42f9202f Mon Sep 17 00:00:00 2001 From: Jeff Mitchell Date: Tue, 29 Mar 2011 08:11:23 -0400 Subject: [PATCH 201/329] Disable acl box for now --- src/libtomahawk/network/servent.cpp | 2 ++ 1 file changed, 2 insertions(+) diff --git a/src/libtomahawk/network/servent.cpp b/src/libtomahawk/network/servent.cpp index c520e4453..182c8a2f4 100644 --- a/src/libtomahawk/network/servent.cpp +++ b/src/libtomahawk/network/servent.cpp @@ -666,6 +666,8 @@ Servent::checkACL( const Connection* conn, const QString &nodeid, bool showDialo return false; #ifndef TOMAHAWK_HEADLESS + //FIXME: Actually enable it when it makes sense + return true; if( peerStatus == ACLSystem::NotFound ) { if( !showDialog ) From 7c8b65a1d3297c5f9e34034aec874d335119c92b Mon Sep 17 00:00:00 2001 From: Jeff Mitchell Date: Tue, 29 Mar 2011 09:58:14 -0400 Subject: [PATCH 202/329] Hopefully fix TWK-13 crash bug. --- src/sip/twitter/twitter.cpp | 68 +++++++++++++------------------------ 1 file changed, 24 insertions(+), 44 deletions(-) diff --git a/src/sip/twitter/twitter.cpp b/src/sip/twitter/twitter.cpp index 843de4505..d74481ab3 100644 --- a/src/sip/twitter/twitter.cpp +++ b/src/sip/twitter/twitter.cpp @@ -54,12 +54,10 @@ TwitterPlugin::TwitterPlugin() m_checkTimer.setInterval( 60000 ); m_checkTimer.setSingleShot( false ); connect( &m_checkTimer, SIGNAL( timeout() ), SLOT( checkTimerFired() ) ); - m_checkTimer.start(); m_connectTimer.setInterval( 60000 ); m_connectTimer.setSingleShot( false ); connect( &m_connectTimer, SIGNAL( timeout() ), SLOT( connectTimerFired() ) ); - m_connectTimer.start(); } void @@ -144,12 +142,14 @@ TwitterPlugin::connectPlugin( bool /*startup*/ ) bool TwitterPlugin::refreshTwitterAuth() { + if( !m_twitterAuth.isNull() ) + delete m_twitterAuth.data(); m_twitterAuth = QWeakPointer( new TomahawkOAuthTwitter( this ) ); - - TomahawkSettings *settings = TomahawkSettings::instance(); - - if ( m_twitterAuth.isNull() ) + + if( m_twitterAuth.isNull() ) return false; + + TomahawkSettings *settings = TomahawkSettings::instance(); m_twitterAuth.data()->setNetworkAccessManager( TomahawkUtils::nam() ); m_twitterAuth.data()->setOAuthToken( settings->twitterOAuthToken().toLatin1() ); @@ -162,14 +162,23 @@ void TwitterPlugin::disconnectPlugin() { qDebug() << Q_FUNC_INFO; + m_checkTimer.stop(); + m_connectTimer.stop(); if( !m_friendsTimeline.isNull() ) - m_friendsTimeline.data()->deleteLater(); + delete m_friendsTimeline.data(); + if( !m_mentions.isNull() ) + delete m_mentions.data(); + if( !m_directMessages.isNull() ) + delete m_directMessages.data(); + if( !m_directMessageNew.isNull() ) + delete m_directMessageNew.data(); + if( !m_directMessageDestroy.isNull() ) + delete m_directMessageDestroy.data(); if( !m_twitterAuth.isNull() ) - m_twitterAuth.data()->deleteLater(); - + delete m_twitterAuth.data(); + m_cachedPeers.empty(); m_attemptedConnects.empty(); - delete m_twitterAuth.data(); m_isOnline = false; } @@ -200,6 +209,8 @@ TwitterPlugin::connectAuthVerifyReply( const QTweetUser &user ) connect( m_directMessageNew.data(), SIGNAL( error(QTweetNetBase::ErrorCode, const QString &) ), SLOT( directMessagePostError(QTweetNetBase::ErrorCode, const QString &) ) ); connect( m_directMessageDestroy.data(), SIGNAL( parsedDirectMessage(const QTweetDMStatus &) ), SLOT( directMessageDestroyed(const QTweetDMStatus &) ) ); m_isOnline = true; + m_connectTimer.start(); + m_checkTimer.start(); QMetaObject::invokeMethod( this, "checkTimerFired", Qt::AutoConnection ); QTimer::singleShot( 20000, this, SLOT( connectTimerFired() ) ); } @@ -223,32 +234,16 @@ TwitterPlugin::connectAuthVerifyReply( const QTweetUser &user ) void TwitterPlugin::checkTimerFired() { - if ( !isValid() ) + if ( !isValid() || m_twitterAuth.isNull() ) return; - if ( m_twitterAuth.isNull() ) - { - if ( refreshTwitterAuth() ) - { - QTweetAccountVerifyCredentials *credVerifier = new QTweetAccountVerifyCredentials( m_twitterAuth.data(), this ); - connect( credVerifier, SIGNAL( parsedUser(const QTweetUser &) ), SLOT( connectAuthVerifyReply(const QTweetUser &) ) ); - credVerifier->verify(); - } - else - { - qDebug() << "TwitterPlugin auth went null somehow and could not refresh"; - return; - } - } - if ( m_cachedFriendsSinceId == 0 ) m_cachedFriendsSinceId = TomahawkSettings::instance()->twitterCachedFriendsSinceId(); qDebug() << "TwitterPlugin looking at friends timeline since id " << m_cachedFriendsSinceId; if ( !m_friendsTimeline.isNull() ) - m_friendsTimeline.data()->fetch( m_cachedFriendsSinceId, 0, 800 ); - + m_friendsTimeline.data()->fetch( m_cachedFriendsSinceId, 0, 800 ); if ( m_cachedMentionsSinceId == 0 ) m_cachedMentionsSinceId = TomahawkSettings::instance()->twitterCachedMentionsSinceId(); @@ -262,24 +257,9 @@ TwitterPlugin::checkTimerFired() void TwitterPlugin::connectTimerFired() { - if ( !isValid() || m_cachedPeers.isEmpty() ) + if ( !isValid() || m_cachedPeers.isEmpty() || m_twitterAuth.isNull() ) return; - if ( m_twitterAuth.isNull() ) - { - if ( refreshTwitterAuth() ) - { - QTweetAccountVerifyCredentials *credVerifier = new QTweetAccountVerifyCredentials( m_twitterAuth.data(), this ); - connect( credVerifier, SIGNAL( parsedUser(const QTweetUser &) ), SLOT( connectAuthVerifyReply(const QTweetUser &) ) ); - credVerifier->verify(); - } - else - { - qDebug() << "TwitterPlugin auth went null somehow and could not refresh"; - return; - } - } - QString myScreenName = TomahawkSettings::instance()->twitterScreenName(); QList peerlist = m_cachedPeers.keys(); qStableSort( peerlist.begin(), peerlist.end() ); From 0a0e27c717c056c798fcffca128d896ebc136ab8 Mon Sep 17 00:00:00 2001 From: Jeff Mitchell Date: Tue, 29 Mar 2011 09:58:14 -0400 Subject: [PATCH 203/329] Hopefully fix TWK-13 crash bug. --- src/sip/twitter/twitter.cpp | 68 +++++++++++++------------------------ 1 file changed, 24 insertions(+), 44 deletions(-) diff --git a/src/sip/twitter/twitter.cpp b/src/sip/twitter/twitter.cpp index 843de4505..d74481ab3 100644 --- a/src/sip/twitter/twitter.cpp +++ b/src/sip/twitter/twitter.cpp @@ -54,12 +54,10 @@ TwitterPlugin::TwitterPlugin() m_checkTimer.setInterval( 60000 ); m_checkTimer.setSingleShot( false ); connect( &m_checkTimer, SIGNAL( timeout() ), SLOT( checkTimerFired() ) ); - m_checkTimer.start(); m_connectTimer.setInterval( 60000 ); m_connectTimer.setSingleShot( false ); connect( &m_connectTimer, SIGNAL( timeout() ), SLOT( connectTimerFired() ) ); - m_connectTimer.start(); } void @@ -144,12 +142,14 @@ TwitterPlugin::connectPlugin( bool /*startup*/ ) bool TwitterPlugin::refreshTwitterAuth() { + if( !m_twitterAuth.isNull() ) + delete m_twitterAuth.data(); m_twitterAuth = QWeakPointer( new TomahawkOAuthTwitter( this ) ); - - TomahawkSettings *settings = TomahawkSettings::instance(); - - if ( m_twitterAuth.isNull() ) + + if( m_twitterAuth.isNull() ) return false; + + TomahawkSettings *settings = TomahawkSettings::instance(); m_twitterAuth.data()->setNetworkAccessManager( TomahawkUtils::nam() ); m_twitterAuth.data()->setOAuthToken( settings->twitterOAuthToken().toLatin1() ); @@ -162,14 +162,23 @@ void TwitterPlugin::disconnectPlugin() { qDebug() << Q_FUNC_INFO; + m_checkTimer.stop(); + m_connectTimer.stop(); if( !m_friendsTimeline.isNull() ) - m_friendsTimeline.data()->deleteLater(); + delete m_friendsTimeline.data(); + if( !m_mentions.isNull() ) + delete m_mentions.data(); + if( !m_directMessages.isNull() ) + delete m_directMessages.data(); + if( !m_directMessageNew.isNull() ) + delete m_directMessageNew.data(); + if( !m_directMessageDestroy.isNull() ) + delete m_directMessageDestroy.data(); if( !m_twitterAuth.isNull() ) - m_twitterAuth.data()->deleteLater(); - + delete m_twitterAuth.data(); + m_cachedPeers.empty(); m_attemptedConnects.empty(); - delete m_twitterAuth.data(); m_isOnline = false; } @@ -200,6 +209,8 @@ TwitterPlugin::connectAuthVerifyReply( const QTweetUser &user ) connect( m_directMessageNew.data(), SIGNAL( error(QTweetNetBase::ErrorCode, const QString &) ), SLOT( directMessagePostError(QTweetNetBase::ErrorCode, const QString &) ) ); connect( m_directMessageDestroy.data(), SIGNAL( parsedDirectMessage(const QTweetDMStatus &) ), SLOT( directMessageDestroyed(const QTweetDMStatus &) ) ); m_isOnline = true; + m_connectTimer.start(); + m_checkTimer.start(); QMetaObject::invokeMethod( this, "checkTimerFired", Qt::AutoConnection ); QTimer::singleShot( 20000, this, SLOT( connectTimerFired() ) ); } @@ -223,32 +234,16 @@ TwitterPlugin::connectAuthVerifyReply( const QTweetUser &user ) void TwitterPlugin::checkTimerFired() { - if ( !isValid() ) + if ( !isValid() || m_twitterAuth.isNull() ) return; - if ( m_twitterAuth.isNull() ) - { - if ( refreshTwitterAuth() ) - { - QTweetAccountVerifyCredentials *credVerifier = new QTweetAccountVerifyCredentials( m_twitterAuth.data(), this ); - connect( credVerifier, SIGNAL( parsedUser(const QTweetUser &) ), SLOT( connectAuthVerifyReply(const QTweetUser &) ) ); - credVerifier->verify(); - } - else - { - qDebug() << "TwitterPlugin auth went null somehow and could not refresh"; - return; - } - } - if ( m_cachedFriendsSinceId == 0 ) m_cachedFriendsSinceId = TomahawkSettings::instance()->twitterCachedFriendsSinceId(); qDebug() << "TwitterPlugin looking at friends timeline since id " << m_cachedFriendsSinceId; if ( !m_friendsTimeline.isNull() ) - m_friendsTimeline.data()->fetch( m_cachedFriendsSinceId, 0, 800 ); - + m_friendsTimeline.data()->fetch( m_cachedFriendsSinceId, 0, 800 ); if ( m_cachedMentionsSinceId == 0 ) m_cachedMentionsSinceId = TomahawkSettings::instance()->twitterCachedMentionsSinceId(); @@ -262,24 +257,9 @@ TwitterPlugin::checkTimerFired() void TwitterPlugin::connectTimerFired() { - if ( !isValid() || m_cachedPeers.isEmpty() ) + if ( !isValid() || m_cachedPeers.isEmpty() || m_twitterAuth.isNull() ) return; - if ( m_twitterAuth.isNull() ) - { - if ( refreshTwitterAuth() ) - { - QTweetAccountVerifyCredentials *credVerifier = new QTweetAccountVerifyCredentials( m_twitterAuth.data(), this ); - connect( credVerifier, SIGNAL( parsedUser(const QTweetUser &) ), SLOT( connectAuthVerifyReply(const QTweetUser &) ) ); - credVerifier->verify(); - } - else - { - qDebug() << "TwitterPlugin auth went null somehow and could not refresh"; - return; - } - } - QString myScreenName = TomahawkSettings::instance()->twitterScreenName(); QList peerlist = m_cachedPeers.keys(); qStableSort( peerlist.begin(), peerlist.end() ); From b59399aecba8602ec753708c81dbdfc16002d2bc Mon Sep 17 00:00:00 2001 From: Jeff Mitchell Date: Tue, 29 Mar 2011 16:22:08 -0400 Subject: [PATCH 204/329] Changelogify --- ChangeLog | 1 + 1 file changed, 1 insertion(+) diff --git a/ChangeLog b/ChangeLog index 89a441c90..c3a791448 100644 --- a/ChangeLog +++ b/ChangeLog @@ -1,4 +1,5 @@ Version 0.0.3: + * Fix crashes in Twitter authentication. For reals now. * Properly honor the chosen port number if a static host and port are marked as preferred. From 3d25e6aefe710a850a6531a331053f08309bf4ac Mon Sep 17 00:00:00 2001 From: Jeff Mitchell Date: Tue, 29 Mar 2011 16:33:44 -0400 Subject: [PATCH 205/329] Add some twitter debug --- src/sip/twitter/twitter.cpp | 3 +++ 1 file changed, 3 insertions(+) diff --git a/src/sip/twitter/twitter.cpp b/src/sip/twitter/twitter.cpp index d74481ab3..b33b77666 100644 --- a/src/sip/twitter/twitter.cpp +++ b/src/sip/twitter/twitter.cpp @@ -291,6 +291,7 @@ TwitterPlugin::parseGotTomahawk( const QRegExp ®ex, const QString &screenName QString node; for ( int i = 0; i < regex.captureCount(); ++i ) { + qDebug() << "Parsing regex captures, current cap = " << regex.cap( i ); if ( regex.cap( i ) == QString( "Got Tomahawk?" ) ) { QString nodeCap = regex.cap( i + 1 ); @@ -350,6 +351,7 @@ TwitterPlugin::friendsTimelineStatuses( const QList< QTweetStatus > &statuses ) if ( status.id() > m_cachedFriendsSinceId ) m_cachedFriendsSinceId = status.id(); + qDebug() << "TwitterPlugin checking mention from " << status.user().screenName() << " with content " << status.text(); parseGotTomahawk( regex, status.user().screenName(), status.text() ); } @@ -385,6 +387,7 @@ TwitterPlugin::mentionsStatuses( const QList< QTweetStatus > &statuses ) if ( status.id() > m_cachedMentionsSinceId ) m_cachedMentionsSinceId = status.id(); + qDebug() << "TwitterPlugin checking mention from " << status.user().screenName() << " with content " << status.text(); parseGotTomahawk( regex, status.user().screenName(), status.text() ); } From a09167050fd80f3009d8e2dc6ccc95da625e494b Mon Sep 17 00:00:00 2001 From: Jeff Mitchell Date: Tue, 29 Mar 2011 17:06:50 -0400 Subject: [PATCH 206/329] Re-match the regex --- src/sip/twitter/twitter.cpp | 1 + 1 file changed, 1 insertion(+) diff --git a/src/sip/twitter/twitter.cpp b/src/sip/twitter/twitter.cpp index b33b77666..2e28b64e5 100644 --- a/src/sip/twitter/twitter.cpp +++ b/src/sip/twitter/twitter.cpp @@ -282,6 +282,7 @@ TwitterPlugin::parseGotTomahawk( const QRegExp ®ex, const QString &screenName { QString myScreenName = TomahawkSettings::instance()->twitterScreenName(); qDebug() << "TwitterPlugin found an exact matching Got Tomahawk? mention or direct message from user " << screenName; + regex.exactMatch( text ); if ( text.startsWith( '@' ) && regex.captureCount() >= 2 && regex.cap( 1 ) != QString( '@' + myScreenName ) ) { qDebug() << "TwitterPlugin skipping mention because it's directed @someone that isn't us"; From f767818966126b665157b4aa171526317a2d6523 Mon Sep 17 00:00:00 2001 From: Jeff Mitchell Date: Tue, 29 Mar 2011 17:27:50 -0400 Subject: [PATCH 207/329] Update some debug in twitter --- src/sip/twitter/twitter.cpp | 5 ++--- 1 file changed, 2 insertions(+), 3 deletions(-) diff --git a/src/sip/twitter/twitter.cpp b/src/sip/twitter/twitter.cpp index 2e28b64e5..655dd832b 100644 --- a/src/sip/twitter/twitter.cpp +++ b/src/sip/twitter/twitter.cpp @@ -269,7 +269,7 @@ TwitterPlugin::connectTimerFired() if ( !peerData.contains( "host" ) || !peerData.contains( "port" ) || !peerData.contains( "pkey" ) ) { - qDebug() << "TwitterPlugin does not have host, port and/or pkey values for " << screenName; + qDebug() << "TwitterPlugin does not have host, port and/or pkey values for " << screenName << " (this is usually *not* a bug or problem but a normal part of the process)"; continue; } @@ -281,7 +281,7 @@ void TwitterPlugin::parseGotTomahawk( const QRegExp ®ex, const QString &screenName, const QString &text ) { QString myScreenName = TomahawkSettings::instance()->twitterScreenName(); - qDebug() << "TwitterPlugin found an exact matching Got Tomahawk? mention or direct message from user " << screenName; + qDebug() << "TwitterPlugin found an exact matching Got Tomahawk? mention or direct message from user " << screenName << ", now parsing"; regex.exactMatch( text ); if ( text.startsWith( '@' ) && regex.captureCount() >= 2 && regex.cap( 1 ) != QString( '@' + myScreenName ) ) { @@ -292,7 +292,6 @@ TwitterPlugin::parseGotTomahawk( const QRegExp ®ex, const QString &screenName QString node; for ( int i = 0; i < regex.captureCount(); ++i ) { - qDebug() << "Parsing regex captures, current cap = " << regex.cap( i ); if ( regex.cap( i ) == QString( "Got Tomahawk?" ) ) { QString nodeCap = regex.cap( i + 1 ); From b8721ad75ad5c358b1fa4aa16e8055b70103d2a6 Mon Sep 17 00:00:00 2001 From: Jeff Mitchell Date: Tue, 29 Mar 2011 16:33:44 -0400 Subject: [PATCH 208/329] Add some twitter debug --- src/sip/twitter/twitter.cpp | 3 +++ 1 file changed, 3 insertions(+) diff --git a/src/sip/twitter/twitter.cpp b/src/sip/twitter/twitter.cpp index d74481ab3..b33b77666 100644 --- a/src/sip/twitter/twitter.cpp +++ b/src/sip/twitter/twitter.cpp @@ -291,6 +291,7 @@ TwitterPlugin::parseGotTomahawk( const QRegExp ®ex, const QString &screenName QString node; for ( int i = 0; i < regex.captureCount(); ++i ) { + qDebug() << "Parsing regex captures, current cap = " << regex.cap( i ); if ( regex.cap( i ) == QString( "Got Tomahawk?" ) ) { QString nodeCap = regex.cap( i + 1 ); @@ -350,6 +351,7 @@ TwitterPlugin::friendsTimelineStatuses( const QList< QTweetStatus > &statuses ) if ( status.id() > m_cachedFriendsSinceId ) m_cachedFriendsSinceId = status.id(); + qDebug() << "TwitterPlugin checking mention from " << status.user().screenName() << " with content " << status.text(); parseGotTomahawk( regex, status.user().screenName(), status.text() ); } @@ -385,6 +387,7 @@ TwitterPlugin::mentionsStatuses( const QList< QTweetStatus > &statuses ) if ( status.id() > m_cachedMentionsSinceId ) m_cachedMentionsSinceId = status.id(); + qDebug() << "TwitterPlugin checking mention from " << status.user().screenName() << " with content " << status.text(); parseGotTomahawk( regex, status.user().screenName(), status.text() ); } From 3657579c32a4c0f1216102e475c88f13feefe9fa Mon Sep 17 00:00:00 2001 From: Jeff Mitchell Date: Tue, 29 Mar 2011 17:06:50 -0400 Subject: [PATCH 209/329] Re-match the regex --- src/sip/twitter/twitter.cpp | 1 + 1 file changed, 1 insertion(+) diff --git a/src/sip/twitter/twitter.cpp b/src/sip/twitter/twitter.cpp index b33b77666..2e28b64e5 100644 --- a/src/sip/twitter/twitter.cpp +++ b/src/sip/twitter/twitter.cpp @@ -282,6 +282,7 @@ TwitterPlugin::parseGotTomahawk( const QRegExp ®ex, const QString &screenName { QString myScreenName = TomahawkSettings::instance()->twitterScreenName(); qDebug() << "TwitterPlugin found an exact matching Got Tomahawk? mention or direct message from user " << screenName; + regex.exactMatch( text ); if ( text.startsWith( '@' ) && regex.captureCount() >= 2 && regex.cap( 1 ) != QString( '@' + myScreenName ) ) { qDebug() << "TwitterPlugin skipping mention because it's directed @someone that isn't us"; From 52fcc7b53f0ca76295f114871de333eb3cbf6404 Mon Sep 17 00:00:00 2001 From: Jeff Mitchell Date: Tue, 29 Mar 2011 17:27:50 -0400 Subject: [PATCH 210/329] Update some debug in twitter --- src/sip/twitter/twitter.cpp | 5 ++--- 1 file changed, 2 insertions(+), 3 deletions(-) diff --git a/src/sip/twitter/twitter.cpp b/src/sip/twitter/twitter.cpp index 2e28b64e5..655dd832b 100644 --- a/src/sip/twitter/twitter.cpp +++ b/src/sip/twitter/twitter.cpp @@ -269,7 +269,7 @@ TwitterPlugin::connectTimerFired() if ( !peerData.contains( "host" ) || !peerData.contains( "port" ) || !peerData.contains( "pkey" ) ) { - qDebug() << "TwitterPlugin does not have host, port and/or pkey values for " << screenName; + qDebug() << "TwitterPlugin does not have host, port and/or pkey values for " << screenName << " (this is usually *not* a bug or problem but a normal part of the process)"; continue; } @@ -281,7 +281,7 @@ void TwitterPlugin::parseGotTomahawk( const QRegExp ®ex, const QString &screenName, const QString &text ) { QString myScreenName = TomahawkSettings::instance()->twitterScreenName(); - qDebug() << "TwitterPlugin found an exact matching Got Tomahawk? mention or direct message from user " << screenName; + qDebug() << "TwitterPlugin found an exact matching Got Tomahawk? mention or direct message from user " << screenName << ", now parsing"; regex.exactMatch( text ); if ( text.startsWith( '@' ) && regex.captureCount() >= 2 && regex.cap( 1 ) != QString( '@' + myScreenName ) ) { @@ -292,7 +292,6 @@ TwitterPlugin::parseGotTomahawk( const QRegExp ®ex, const QString &screenName QString node; for ( int i = 0; i < regex.captureCount(); ++i ) { - qDebug() << "Parsing regex captures, current cap = " << regex.cap( i ); if ( regex.cap( i ) == QString( "Got Tomahawk?" ) ) { QString nodeCap = regex.cap( i + 1 ); From c61813915df7be87cfc06555057ed40db523ff76 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Julian=20B=C3=A4ume?= Date: Wed, 30 Mar 2011 06:53:54 +0800 Subject: [PATCH 211/329] initial german translation it's not finished, but a first start --- lang/tomahawk_de.ts | 1484 +++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 1484 insertions(+) create mode 100644 lang/tomahawk_de.ts diff --git a/lang/tomahawk_de.ts b/lang/tomahawk_de.ts new file mode 100644 index 000000000..1a028aea7 --- /dev/null +++ b/lang/tomahawk_de.ts @@ -0,0 +1,1484 @@ + + + + + AlbumModel + + + Album + Album + + + + + All albums from %1 + Alle Alben von %1 + + + + AudioControls + + + Form + + + + + Prev + + + + + Play + + + + + Pause + + + + + Next + + + + + TextLabel + + + + + Artist + + + + + Album + + + + + Owner + + + + + Time + + + + + Time Left + + + + + Shuffle + + + + + Repeat + + + + + Low + + + + + High + + + + + ClearButton + + + Clear + Leeren + + + + CollectionFlatModel + + + Your Collection + Deine Sammlung + + + + Collection of %1 + Deine Sammlung von %1 + + + + CollectionModel + + + Name + Name + + + + Tracks + Stücke + + + + Duration + Spieldauer + + + + Origin + Quelle + + + + CollectionView + + + &Play + &Abspielen + + + + Add to &Queue + Zur &Warteschlange hinzufügen + + + + This collection is empty. + Diese Sammlung ist leer. + + + + InfoBar + + + InfoBar + + + + + + + TextLabel + + + + + JabberPlugin + + + Add Friend... + Freund hinzufügen… + + + + + Add Friend + Freund hinzufügen + + + + + Enter Jabber ID: + Jabber-ID eingeben: + + + + Jabber_p + + + Authorize User + Benutzer authorisieren + + + + Do you want to grant <b>%1</b> access to your Collection? + Willst du <b>%1</b> wirklich den Zugriff auf deine Sammlung erlauben? + + + + NewPlaylistWidget + + + Enter a title for the new playlist: + Gib einen Titel für die neue Playliste ein: + + + + Tomahawk offers a variety of ways to help you create playlists and find music you enjoy! + Tomahawk bietet verschiedene Wege, Playlisten zu erstellen und Musik zu finden, die du magst! + + + + Just enter a genre or tag name and Tomahawk will suggest a few songs to get you started with your new playlist: + Gib einfach ein Genre oder einen Tagnamen ein und Tomahawk wird dir einige Lieder vorschlagen, um dir zu helfen, eine neue Playliste zu erstellen: + + + + &Create Playlist + Playliste &erstellen + + + + Create a new playlist + Erstelle eine neue Playliste + + + + PlaylistDelegate + + + %1 tracks + %1 Stücke + + + + PlaylistManager + + + All available tracks + Alle verfügbaren Stücke + + + + All available albums + Alle verfügbaren Alben + + + + PlaylistModel + + + A playlist by %1 + Eine Playliste von %1 + + + + you + dir + + + + PlaylistView + + + &Play + &Abspielen + + + + Add to &Queue + In &Warteschlange einreihen + + + + &Delete Items + Elemente &entfernen + + + + &Delete Item + Element &entfernen + + + + This playlist is currently empty. Add some tracks to it and enjoy the music! + Die Playliste ist derzeit leer. Füge einige Stücke hinzu und genieße die Musik! + + + + ProxyDialog + + + Proxy Settings + Proxy-Einstellungen + + + + Host + Rechnername + + + + Port + Port + + + + User + Benutzer + + + + Password + Passwort + + + + Type + Typ + + + + QueueView + + + + Click to show queue + Klicke hier, um die Warteschlange anzuzeigen + + + + Click to hide queue + Klicke hier, um die Warteschlange auszublenden + + + + SearchLineEdit + + + Search + Suchen + + + + SettingsDialog + + + Music Player Settings + Übersetzung eher dürftig + Einstellungen für das Musikabspielprogramm + + + + Jabber + Jabber + + + + Jabber ID: + Jabber-ID: + + + + + Password: + Passwort: + + + + Advanced Jabber Settings + Erweiterte Einstellungen für Jabber + + + + Server: + Server: + + + + Port: + Port: + + + + Network + Netzwerk + + + + Advanced Network Settings + Erweiterte Netzwerkeinstellungen + + + + If you're having difficulty connecting to peers, try setting this to your external IP address/host name and a port number (default 50210). Make sure to forward that port to this machine! + Wenn du Schwierigkeiten hast, zu anderen Leuten zu verbinden, versuche diene externe IP-Addresse/Rechnernamen und eine Portnummer (Standard 50210) hier einzutragen. Stelle sicher, den Port entsprechend an diesen Rechner weiterzuleiten! + + + + Static Host Name: + Statischer Rechnername: + + + + Static Port: + Statischer Port: + + + + Always use static host name/port? (Overrides UPnP discovery/port forwarding) + Statischen Rechnernamen/Port immer benutzen? (Überschreibt UPnP-discovery/Portweiterleitung) + + + + Proxy Settings... + Proxy-Einstellungen… + + + + Playdar HTTP API + Playdar HTTP API + + + + Connect automatically when Tomahawk starts + Automatisch beim Start von Tomahawk verbinden + + + + Use UPnP to establish port forward + Benutze UPnP um die Portweiterleitung zu konfigurieren + + + + Local Music + Lokale Musik + + + + Path to scan for music files: + Pfad zu den Musikdateien: + + + + ... + … + + + + Last.fm + Last.fm + + + + Scrobble tracks to Last.fm + Gespielte Stücke an Last.fm übertragen + + + + Last.fm Login + Last.fm Anmeldung + + + + Username: + Benutzername: + + + + Test Login + Anmeldung Testen + + + + Script Resolvers + + + + + Loaded script resolvers: + + + + + Select Music Folder + Musikordner auswählen + + + + + Failed + Fehlgeschlagen + + + + Success + Erfolgreich + + + + Could not contact server + Konnte den Server nicht erreichen + + + + Load script resolver file + + + + + SourceDelegate + + + Offline + Nicht Verbunden + + + + All available tracks + Alle verfügbaren Stücke + + + + Online + Verbunden + + + + SourceInfoWidget + + + Recent Albums + Aktuelle Alben + + + + Latest Additions to their Collection + Zuletzt zur Sammlung hinzugefügte Stücke + + + + Recently played Tracks + Aktuell gespiele Stücke + + + + Info about %1 + Information über %1 + + + + Your Collection + Deine Sammlung + + + + SourceTreeItem + + + Super Collection + Komplettsammlung + + + + SourceTreeItemWidget + + + Form + + + + + + + + TextLabel + + + + + Off + + + + + Info + + + + + Super Collection + Komplettsammlung + + + + All available tracks + Alle verfügbaren Stücke + + + + Idle + + + + + %L1 tracks + %L1 Stücke + + + + Checking + Teste + + + + Fetching + Hole + + + + Parsing + Parse + + + + Saving + Speichere + + + + Synced + Synchronisiert + + + + Scanning (%L1 tracks) + Durchsuche (%L1 Stücke) + + + + Offline + Nicht Verbunden + + + + SourceTreeView + + + &Load Playlist + &Lade Playliste + + + + &Rename Playlist + Playliste &umbenennen + + + + &Delete Playlist + Playliste &löschen + + + + Tomahawk::DynamicControlList + + + Click to collapse + Klicken zum Zusammenfalten + + + + Tomahawk::DynamicModel + + + + Could not find a playable track. + +Please change the filters or try again. + Konnte kein spielbares Stück finden. + +Bitte ändere den Filter oder versuche es erneut. + + + + Tomahawk::DynamicSetupWidget + + + Type: + Typ: + + + + Generate + Erzeugen + + + + Tomahawk::DynamicView + + + Add some filters above to seed this station! + Füge einige Filter hinzu, um diese Station zu initialisieren! + + + + Press Generate to get started! + Drücke Erzeugen, um zu beginnen! + + + + Add some filters above, and press Generate to get started! + Füge oben einige Filter hinzu und drücke Erzeugen um zu beginnen! + + + + Tomahawk::EchonestControl + + + + + + is + ist + + + + + + + + + Less + Kleiner + + + + + + + + + More + Größer + + + + 0 BPM + + + + + 500 BPM + + + + + 0 secs + 0 s + + + + 3600 secs + 3600 s + + + + -100 dB + + + + + 100 dB + + + + + Major + Dur + + + + Minor + Moll + + + + C + C + + + + C Sharp + Cis + + + + D + D + + + + E Flat + Es + + + + E + E + + + + F + F + + + + F Sharp + Fis + + + + G + G + + + + A Flat + As + + + + A + A + + + + B Flat + stimmt das? + B + + + + B + H + + + + Ascending + Aufsteigend + + + + Descending + Absteigend + + + + Tempo + Tempo + + + + Duration + Dauer + + + + Loudness + Lautstärke + + + + Artist Familiarity + + + + + Artist Hotttnesss + + + + + Song Hotttnesss + + + + + Latitude + Breitengrad + + + + Longitude + Längengrad + + + + Mode + Modus + + + + Key + Schlüssel + + + + Energy + Energie + + + + Danceability + Tanzbarkeit + + + + Tomahawk::EchonestSteerer + + + Steer this station: + Steuere diese Station: + + + + Takes effect on track change + Wird nach dem Wechsel eines Stückes aktiv + + + + Much less + Viel Weniger + + + + Less + Weniger + + + + A bit less + Etwas Weniger + + + + Keep at current + So belassen + + + + A bit more + Etwas Mehr + + + + More + Mehr + + + + Much more + Viel Mehr + + + + Tempo + Tempo + + + + Loudness + Lautstärke + + + + Danceability + Tanzbarkeit + + + + Energy + Energie + + + + Song Hotttnesss + + + + + Artist Hotttnesss + + + + + Artist Familiarity + + + + + By Description + Von der Beschreibung + + + + Enter a description + Gib eine Beschreibung ein + + + + Reset all steering commands + Setze alle Steuerkommandos zurück + + + + Tomahawk::Source + + + + Scanning (%L1 tracks) + Scanne (%L1 Stücke) + + + + Checking + Teste + + + + Fetching + Hole + + + + Parsing + Parse + + + + Saving + Speichere + + + + TomahawkTrayIcon + + + Play + Abspielen + + + + Pause + Pause + + + + Stop + Anhalten + + + + Previous Track + Vorheriges Stück + + + + Next Track + Nächstes Stück + + + + Quit + Verlassen + + + + Currently not playing. + Derzeit wird nichts gespielt. + + + + TomahawkWindow + + + Tomahawk + Tomahawk + + + + &Settings + &Einstellungen + + + + &Music Player + &Abspielprogramm + + + + &Playlist + &Playliste + + + + &Network + &Netzwerk + + + + &Help + &Hilfe + + + + &Quit + &Verlassen + + + + Ctrl+Q + Strg+Q + + + + + Go &online + &Verbindung herstellen + + + + Add &Friend... + Freund &hinzufügen… + + + + Re&scan Collection... + Sammlung neu&laden… + + + + &Configure Tomahawk... + Tomahawk &einrichten… + + + + Load &XSPF... + &XSPF-Datei laden… + + + + Create &New Playlist... + Neue &Playliste erstellen… + + + + About &Tomahawk... + Über &Tomahawk… + + + + Create New &Automatic Playlist + Neue, &automatische Playliste erstellen + + + + Create New &Station + Neue &Station erstellen + + + + Show Offline Sources + Nicht-Verfügbare Quellen anzeigen + + + + Hide Offline Sources + Nicht-Verfügbare Quellen ausblenden + + + + + Check for updates... + Teste auf updates… + + + + Back + Zurück + + + + Forward + Forwärts + + + + Home + + + + + + + Connect To Peer + Zu Gegenstelle verbinden + + + + Enter peer address: + Gib die Adresse der Gegenstelle ein: + + + + Enter peer port: + Gib den Port der Gegenstelle ein: + + + + Enter peer key: + Gib den Schlüssel der Gegenstelle ein: + + + + Go &offline + Verbindung &trennen + + + + Authentication Error + Authentifizierungsfehler + + + + by + von + + + + <h2><b>Tomahawk %1</h2>Copyright 2010, 2011<br/>Christian Muehlhaeuser &lt;muesli@tomahawk-player.org&gt;<br/><br/>Thanks to: Leo Franchi, Jeff Mitchell, Dominik Schmidt, Jason Herskowitz, Alejandro Wainzinger, Harald Sitter and Steve Robertson + + + + + TopBar + + + Form + + + + + 0 Sources + + + + + 0 Tracks + + + + + 0 Artists + + + + + 0 Shown + + + + + Tracks + Stücke + + + + Artists + Künstler + + + + Sources + Quellen + + + + Shown + Angezeigt + + + + TrackModel + + + Artist + Künstler + + + + Track + Titel + + + + Album + Album + + + + Duration + Spieldauer + + + + Bitrate + Bitrate + + + + Age + Alter + + + + Year + Jahr + + + + Size + Größe + + + + Origin + Quelle + + + + TrackView + + + Sorry, your filter '%1' did not match any results. + Entschuldige, dein Filter '%1' erzeugte keine Ergebnisse. + + + + TransferView + + + Peer + Gegenstelle + + + + Rate + Rate + + + + Track + Stück + + + + TwitterConfigWidget + + + Authenticating with Twitter allows you to discover and play music from your Twitter friends running Tomahawk. + + + + + This feature works best when you have set a static host name in the "Network" settings tab under Advanced Settings, but may work even if you do not. Tomahawk uses Direct Messages and this will only work when both Twitter users have followed each other. + + + + + Status: No saved credentials + + + + + Authenticate with Twitter + + + + + Here's how it works: just press one of the buttons below to tweet "Got Tomahawk?" and some necessary information. Then be (very) patient. Twitter is an asynchronous protocol so it can take a bit! + +If connections to peers seem to have been lost, just press the appropriate button again to re-post a tweet for resynchronization. + + + + + Select the kind of tweet you would like, then press the button to post it: + + + + + Global Tweet + + + + + @Mention + + + + + Direct Message + + + + + e.g. @tomahawkplayer + + + + + Tweet! + + + + + WelcomeWidget + + + Recently played playlists: + Aktuell gespielte Playlisten: + + + + Recently played tracks: + Aktuell gespielte Stücke: + + + + You have not played any playlists yet. + Du hast bisher keine Playlisten abgespielt. + + + + Welcome to Tomahawk + Willkommen bei Tomahawk + + + + XSPFLoader + + + New Playlist + Neue Playliste + + + + Failed to save tracks + Konnte Stücke nicht abspeichern + + + + Some tracks in the playlist do not contain an artist and a title. They will be ignored. + Einige Stücke in der Playliste enthalten weder Künstler noch Titel. Diese werden ignoriert. + + + + XSPF Error + XSPF-Fehler + + + + This is not a valid XSPF playlist. + Dies ist keine valide XSPF-Playliste. + + + From 35c2fccd6c91f7c09b431783f8ff4d259a2411a1 Mon Sep 17 00:00:00 2001 From: Jeff Mitchell Date: Tue, 29 Mar 2011 19:35:18 -0400 Subject: [PATCH 212/329] Various proxy-related fixes. Still doesn't work properly...parts of it do but gloox jabber doesn't. (Haven't tested twitter yet.) --- src/settingsdialog.cpp | 4 ++-- src/sip/jabber/jabber_p.cpp | 25 ++++++++++++++++++++++++- src/sip/zeroconf/tomahawkzeroconf.h | 2 ++ src/tomahawkapp.cpp | 8 ++++++-- src/tomahawkwindow.h | 2 -- 5 files changed, 34 insertions(+), 7 deletions(-) diff --git a/src/settingsdialog.cpp b/src/settingsdialog.cpp index bafcb3ba3..58fbd1f96 100644 --- a/src/settingsdialog.cpp +++ b/src/settingsdialog.cpp @@ -316,10 +316,10 @@ ProxyDialog::saveSettings() s->setProxyPassword( ui->passwordLineEdit->text() ); s->setProxyType( ui->typeBox->itemData( ui->typeBox->currentIndex() ).toInt() ); - // Now, if a proxy is defined, set QNAM - if( s->proxyType() == QNetworkProxy::NoProxy || s->proxyHost().isEmpty() ) + if( s->proxyHost().isEmpty() ) return; + // Now, set QNAM QNetworkProxy proxy( static_cast(s->proxyType()), s->proxyHost(), s->proxyPort(), s->proxyUsername(), s->proxyPassword() ); QNetworkAccessManager* nam = TomahawkUtils::nam(); nam->setProxy( proxy ); diff --git a/src/sip/jabber/jabber_p.cpp b/src/sip/jabber/jabber_p.cpp index c5836005c..c4f48a658 100644 --- a/src/sip/jabber/jabber_p.cpp +++ b/src/sip/jabber/jabber_p.cpp @@ -29,6 +29,7 @@ #include #include #include +#include #define TOMAHAWK_CAP_NODE_NAME QString::fromAscii("http://tomahawk-player.org/") @@ -66,6 +67,10 @@ Jabber_p::Jabber_p( const QString& jid, const QString& password, const QString& const_cast(caps)->setNode(TOMAHAWK_CAP_NODE_NAME.toStdString()); } m_server = server; + + qDebug() << "proxy type is " << TomahawkUtils::proxy()->type(); + + setProxy( TomahawkUtils::proxy() ); } @@ -83,11 +88,26 @@ Jabber_p::~Jabber_p() void Jabber_p::resolveHostSRV() { + qDebug() << Q_FUNC_INFO; if( m_server.isEmpty() ) { qDebug() << "No server found!"; return; } + if( TomahawkUtils::proxy()->type() == QNetworkProxy::Socks5Proxy || + ( TomahawkUtils::proxy()->type() == QNetworkProxy::DefaultProxy && + QNetworkProxy::applicationProxy().type() == QNetworkProxy::Socks5Proxy ) ) + { + if( TomahawkSettings::instance()->jabberServer().isEmpty() ) + { + qDebug() << "Right now, you must explicitly set your jabber server if you are using a proxy, due to a bug in the DNS lookup library"; + m_server = QString(); + } + QMetaObject::invokeMethod( this, "go", Qt::QueuedConnection ); + return; + } + + TomahawkUtils::DNSResolver *resolver = TomahawkUtils::dnsResolver(); connect( resolver, SIGNAL(result(QString &)), SLOT(resolveResult(QString &)) ); qDebug() << "Resolving SRV record of " << m_server; @@ -99,7 +119,7 @@ Jabber_p::setProxy( QNetworkProxy* proxy ) { qDebug() << Q_FUNC_INFO; - if( !m_client.isNull() || !proxy ) + if( m_client.isNull() || !proxy ) { qDebug() << "No client or no proxy"; return; @@ -116,6 +136,8 @@ Jabber_p::setProxy( QNetworkProxy* proxy ) else if( proxy->type() == QNetworkProxy::Socks5Proxy ) { qDebug() << "Setting proxy to SOCKS5"; + qDebug() << "proxy host = " << proxy->hostName(); + qDebug() << "proxy port = " << proxy->port(); m_client->setConnectionImpl( new gloox::ConnectionSOCKS5Proxy( m_client.data(), new gloox::ConnectionTCPClient( m_client->logInstance(), proxy->hostName().toStdString(), proxy->port() ), m_client->logInstance(), m_client->server(), m_client->port() ) ); @@ -138,6 +160,7 @@ Jabber_p::resolveResult( QString& result ) void Jabber_p::go() { + qDebug() << Q_FUNC_INFO; if( !m_server.isEmpty() ) m_client->setServer( m_server.toStdString() ); else diff --git a/src/sip/zeroconf/tomahawkzeroconf.h b/src/sip/zeroconf/tomahawkzeroconf.h index d9df53ada..44dd84a87 100644 --- a/src/sip/zeroconf/tomahawkzeroconf.h +++ b/src/sip/zeroconf/tomahawkzeroconf.h @@ -25,6 +25,7 @@ #include #include #include +#include #include #include @@ -80,6 +81,7 @@ public: : QObject( parent ), m_sock( this ), m_port( port ) { qDebug() << Q_FUNC_INFO; + m_sock.setProxy( QNetworkProxy::NoProxy ); m_sock.bind( ZCONF_PORT, QUdpSocket::ShareAddress ); connect( &m_sock, SIGNAL( readyRead() ), this, SLOT( readPacket() ) ); } diff --git a/src/tomahawkapp.cpp b/src/tomahawkapp.cpp index 02d98e1ce..46c002c4a 100644 --- a/src/tomahawkapp.cpp +++ b/src/tomahawkapp.cpp @@ -52,6 +52,7 @@ #include "audio/audioengine.h" #include "utils/xspfloader.h" +#include #include "config.h" #ifndef TOMAHAWK_HEADLESS @@ -175,8 +176,6 @@ TomahawkApp::TomahawkApp( int& argc, char *argv[] ) registerMetaTypes(); setupLogfile(); - Echonest::Config::instance()->setAPIKey( "JRIHWEP6GPOER2QQ6" ); - new TomahawkSettings( this ); m_audioEngine = new AudioEngine; new ScanManager( this ); @@ -248,10 +247,15 @@ TomahawkApp::TomahawkApp( int& argc, char *argv[] ) qDebug() << "Proxy type =" << QString::number( static_cast(TomahawkUtils::proxy()->type()) ); qDebug() << "Proxy host =" << TomahawkUtils::proxy()->hostName(); TomahawkUtils::nam()->setProxy( *TomahawkUtils::proxy() ); + lastfm::nam()->setProxy( *TomahawkUtils::proxy() ); } else TomahawkUtils::setProxy( new QNetworkProxy( QNetworkProxy::NoProxy ) ); + + Echonest::Config::instance()->setAPIKey( "JRIHWEP6GPOER2QQ6" ); + Echonest::Config::instance()->setNetworkAccessManager( TomahawkUtils::nam() ); + QNetworkProxy::setApplicationProxy( *TomahawkUtils::proxy() ); qDebug() << "Init SIP system."; diff --git a/src/tomahawkwindow.h b/src/tomahawkwindow.h index 2f24f9f65..e8211fbb6 100644 --- a/src/tomahawkwindow.h +++ b/src/tomahawkwindow.h @@ -21,7 +21,6 @@ #include #include -#include #include #include #include @@ -88,7 +87,6 @@ private: Ui::TomahawkWindow* ui; AudioControls* m_audioControls; TomahawkTrayIcon* m_trayIcon; - QNetworkAccessManager m_nam; QPushButton* m_statusButton; QAction* m_backAvailable; From f52205d2cce6bc770a5f2314e774feeb7eae570a Mon Sep 17 00:00:00 2001 From: Dominik Schmidt Date: Wed, 30 Mar 2011 05:05:22 +0200 Subject: [PATCH 213/329] Update jreen plugin to newer jreen and sip plugin interface --- src/sip/jreen/jabber.cpp | 49 ++++++++++++++++------ src/sip/jreen/jabber.h | 6 +++ src/sip/jreen/jabber_p.cpp | 86 +++++++++++++++++++------------------- src/sip/jreen/jabber_p.h | 22 +++++----- thirdparty/jreen | 2 +- 5 files changed, 98 insertions(+), 67 deletions(-) diff --git a/src/sip/jreen/jabber.cpp b/src/sip/jreen/jabber.cpp index 0d0c9acc6..c97067f59 100644 --- a/src/sip/jreen/jabber.cpp +++ b/src/sip/jreen/jabber.cpp @@ -77,10 +77,10 @@ JabberPlugin::connectPlugin( bool startup ) if ( startup && !TomahawkSettings::instance()->jabberAutoConnect() ) return false; - QString jid = TomahawkSettings::instance()->jabberUsername(); - QString server = TomahawkSettings::instance()->jabberServer(); - QString password = TomahawkSettings::instance()->jabberPassword(); - unsigned int port = TomahawkSettings::instance()->jabberPort(); + QString jid = m_currentUsername = TomahawkSettings::instance()->jabberUsername(); + QString server = m_currentServer = TomahawkSettings::instance()->jabberServer(); + QString password = m_currentPassword = TomahawkSettings::instance()->jabberPassword(); + unsigned int port = m_currentPort = TomahawkSettings::instance()->jabberPort(); QStringList splitJid = jid.split( '@', QString::SkipEmptyParts ); if ( splitJid.size() < 2 ) @@ -115,7 +115,7 @@ void JabberPlugin::disconnectPlugin() { onDisconnected(); - + if ( p ) p->disconnect(); @@ -123,35 +123,35 @@ JabberPlugin::disconnectPlugin() p = 0; } -void +void JabberPlugin::onConnected() { if( !m_menu ) { m_menu = new QMenu( QString( "JREEN (" ).append( accountName() ).append(")" ) ); m_addFriendAction = m_menu->addAction( "Add Friend..." ); QAction *connectAction = m_menu->addAction( "Connect" ); - + connect( m_addFriendAction, SIGNAL(triggered() ), this, SLOT( showAddFriendDialog() ) ); connect( connectAction, SIGNAL( triggered() ), SLOT( connectPlugin() ) ); - + emit addMenu( m_menu ); } - + emit connected(); } -void +void JabberPlugin::onDisconnected() { if( m_menu && m_addFriendAction ) { emit removeMenu( m_menu ); - + delete m_menu; m_menu = 0; m_addFriendAction = 0; // deleted by menu } - + emit disconnected(); } @@ -190,4 +190,29 @@ JabberPlugin::showAddFriendDialog() addContact( id ); } +void +JabberPlugin::checkSettings() +{ + bool reconnect = false; + if ( m_currentUsername != TomahawkSettings::instance()->jabberUsername() ) + reconnect = true; + if ( m_currentPassword != TomahawkSettings::instance()->jabberPassword() ) + reconnect = true; + if ( m_currentServer != TomahawkSettings::instance()->jabberServer() ) + reconnect = true; + if ( m_currentPort != TomahawkSettings::instance()->jabberPort() ) + reconnect = true; + + m_currentUsername = TomahawkSettings::instance()->jabberUsername(); + m_currentPassword = TomahawkSettings::instance()->jabberPassword(); + m_currentServer = TomahawkSettings::instance()->jabberServer(); + m_currentPort = TomahawkSettings::instance()->jabberPort(); + + if ( reconnect && ( p || TomahawkSettings::instance()->jabberAutoConnect() ) ) + { + disconnectPlugin(); + connectPlugin( false ); + } +} + Q_EXPORT_PLUGIN2( sip, JabberPlugin ) diff --git a/src/sip/jreen/jabber.h b/src/sip/jreen/jabber.h index 543a5a218..ccbbafc0e 100644 --- a/src/sip/jreen/jabber.h +++ b/src/sip/jreen/jabber.h @@ -47,6 +47,7 @@ public: public slots: virtual bool connectPlugin( bool startup ); void disconnectPlugin(); + void checkSettings(); void sendMsg( const QString& to, const QString& msg ); void broadcastMsg( const QString &msg ); void addContact( const QString &jid, const QString& msg = QString() ); @@ -60,6 +61,11 @@ private: Jabber_p* p; QMenu* m_menu; QAction* m_addFriendAction; + + QString m_currentUsername; + QString m_currentPassword; + QString m_currentServer; + unsigned int m_currentPort; }; #endif diff --git a/src/sip/jreen/jabber_p.cpp b/src/sip/jreen/jabber_p.cpp index a902ef4a3..93987b35e 100644 --- a/src/sip/jreen/jabber_p.cpp +++ b/src/sip/jreen/jabber_p.cpp @@ -47,22 +47,22 @@ Jabber_p::Jabber_p( const QString& jid, const QString& password, const QString& //qsrand( QTime( 0, 0, 0 ).secsTo( QTime::currentTime() ) ); qsrand(QDateTime::currentDateTime().toTime_t()); - m_presences[jreen::Presence::Available] = "available"; - m_presences[jreen::Presence::Chat] = "chat"; - m_presences[jreen::Presence::Away] = "away"; - m_presences[jreen::Presence::DND] = "dnd"; - m_presences[jreen::Presence::XA] = "xa"; - m_presences[jreen::Presence::Unavailable] = "unavailable"; - m_presences[jreen::Presence::Probe] = "probe"; - m_presences[jreen::Presence::Error] = "error"; - m_presences[jreen::Presence::Invalid] = "invalid"; + m_presences[Jreen::Presence::Available] = "available"; + m_presences[Jreen::Presence::Chat] = "chat"; + m_presences[Jreen::Presence::Away] = "away"; + m_presences[Jreen::Presence::DND] = "dnd"; + m_presences[Jreen::Presence::XA] = "xa"; + m_presences[Jreen::Presence::Unavailable] = "unavailable"; + m_presences[Jreen::Presence::Probe] = "probe"; + m_presences[Jreen::Presence::Error] = "error"; + m_presences[Jreen::Presence::Invalid] = "invalid"; - m_jid = jreen::JID( jid ); + m_jid = Jreen::JID( jid ); - m_client = new jreen::Client( jid, password ); + m_client = new Jreen::Client( jid, password ); m_client->setResource( QString( "tomahawk%1" ).arg( "DOMME" ) ); - jreen::Capabilities::Ptr caps = m_client->presence().findExtension(); + Jreen::Capabilities::Ptr caps = m_client->presence().findExtension(); caps->setNode(TOMAHAWK_CAP_NODE_NAME); qDebug() << "Our JID set to:" << m_client->jid().full(); @@ -71,10 +71,10 @@ Jabber_p::Jabber_p( const QString& jid, const QString& password, const QString& connect(m_client->connection(), SIGNAL(error(SocketError)), SLOT(onError(SocketError))); connect(m_client, SIGNAL(serverFeaturesReceived(QSet)), SLOT(onConnect())); - connect(m_client, SIGNAL(disconnected(jreen::Client::DisconnectReason)), SLOT(onDisconnect(jreen::Client::DisconnectReason))); + connect(m_client, SIGNAL(disconnected(Jreen::Client::DisconnectReason)), SLOT(onDisconnect(Jreen::Client::DisconnectReason))); connect(m_client, SIGNAL(destroyed(QObject*)), this, SLOT(onDestroy())); - connect(m_client, SIGNAL(newMessage(jreen::Message)), SLOT(onNewMessage(jreen::Message))); - connect(m_client, SIGNAL(newPresence(jreen::Presence)), SLOT(onNewPresence(jreen::Presence))); + connect(m_client, SIGNAL(newMessage(Jreen::Message)), SLOT(onNewMessage(Jreen::Message))); + connect(m_client, SIGNAL(newPresence(Jreen::Presence)), SLOT(onNewPresence(Jreen::Presence))); qDebug() << "Connecting to the XMPP server..."; m_client->connectToServer(); @@ -124,7 +124,7 @@ Jabber_p::sendMsg( const QString& to, const QString& msg ) } qDebug() << Q_FUNC_INFO << to << msg; - jreen::Message m( jreen::Message::Chat, jreen::JID(to), msg); + Jreen::Message m( Jreen::Message::Chat, Jreen::JID(to), msg); m_client->send( m ); // assuming this is threadsafe } @@ -149,7 +149,7 @@ Jabber_p::broadcastMsg( const QString &msg ) foreach( const QString& jidstr, m_peers.keys() ) { qDebug() << "Broadcasting to" << jidstr <<"..."; - jreen::Message m(jreen::Message::Chat, jreen::JID(jidstr), msg, ""); + Jreen::Message m(Jreen::Message::Chat, Jreen::JID(jidstr), msg, ""); m_client->send( m ); } } @@ -191,27 +191,27 @@ Jabber_p::onConnect() emit connected(); qDebug() << "Connected as:" << m_jid.full(); - m_client->setPresence(jreen::Presence::Available, "Tomahawk-JREEN available", 1); + m_client->setPresence(Jreen::Presence::Available, "Tomahawk-JREEN available", 1); m_client->disco()->setSoftwareVersion( "Tomahawk JREEN", "0.0.0.0", "Foobar" ); m_client->setPingInterval(60000); - m_roster = new jreen::SimpleRoster( m_client ); + m_roster = new Jreen::SimpleRoster( m_client ); m_roster->load(); // join MUC with bare jid as nickname //TODO: make the room a list of rooms and make that configurable QString bare(m_jid.bare()); - m_room = new jreen::MUCRoom(m_client, jreen::JID(QString("tomahawk@conference.qutim.org/").append(bare.replace("@", "-")))); - m_room->setHistorySeconds(0); - m_room->join(); + m_room = new Jreen::MUCRoom(m_client, Jreen::JID(QString("tomahawk@conference.qutim.org/").append(bare.replace("@", "-")))); + //m_room->setHistorySeconds(0); + //m_room->join(); // treat muc participiants like contacts - connect(m_room, SIGNAL(messageReceived(jreen::Message, bool)), this, SLOT(onNewMessage(jreen::Message))); - connect(m_room, SIGNAL(presenceReceived(jreen::Presence,const jreen::MUCRoom::Participant*)), this, SLOT(onNewPresence(jreen::Presence))); + connect(m_room, SIGNAL(messageReceived(Jreen::Message, bool)), this, SLOT(onNewMessage(Jreen::Message))); + connect(m_room, SIGNAL(presenceReceived(Jreen::Presence,const Jreen::MUCRoom::Participant*)), this, SLOT(onNewPresence(Jreen::Presence))); } void -Jabber_p::onDisconnect( jreen::Client::DisconnectReason reason ) +Jabber_p::onDisconnect( Jreen::Client::DisconnectReason reason ) { QString error; bool reconnect = false; @@ -219,39 +219,39 @@ Jabber_p::onDisconnect( jreen::Client::DisconnectReason reason ) switch( reason ) { - case jreen::Client::User: + case Jreen::Client::User: error = "User Interaction"; break; - case jreen::Client::HostUnknown: + case Jreen::Client::HostUnknown: error = "Host is unknown"; break; - case jreen::Client::ItemNotFound: + case Jreen::Client::ItemNotFound: error = "Item not found"; break; - case jreen::Client::AuthorizationError: + case Jreen::Client::AuthorizationError: error = "Authorization Error"; break; - case jreen::Client::RemoteStreamError: + case Jreen::Client::RemoteStreamError: error = "Remote Stream Error"; reconnect = true; break; - case jreen::Client::RemoteConnectionFailed: + case Jreen::Client::RemoteConnectionFailed: error = "Remote Connection failed"; break; - case jreen::Client::InternalServerError: + case Jreen::Client::InternalServerError: error = "Internal Server Error"; reconnect = true; break; - case jreen::Client::SystemShutdown: + case Jreen::Client::SystemShutdown: error = "System shutdown"; reconnect = true; reconnectInSeconds = 60; break; - case jreen::Client::Conflict: + case Jreen::Client::Conflict: error = "Conflict"; break; - case jreen::Client::Unknown: + case Jreen::Client::Unknown: error = "Unknown"; break; @@ -270,7 +270,7 @@ Jabber_p::onDisconnect( jreen::Client::DisconnectReason reason ) } void -Jabber_p::onNewMessage( const jreen::Message& m ) +Jabber_p::onNewMessage( const Jreen::Message& m ) { QString from = m.from().full(); QString msg = m.body(); @@ -283,10 +283,10 @@ Jabber_p::onNewMessage( const jreen::Message& m ) } -void Jabber_p::onNewPresence( const jreen::Presence& presence) +void Jabber_p::onNewPresence( const Jreen::Presence& presence) { - jreen::JID jid = presence.from(); + Jreen::JID jid = presence.from(); QString fulljid( jid.full() ); qDebug() << Q_FUNC_INFO << "handle presence" << fulljid << presence.subtype(); @@ -300,7 +300,7 @@ void Jabber_p::onNewPresence( const jreen::Presence& presence) } // ignore anyone not running tomahawk: - jreen::Capabilities::Ptr caps = presence.findExtension(); + Jreen::Capabilities::Ptr caps = presence.findExtension(); if ( caps && (caps->node() == TOMAHAWK_CAP_NODE_NAME )) { qDebug() << Q_FUNC_INFO << presence.from().full() << "tomahawk detected by caps"; @@ -356,13 +356,13 @@ void Jabber_p::onNewPresence( const jreen::Presence& presence) } bool -Jabber_p::presenceMeansOnline( jreen::Presence::Type p ) +Jabber_p::presenceMeansOnline( Jreen::Presence::Type p ) { switch(p) { - case jreen::Presence::Invalid: - case jreen::Presence::Unavailable: - case jreen::Presence::Error: + case Jreen::Presence::Invalid: + case Jreen::Presence::Unavailable: + case Jreen::Presence::Error: return false; break; default: diff --git a/src/sip/jreen/jabber_p.h b/src/sip/jreen/jabber_p.h index f10541c2c..ab0826f20 100644 --- a/src/sip/jreen/jabber_p.h +++ b/src/sip/jreen/jabber_p.h @@ -77,25 +77,25 @@ public slots: void addContact( const QString& jid, const QString& msg = QString() ); void disconnect(); - void onDisconnect(jreen::Client::DisconnectReason reason); + void onDisconnect(Jreen::Client::DisconnectReason reason); void onConnect(); private slots: - virtual void onNewPresence( const jreen::Presence& presence ); - virtual void onNewMessage( const jreen::Message& msg ); - virtual void onError( const jreen::Connection::SocketError& e ) + virtual void onNewPresence( const Jreen::Presence& presence ); + virtual void onNewMessage( const Jreen::Message& msg ); + virtual void onError( const Jreen::Connection::SocketError& e ) { qDebug() << e; } private: - bool presenceMeansOnline( jreen::Presence::Type p ); - jreen::Client *m_client; - jreen::MUCRoom *m_room; - jreen::SimpleRoster *m_roster; - jreen::JID m_jid; - QMap m_presences; - QMap m_peers; + bool presenceMeansOnline( Jreen::Presence::Type p ); + Jreen::Client *m_client; + Jreen::MUCRoom *m_room; + Jreen::SimpleRoster *m_roster; + Jreen::JID m_jid; + QMap m_presences; + QMap m_peers; QString m_server; }; diff --git a/thirdparty/jreen b/thirdparty/jreen index 040ca3f3c..126ef9d96 160000 --- a/thirdparty/jreen +++ b/thirdparty/jreen @@ -1 +1 @@ -Subproject commit 040ca3f3cb9b30b4845fc23054c833fda4717460 +Subproject commit 126ef9d96bf774b9808a16dd8c94001af408528b From 492c2acf18422176bb1cf668cb2cc30f02fb8305 Mon Sep 17 00:00:00 2001 From: Frank Osterfeld Date: Wed, 30 Mar 2011 19:07:05 +0200 Subject: [PATCH 214/329] i18n: tr() and avoid string puzzle --- src/libtomahawk/network/servent.cpp | 12 +++---- src/sip/jabber/jabber.cpp | 2 +- src/sip/twitter/twitter.cpp | 2 +- src/sip/twitter/twitterconfigwidget.cpp | 46 ++++++++++++------------- src/xmppbot/xmppbot.cpp | 10 +++--- 5 files changed, 36 insertions(+), 36 deletions(-) diff --git a/src/libtomahawk/network/servent.cpp b/src/libtomahawk/network/servent.cpp index 182c8a2f4..1d54d0897 100644 --- a/src/libtomahawk/network/servent.cpp +++ b/src/libtomahawk/network/servent.cpp @@ -676,12 +676,12 @@ Servent::checkACL( const Connection* conn, const QString &nodeid, bool showDialo qDebug() << "ACL for this node not found"; QMessageBox msgBox; msgBox.setIcon( QMessageBox::Question ); - msgBox.setText( "Incoming Connection Attempt" ); - msgBox.setInformativeText( QString( "Another Tomahawk instance is attempting to connect to you. Select whether to allow or deny this connection.\n\nPeer name: %1\nPeer ID: %2\n\nRemember: Only allow peers to connect if you have the legal right for them to stream music from you.").arg( conn->name(), nodeid ) ); - QPushButton *denyButton = msgBox.addButton( "Deny", QMessageBox::HelpRole ); - QPushButton *alwaysDenyButton = msgBox.addButton( "Always Deny", QMessageBox::YesRole ); - QPushButton *allowButton = msgBox.addButton( "Allow", QMessageBox::NoRole ); - QPushButton *alwaysAllowButton = msgBox.addButton( "Always Allow", QMessageBox::ActionRole ); + msgBox.setText( tr( "Incoming Connection Attempt" ) ); + msgBox.setInformativeText( tr( "Another Tomahawk instance is attempting to connect to you. Select whether to allow or deny this connection.\n\nPeer name: %1\nPeer ID: %2\n\nRemember: Only allow peers to connect if you have the legal right for them to stream music from you.").arg( conn->name(), nodeid ) ); + QPushButton *denyButton = msgBox.addButton( tr( "Deny" ), QMessageBox::HelpRole ); + QPushButton *alwaysDenyButton = msgBox.addButton( tr( "Always Deny" ), QMessageBox::YesRole ); + QPushButton *allowButton = msgBox.addButton( tr( "Allow" ), QMessageBox::NoRole ); + QPushButton *alwaysAllowButton = msgBox.addButton( tr( "Always Allow" ), QMessageBox::ActionRole ); msgBox.setDefaultButton( denyButton ); msgBox.setEscapeButton( denyButton ); diff --git a/src/sip/jabber/jabber.cpp b/src/sip/jabber/jabber.cpp index ce62909c4..dab22ac5b 100644 --- a/src/sip/jabber/jabber.cpp +++ b/src/sip/jabber/jabber.cpp @@ -120,7 +120,7 @@ JabberPlugin::onConnected() { if ( !m_menu ) { - m_menu = new QMenu( QString( "Jabber (" ).append( accountName() ).append( ")" ) ); + m_menu = new QMenu( tr( "Jabber (%1)" ).arg( accountName() ) ); m_addFriendAction = m_menu->addAction( tr( "Add Friend..." ) ); connect( m_addFriendAction, SIGNAL( triggered() ), SLOT( showAddFriendDialog() ) ) ; diff --git a/src/sip/twitter/twitter.cpp b/src/sip/twitter/twitter.cpp index 655dd832b..772c9abdb 100644 --- a/src/sip/twitter/twitter.cpp +++ b/src/sip/twitter/twitter.cpp @@ -87,7 +87,7 @@ TwitterPlugin::name() const QString TwitterPlugin::friendlyName() { - return QString("Twitter"); + return tr("Twitter"); } const QString diff --git a/src/sip/twitter/twitterconfigwidget.cpp b/src/sip/twitter/twitterconfigwidget.cpp index 85660318a..8211f97dc 100644 --- a/src/sip/twitter/twitterconfigwidget.cpp +++ b/src/sip/twitter/twitterconfigwidget.cpp @@ -51,8 +51,8 @@ TwitterConfigWidget::TwitterConfigWidget( SipPlugin* plugin, QWidget *parent ) : TomahawkSettings* s = TomahawkSettings::instance(); if ( s->twitterOAuthToken().isEmpty() || s->twitterOAuthTokenSecret().isEmpty() || s->twitterScreenName().isEmpty() ) { - ui->twitterStatusLabel->setText("Status: No saved credentials"); - ui->twitterAuthenticateButton->setText( "Authenticate" ); + ui->twitterStatusLabel->setText( tr( "Status: No saved credentials" ) ); + ui->twitterAuthenticateButton->setText( tr( "Authenticate" ) ); ui->twitterInstructionsInfoLabel->setVisible( false ); ui->twitterGlobalTweetLabel->setVisible( false ); ui->twitterTweetGotTomahawkButton->setVisible( false ); @@ -63,8 +63,8 @@ TwitterConfigWidget::TwitterConfigWidget( SipPlugin* plugin, QWidget *parent ) : } else { - ui->twitterStatusLabel->setText("Status: Credentials saved for " + s->twitterScreenName() ); - ui->twitterAuthenticateButton->setText( "De-authenticate" ); + ui->twitterStatusLabel->setText( tr( "Status: Credentials saved for %1" ).arg( s->twitterScreenName() ) ); + ui->twitterAuthenticateButton->setText( tr( "De-authenticate" ) ); ui->twitterInstructionsInfoLabel->setVisible( true ); ui->twitterGlobalTweetLabel->setVisible( true ); ui->twitterTweetGotTomahawkButton->setVisible( true ); @@ -84,7 +84,7 @@ TwitterConfigWidget::~TwitterConfigWidget() void TwitterConfigWidget::authDeauthTwitter() { - if ( ui->twitterAuthenticateButton->text() == "Authenticate" ) + if ( ui->twitterAuthenticateButton->text() == tr( "Authenticate" ) ) //FIXME: don't rely on UI strings here! authenticateTwitter(); else deauthenticateTwitter(); @@ -114,7 +114,7 @@ TwitterConfigWidget::authenticateVerifyReply( const QTweetUser &user ) qDebug() << Q_FUNC_INFO; if ( user.id() == 0 ) { - QMessageBox::critical( 0, QString("Tweetin' Error"), QString("The credentials could not be verified.\nYou may wish to try re-authenticating.") ); + QMessageBox::critical( 0, tr("Tweetin' Error"), tr("The credentials could not be verified.\nYou may wish to try re-authenticating.") ); emit twitterAuthed( false ); return; } @@ -124,8 +124,8 @@ TwitterConfigWidget::authenticateVerifyReply( const QTweetUser &user ) s->setTwitterCachedFriendsSinceId( 0 ); s->setTwitterCachedMentionsSinceId( 0 ); - ui->twitterStatusLabel->setText("Status: Credentials saved for " + s->twitterScreenName() ); - ui->twitterAuthenticateButton->setText( "De-authenticate" ); + ui->twitterStatusLabel->setText( tr( "Status: Credentials saved for %1" ).arg( s->twitterScreenName() ) ); + ui->twitterAuthenticateButton->setText( tr( "De-authenticate" ) ); ui->twitterInstructionsInfoLabel->setVisible( true ); ui->twitterGlobalTweetLabel->setVisible( true ); ui->twitterTweetGotTomahawkButton->setVisible( true ); @@ -142,7 +142,7 @@ TwitterConfigWidget::authenticateVerifyError( QTweetNetBase::ErrorCode code, con { qDebug() << Q_FUNC_INFO; qDebug() << "Error validating credentials, error code is " << code << ", error message is " << errorMsg; - ui->twitterStatusLabel->setText("Status: Error validating credentials"); + ui->twitterStatusLabel->setText(tr("Status: Error validating credentials")); emit twitterAuthed( false ); return; } @@ -156,8 +156,8 @@ TwitterConfigWidget::deauthenticateTwitter() s->setTwitterOAuthTokenSecret( QString() ); s->setTwitterScreenName( QString() ); - ui->twitterStatusLabel->setText("Status: No saved credentials"); - ui->twitterAuthenticateButton->setText( "Authenticate" ); + ui->twitterStatusLabel->setText(tr("Status: No saved credentials")); + ui->twitterAuthenticateButton->setText( tr( "Authenticate" ) ); ui->twitterInstructionsInfoLabel->setVisible( false ); ui->twitterGlobalTweetLabel->setVisible( false ); ui->twitterTweetGotTomahawkButton->setVisible( false ); @@ -170,7 +170,7 @@ TwitterConfigWidget::deauthenticateTwitter() void TwitterConfigWidget::tweetComboBoxIndexChanged( int index ) { - if( ui->twitterTweetComboBox->currentText() == "Global Tweet" ) + if( ui->twitterTweetComboBox->currentText() == tr( "Global Tweet" ) ) //FIXME: use data! { ui->twitterUserTweetLineEdit->setReadOnly( true ); ui->twitterUserTweetLineEdit->setEnabled( false ); @@ -181,10 +181,10 @@ TwitterConfigWidget::tweetComboBoxIndexChanged( int index ) ui->twitterUserTweetLineEdit->setEnabled( true ); } - if( ui->twitterTweetComboBox->currentText() == "Direct Message" ) - ui->twitterTweetGotTomahawkButton->setText( "Send Message!" ); + if( ui->twitterTweetComboBox->currentText() == tr( "Direct Message" ) ) //FIXME: use data! + ui->twitterTweetGotTomahawkButton->setText( tr( "Send Message!" ) ); else - ui->twitterTweetGotTomahawkButton->setText( "Tweet!" ); + ui->twitterTweetGotTomahawkButton->setText( tr( "Tweet!" ) ); } void @@ -194,7 +194,7 @@ TwitterConfigWidget::startPostGotTomahawkStatus() if ( m_postGTtype != "Global Tweet" && ( ui->twitterUserTweetLineEdit->text().isEmpty() || ui->twitterUserTweetLineEdit->text() == "@" ) ) { - QMessageBox::critical( 0, QString("Tweetin' Error"), QString("You must enter a user name for this type of tweet.") ); + QMessageBox::critical( 0, tr("Tweetin' Error"), tr("You must enter a user name for this type of tweet.") ); return; } @@ -202,7 +202,7 @@ TwitterConfigWidget::startPostGotTomahawkStatus() TomahawkSettings* s = TomahawkSettings::instance(); if ( s->twitterOAuthToken().isEmpty() || s->twitterOAuthTokenSecret().isEmpty() || s->twitterScreenName().isEmpty() ) { - QMessageBox::critical( 0, QString("Tweetin' Error"), QString("Your saved credentials could not be loaded.\nYou may wish to try re-authenticating.") ); + QMessageBox::critical( this, tr("Tweetin' Error"), tr("Your saved credentials could not be loaded.\nYou may wish to try re-authenticating.") ); emit twitterAuthed( false ); return; } @@ -220,7 +220,7 @@ TwitterConfigWidget::postGotTomahawkStatusAuthVerifyReply( const QTweetUser &use { if ( user.id() == 0 ) { - QMessageBox::critical( 0, QString("Tweetin' Error"), QString("Your saved credentials could not be verified.\nYou may wish to try re-authenticating.") ); + QMessageBox::critical( 0, tr("Tweetin' Error"), tr("Your saved credentials could not be verified.\nYou may wish to try re-authenticating.") ); emit twitterAuthed( false ); return; } @@ -264,18 +264,18 @@ void TwitterConfigWidget::postGotTomahawkStatusUpdateReply( const QTweetStatus& status ) { if ( status.id() == 0 ) - QMessageBox::critical( 0, QString("Tweetin' Error"), QString("There was an error posting your status -- sorry!") ); + QMessageBox::critical( 0, tr("Tweetin' Error"), tr("There was an error posting your status -- sorry!") ); else - QMessageBox::information( 0, QString("Tweeted!"), QString("Your tweet has been posted!") ); + QMessageBox::information( 0, tr("Tweeted!"), tr("Your tweet has been posted!") ); } void TwitterConfigWidget::postGotTomahawkDirectMessageReply( const QTweetDMStatus& status ) { if ( status.id() == 0 ) - QMessageBox::critical( 0, QString("Tweetin' Error"), QString("There was an error posting your direct message -- sorry!") ); + QMessageBox::critical( 0, tr("Tweetin' Error"), tr("There was an error posting your direct message -- sorry!") ); else - QMessageBox::information( 0, QString("Tweeted!"), QString("Your message has been posted!") ); + QMessageBox::information( 0, tr("Tweeted!"), tr("Your message has been posted!") ); } void @@ -283,5 +283,5 @@ TwitterConfigWidget::postGotTomahawkStatusUpdateError( QTweetNetBase::ErrorCode { qDebug() << Q_FUNC_INFO; qDebug() << "Error posting Got Tomahawk message, error code is " << code << ", error message is " << errorMsg; - QMessageBox::critical( 0, QString("Tweetin' Error"), QString("There was an error posting your status -- sorry!") ); + QMessageBox::critical( 0, tr("Tweetin' Error"), tr("There was an error posting your status -- sorry!") ); } diff --git a/src/xmppbot/xmppbot.cpp b/src/xmppbot/xmppbot.cpp index e6be8a57c..d7d267f9d 100644 --- a/src/xmppbot/xmppbot.cpp +++ b/src/xmppbot/xmppbot.cpp @@ -306,9 +306,9 @@ void XMPPBot::infoReturnedSlot(QString caller, Tomahawk::InfoSystem::InfoType ty } InfoGenericMap tmap = output.value(); QString artist = input.toString(); - m_currReturnMessage += QString("\nTerms for %1:\n").arg(artist); + m_currReturnMessage += tr("\nTerms for %1:\n").arg(artist); if (tmap.isEmpty()) - m_currReturnMessage += QString("No terms found, sorry."); + m_currReturnMessage += tr("No terms found, sorry."); else { bool first = true; @@ -341,7 +341,7 @@ void XMPPBot::infoReturnedSlot(QString caller, Tomahawk::InfoSystem::InfoType ty QString artist = input.toString(); qreal retVal = output.toReal(); QString retValString = (retVal == 0.0 ? "(none)" : QString::number(retVal)); - m_currReturnMessage += QString("\nHotttness for %1: %2\n").arg(artist).arg(retValString); + m_currReturnMessage += tr("\nHotttness for %1: %2\n").arg(artist, retValString); break; } case InfoArtistFamiliarity: @@ -357,7 +357,7 @@ void XMPPBot::infoReturnedSlot(QString caller, Tomahawk::InfoSystem::InfoType ty QString artist = input.toString(); qreal retVal = output.toReal(); QString retValString = (retVal == 0.0 ? "(none)" : QString::number(retVal)); - m_currReturnMessage += QString("\nFamiliartiy for %1: %2\n").arg(artist).arg(retValString); + m_currReturnMessage += tr("\nFamiliarity for %1: %2\n").arg(artist, retValString); break; } case InfoTrackLyrics: @@ -375,7 +375,7 @@ void XMPPBot::infoReturnedSlot(QString caller, Tomahawk::InfoSystem::InfoType ty QString track = inHash["trackName"].toString(); QString lyrics = output.toString(); qDebug() << "lyrics = " << lyrics; - m_currReturnMessage += QString("\nLyrics for \"%1\" by %2:\n\n%3\n").arg(track).arg(artist).arg(lyrics); + m_currReturnMessage += tr("\nLyrics for \"%1\" by %2:\n\n%3\n").arg(track, artist, lyrics); break; } default: From b6d696928156c6abb78777d491e5c40112d0c710 Mon Sep 17 00:00:00 2001 From: Frank Osterfeld Date: Wed, 30 Mar 2011 18:14:29 +0200 Subject: [PATCH 215/329] pass parent to messageboxes --- src/sip/SipHandler.cpp | 6 +++--- src/sip/twitter/twitterconfigwidget.cpp | 16 ++++++++-------- src/tomahawkwindow.cpp | 12 ++++++------ .../tomahawk-custom/tomahawkoauthtwitter.cpp | 2 +- 4 files changed, 18 insertions(+), 18 deletions(-) diff --git a/src/sip/SipHandler.cpp b/src/sip/SipHandler.cpp index b07eb796c..59aa8ea12 100644 --- a/src/sip/SipHandler.cpp +++ b/src/sip/SipHandler.cpp @@ -182,9 +182,9 @@ SipHandler::connectPlugins( bool startup, const QString &pluginName ) if ( !TomahawkSettings::instance()->acceptedLegalWarning() ) { int result = QMessageBox::question( - TomahawkApp::instance()->mainWindow(), "Legal Warning", - "By pressing OK below, you agree that your use of Tomahawk will be in accordance with any applicable laws, including copyright and intellectual property laws, in effect in your country of residence, and indemnify the Tomahawk developers and project from liability should you choose to break those laws.\n\nFor more information, please see http://gettomahawk.com/legal", - "I Do Not Agree", "I Agree" + TomahawkApp::instance()->mainWindow(), tr( "Legal Warning" ), + tr( "By pressing OK below, you agree that your use of Tomahawk will be in accordance with any applicable laws, including copyright and intellectual property laws, in effect in your country of residence, and indemnify the Tomahawk developers and project from liability should you choose to break those laws.\n\nFor more information, please see http://gettomahawk.com/legal" ), + tr( "I Do Not Agree" ), tr( "I Agree" ) ); if ( result != 1 ) return; diff --git a/src/sip/twitter/twitterconfigwidget.cpp b/src/sip/twitter/twitterconfigwidget.cpp index 8211f97dc..00eb46d87 100644 --- a/src/sip/twitter/twitterconfigwidget.cpp +++ b/src/sip/twitter/twitterconfigwidget.cpp @@ -114,7 +114,7 @@ TwitterConfigWidget::authenticateVerifyReply( const QTweetUser &user ) qDebug() << Q_FUNC_INFO; if ( user.id() == 0 ) { - QMessageBox::critical( 0, tr("Tweetin' Error"), tr("The credentials could not be verified.\nYou may wish to try re-authenticating.") ); + QMessageBox::critical( this, tr("Tweetin' Error"), tr("The credentials could not be verified.\nYou may wish to try re-authenticating.") ); emit twitterAuthed( false ); return; } @@ -194,7 +194,7 @@ TwitterConfigWidget::startPostGotTomahawkStatus() if ( m_postGTtype != "Global Tweet" && ( ui->twitterUserTweetLineEdit->text().isEmpty() || ui->twitterUserTweetLineEdit->text() == "@" ) ) { - QMessageBox::critical( 0, tr("Tweetin' Error"), tr("You must enter a user name for this type of tweet.") ); + QMessageBox::critical( this, tr("Tweetin' Error"), tr("You must enter a user name for this type of tweet.") ); return; } @@ -220,7 +220,7 @@ TwitterConfigWidget::postGotTomahawkStatusAuthVerifyReply( const QTweetUser &use { if ( user.id() == 0 ) { - QMessageBox::critical( 0, tr("Tweetin' Error"), tr("Your saved credentials could not be verified.\nYou may wish to try re-authenticating.") ); + QMessageBox::critical( this, tr("Tweetin' Error"), tr("Your saved credentials could not be verified.\nYou may wish to try re-authenticating.") ); emit twitterAuthed( false ); return; } @@ -264,18 +264,18 @@ void TwitterConfigWidget::postGotTomahawkStatusUpdateReply( const QTweetStatus& status ) { if ( status.id() == 0 ) - QMessageBox::critical( 0, tr("Tweetin' Error"), tr("There was an error posting your status -- sorry!") ); + QMessageBox::critical( this, tr("Tweetin' Error"), tr("There was an error posting your status -- sorry!") ); else - QMessageBox::information( 0, tr("Tweeted!"), tr("Your tweet has been posted!") ); + QMessageBox::information( this, tr("Tweeted!"), tr("Your tweet has been posted!") ); } void TwitterConfigWidget::postGotTomahawkDirectMessageReply( const QTweetDMStatus& status ) { if ( status.id() == 0 ) - QMessageBox::critical( 0, tr("Tweetin' Error"), tr("There was an error posting your direct message -- sorry!") ); + QMessageBox::critical( this, tr("Tweetin' Error"), tr("There was an error posting your direct message -- sorry!") ); else - QMessageBox::information( 0, tr("Tweeted!"), tr("Your message has been posted!") ); + QMessageBox::information( this, tr("Tweeted!"), tr("Your message has been posted!") ); } void @@ -283,5 +283,5 @@ TwitterConfigWidget::postGotTomahawkStatusUpdateError( QTweetNetBase::ErrorCode { qDebug() << Q_FUNC_INFO; qDebug() << "Error posting Got Tomahawk message, error code is " << code << ", error message is " << errorMsg; - QMessageBox::critical( 0, tr("Tweetin' Error"), tr("There was an error posting your status -- sorry!") ); + QMessageBox::critical( this, tr("Tweetin' Error"), tr("There was an error posting your status -- sorry!") ); } diff --git a/src/tomahawkwindow.cpp b/src/tomahawkwindow.cpp index 868f1609b..a81395193 100644 --- a/src/tomahawkwindow.cpp +++ b/src/tomahawkwindow.cpp @@ -380,7 +380,7 @@ void TomahawkWindow::loadSpiff() { bool ok; - QString urlstr = QInputDialog::getText( this, "Load XSPF", "Path:", QLineEdit::Normal, "http://ws.audioscrobbler.com/1.0/tag/metal/toptracks.xspf", &ok ); + QString urlstr = QInputDialog::getText( this, tr( "Load XSPF" ), tr( "Path:" ), QLineEdit::Normal, "http://ws.audioscrobbler.com/1.0/tag/metal/toptracks.xspf", &ok ); if ( !ok || urlstr.isEmpty() ) return; @@ -395,7 +395,7 @@ void TomahawkWindow::createAutomaticPlaylist() { bool ok; - QString name = QInputDialog::getText( this, "Create New Automatic Playlist", "Name:", QLineEdit::Normal, "New Automatic Playlist", &ok ); + QString name = QInputDialog::getText( this, tr( "Create New Automatic Playlist" ), tr( "Name:" ), QLineEdit::Normal, tr( "New Automatic Playlist" ), &ok ); if ( !ok || name.isEmpty() ) return; @@ -414,7 +414,7 @@ void TomahawkWindow::createStation() { bool ok; - QString name = QInputDialog::getText( this, "Create New Station", "Name:", QLineEdit::Normal, "New Station", &ok ); + QString name = QInputDialog::getText( this, tr( "Create New Station" ), tr( "Name:" ), QLineEdit::Normal, tr( "New Station" ), &ok ); if ( !ok || name.isEmpty() ) return; @@ -493,8 +493,8 @@ TomahawkWindow::setWindowTitle( const QString& title ) QMainWindow::setWindowTitle( title ); else { - QString s = m_currentTrack->track() + " " + tr( "by" ) + " " + m_currentTrack->artist()->name(); - QMainWindow::setWindowTitle( s + " - " + title ); + QString s = tr( "%1 by %2", "track, artist name" ).arg( m_currentTrack->track(), m_currentTrack->artist()->name() ); + QMainWindow::setWindowTitle( tr( "%1 - %2", "current track, some window title" ).arg( s, title ) ); } } @@ -502,7 +502,7 @@ TomahawkWindow::setWindowTitle( const QString& title ) void TomahawkWindow::showAboutTomahawk() { - QMessageBox::about( this, "About Tomahawk", + QMessageBox::about( this, tr( "About Tomahawk" ), tr( "

Tomahawk %1

Copyright 2010, 2011
Christian Muehlhaeuser <muesli@tomahawk-player.org>

" "Thanks to: Leo Franchi, Jeff Mitchell, Dominik Schmidt, Jason Herskowitz, Alejandro Wainzinger, Harald Sitter and Steve Robertson" ) .arg( qApp->applicationVersion() ) ); diff --git a/thirdparty/qtweetlib/tomahawk-custom/tomahawkoauthtwitter.cpp b/thirdparty/qtweetlib/tomahawk-custom/tomahawkoauthtwitter.cpp index 0d4c3e06b..8f2549643 100644 --- a/thirdparty/qtweetlib/tomahawk-custom/tomahawkoauthtwitter.cpp +++ b/thirdparty/qtweetlib/tomahawk-custom/tomahawkoauthtwitter.cpp @@ -12,7 +12,7 @@ int TomahawkOAuthTwitter::authorizationWidget() { bool ok; - int i = QInputDialog::getInt(0, QString( "Twitter PIN" ), QString( "After authenticating on Twitter's web site,\nenter the displayed PIN number here:" ), 0, 0, 2147483647, 1, &ok); + int i = QInputDialog::getInt(0, tr( "Twitter PIN" ), tr( "After authenticating on Twitter's web site,\nenter the displayed PIN number here:" ), 0, 0, 2147483647, 1, &ok); if (ok) return i; From a05795bf6bcf4015151f57fb2fcfb068de2fe8c6 Mon Sep 17 00:00:00 2001 From: Frank Osterfeld Date: Wed, 30 Mar 2011 18:23:53 +0200 Subject: [PATCH 216/329] fix case --- src/tomahawkwindow.cpp | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/tomahawkwindow.cpp b/src/tomahawkwindow.cpp index a81395193..843d8b525 100644 --- a/src/tomahawkwindow.cpp +++ b/src/tomahawkwindow.cpp @@ -147,7 +147,7 @@ TomahawkWindow::TomahawkWindow( QWidget* parent ) toolbar->installEventFilter( new WidgetDragFilter( toolbar ) ); #if defined( Q_OS_DARWIN ) && defined( HAVE_SPARKLE ) - QAction* checkForUpdates = ui->menu_Help->addAction( tr( "Check for updates...") ); + QAction* checkForUpdates = ui->menu_Help->addAction( tr( "Check For Updates...") ); checkForUpdates->setMenuRole( QAction::ApplicationSpecificRole ); connect(checkForUpdates, SIGNAL( triggered( bool ) ), SLOT( checkForUpdates() ) ); #elif defined( WIN32 ) @@ -163,7 +163,7 @@ TomahawkWindow::TomahawkWindow( QWidget* parent ) updater->SetVersion( VERSION ); ui->menu_Help->addSeparator(); - QAction* checkForUpdates = ui->menu_Help->addAction( tr( "Check for updates...") ); + QAction* checkForUpdates = ui->menu_Help->addAction( tr( "Check For Updates...") ); connect( checkForUpdates, SIGNAL( triggered() ), updater, SLOT( CheckNow() ) ); #endif From 56deed391e48357b3a094f4c98c5473bdc40d8c9 Mon Sep 17 00:00:00 2001 From: Steven Robertson Date: Thu, 31 Mar 2011 00:14:20 +0100 Subject: [PATCH 217/329] Added check for running tomahawk.exe process and option to kill the process (or ignore) and proceed. --- admin/win/README.txt | 0 admin/win/nsi/RELEASE_NOTES.txt | 0 admin/win/nsi/installer.ico | Bin .../win/nsi/nsis_processes/bin/Processes.dll | Bin 0 -> 36352 bytes admin/win/nsi/nsis_processes/license.rtf | 35 ++ admin/win/nsi/nsis_processes/readme.txt | 122 ++++++ admin/win/nsi/nsis_processes/src/StdAfx.cpp | 8 + admin/win/nsi/nsis_processes/src/StdAfx.h | 34 ++ admin/win/nsi/nsis_processes/src/exdll.c | 37 ++ admin/win/nsi/nsis_processes/src/exdll.h | 136 ++++++ .../win/nsi/nsis_processes/src/processes.cpp | 411 ++++++++++++++++++ admin/win/nsi/nsis_processes/src/processes.h | 49 +++ .../win/nsi/nsis_processes/src/processes.ncb | Bin 0 -> 44032 bytes admin/win/nsi/nsis_processes/src/processes.rc | 103 +++++ .../win/nsi/nsis_processes/src/processes.sln | 21 + .../win/nsi/nsis_processes/src/processes.txt | 122 ++++++ .../nsi/nsis_processes/src/processes.vcproj | 222 ++++++++++ admin/win/nsi/nsis_processes/src/resource.h | 15 + admin/win/nsi/page_header.bmp | Bin admin/win/nsi/revision.txt | 2 +- admin/win/nsi/tomahawk.ini | 0 admin/win/nsi/tomahawk.nsi | 42 ++ admin/win/nsi/welcome.bmp | Bin admin/win/page_header.bmp | Bin 25818 -> 0 bytes admin/win/welcome.bmp | Bin 154542 -> 0 bytes 25 files changed, 1358 insertions(+), 1 deletion(-) delete mode 100755 admin/win/README.txt mode change 100755 => 100644 admin/win/nsi/RELEASE_NOTES.txt mode change 100755 => 100644 admin/win/nsi/installer.ico create mode 100755 admin/win/nsi/nsis_processes/bin/Processes.dll create mode 100755 admin/win/nsi/nsis_processes/license.rtf create mode 100755 admin/win/nsi/nsis_processes/readme.txt create mode 100755 admin/win/nsi/nsis_processes/src/StdAfx.cpp create mode 100755 admin/win/nsi/nsis_processes/src/StdAfx.h create mode 100755 admin/win/nsi/nsis_processes/src/exdll.c create mode 100755 admin/win/nsi/nsis_processes/src/exdll.h create mode 100755 admin/win/nsi/nsis_processes/src/processes.cpp create mode 100755 admin/win/nsi/nsis_processes/src/processes.h create mode 100755 admin/win/nsi/nsis_processes/src/processes.ncb create mode 100755 admin/win/nsi/nsis_processes/src/processes.rc create mode 100755 admin/win/nsi/nsis_processes/src/processes.sln create mode 100755 admin/win/nsi/nsis_processes/src/processes.txt create mode 100755 admin/win/nsi/nsis_processes/src/processes.vcproj create mode 100755 admin/win/nsi/nsis_processes/src/resource.h mode change 100755 => 100644 admin/win/nsi/page_header.bmp mode change 100644 => 100755 admin/win/nsi/revision.txt mode change 100755 => 100644 admin/win/nsi/tomahawk.ini mode change 100755 => 100644 admin/win/nsi/tomahawk.nsi mode change 100755 => 100644 admin/win/nsi/welcome.bmp delete mode 100755 admin/win/page_header.bmp delete mode 100755 admin/win/welcome.bmp diff --git a/admin/win/README.txt b/admin/win/README.txt deleted file mode 100755 index e69de29bb..000000000 diff --git a/admin/win/nsi/RELEASE_NOTES.txt b/admin/win/nsi/RELEASE_NOTES.txt old mode 100755 new mode 100644 diff --git a/admin/win/nsi/installer.ico b/admin/win/nsi/installer.ico old mode 100755 new mode 100644 diff --git a/admin/win/nsi/nsis_processes/bin/Processes.dll b/admin/win/nsi/nsis_processes/bin/Processes.dll new file mode 100755 index 0000000000000000000000000000000000000000..e532bf8bb55a5b7cadfe6d6e05058ae3f446739a GIT binary patch literal 36352 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≦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 literal 0 HcmV?d00001 diff --git a/admin/win/nsi/nsis_processes/license.rtf b/admin/win/nsi/nsis_processes/license.rtf new file mode 100755 index 000000000..2ce5a58c9 --- /dev/null +++ b/admin/win/nsi/nsis_processes/license.rtf @@ -0,0 +1,35 @@ +{\rtf1\ansi\ansicpg1252\uc1\deff0\stshfdbch0\stshfloch0\stshfhich0\stshfbi0\deflang1033\deflangfe1033{\fonttbl{\f0\froman\fcharset0\fprq2{\*\panose 02020603050405020304}Times New Roman;}{\f39\fswiss\fcharset0\fprq2{\*\panose 020b0604030504040204}Verdana;} +{\f172\froman\fcharset238\fprq2 Times New Roman CE;}{\f173\froman\fcharset204\fprq2 Times New Roman Cyr;}{\f175\froman\fcharset161\fprq2 Times New Roman Greek;}{\f176\froman\fcharset162\fprq2 Times New Roman Tur;} +{\f177\froman\fcharset177\fprq2 Times New Roman (Hebrew);}{\f178\froman\fcharset178\fprq2 Times New Roman (Arabic);}{\f179\froman\fcharset186\fprq2 Times New Roman Baltic;}{\f180\froman\fcharset163\fprq2 Times New Roman (Vietnamese);} +{\f562\fswiss\fcharset238\fprq2 Verdana CE;}{\f563\fswiss\fcharset204\fprq2 Verdana Cyr;}{\f565\fswiss\fcharset161\fprq2 Verdana Greek;}{\f566\fswiss\fcharset162\fprq2 Verdana Tur;}{\f569\fswiss\fcharset186\fprq2 Verdana Baltic;} +{\f570\fswiss\fcharset163\fprq2 Verdana (Vietnamese);}}{\colortbl;\red0\green0\blue0;\red0\green0\blue255;\red0\green255\blue255;\red0\green255\blue0;\red255\green0\blue255;\red255\green0\blue0;\red255\green255\blue0;\red255\green255\blue255; +\red0\green0\blue128;\red0\green128\blue128;\red0\green128\blue0;\red128\green0\blue128;\red128\green0\blue0;\red128\green128\blue0;\red128\green128\blue128;\red192\green192\blue192;}{\stylesheet{ +\ql \li0\ri0\widctlpar\aspalpha\aspnum\faauto\adjustright\rin0\lin0\itap0 \fs24\lang1033\langfe1033\cgrid\langnp1033\langfenp1033 \snext0 Normal;}{\*\cs10 \additive \ssemihidden Default Paragraph Font;}{\* +\ts11\tsrowd\trftsWidthB3\trpaddl108\trpaddr108\trpaddfl3\trpaddft3\trpaddfb3\trpaddfr3\trcbpat1\trcfpat1\tscellwidthfts0\tsvertalt\tsbrdrt\tsbrdrl\tsbrdrb\tsbrdrr\tsbrdrdgl\tsbrdrdgr\tsbrdrh\tsbrdrv +\ql \li0\ri0\widctlpar\aspalpha\aspnum\faauto\adjustright\rin0\lin0\itap0 \fs20\lang1024\langfe1024\cgrid\langnp1024\langfenp1024 \snext11 \ssemihidden Normal Table;}{\*\cs15 \additive \ul\cf2 \sbasedon10 \styrsid7485074 Hyperlink;}} +{\*\latentstyles\lsdstimax156\lsdlockeddef0}{\*\rsidtbl \rsid6712196\rsid7485074\rsid11352300\rsid15940516}{\*\generator Microsoft Word 11.0.5604;}{\info{\title Processes v1}{\author Hardwired}{\operator Hardwired}{\creatim\yr2004\mo12\dy12\hr23\min42} +{\revtim\yr2004\mo12\dy12\hr23\min51}{\version2}{\edmins9}{\nofpages1}{\nofwords80}{\nofchars458}{\nofcharsws537}{\vern24689}}\widowctrl\ftnbj\aenddoc\noxlattoyen\expshrtn\noultrlspc\dntblnsbdb\nospaceforul\formshade\horzdoc\dgmargin\dghspace180 +\dgvspace180\dghorigin1800\dgvorigin1440\dghshow1\dgvshow1 +\jexpand\viewkind1\viewscale100\pgbrdrhead\pgbrdrfoot\splytwnine\ftnlytwnine\htmautsp\nolnhtadjtbl\useltbaln\alntblind\lytcalctblwd\lyttblrtgr\lnbrkrule\nobrkwrptbl\snaptogridincell\allowfieldendsel\wrppunct +\asianbrkrule\rsidroot7485074\newtblstyruls\nogrowautofit \fet0\sectd \linex0\endnhere\sectlinegrid360\sectdefaultcl\sftnbj {\*\pnseclvl1\pnucrm\pnstart1\pnindent720\pnhang {\pntxta .}}{\*\pnseclvl2\pnucltr\pnstart1\pnindent720\pnhang {\pntxta .}} +{\*\pnseclvl3\pndec\pnstart1\pnindent720\pnhang {\pntxta .}}{\*\pnseclvl4\pnlcltr\pnstart1\pnindent720\pnhang {\pntxta )}}{\*\pnseclvl5\pndec\pnstart1\pnindent720\pnhang {\pntxtb (}{\pntxta )}}{\*\pnseclvl6\pnlcltr\pnstart1\pnindent720\pnhang {\pntxtb (} +{\pntxta )}}{\*\pnseclvl7\pnlcrm\pnstart1\pnindent720\pnhang {\pntxtb (}{\pntxta )}}{\*\pnseclvl8\pnlcltr\pnstart1\pnindent720\pnhang {\pntxtb (}{\pntxta )}}{\*\pnseclvl9\pnlcrm\pnstart1\pnindent720\pnhang {\pntxtb (}{\pntxta )}}\pard\plain +\qj \li0\ri0\widctlpar\aspalpha\aspnum\faauto\adjustright\rin0\lin0\itap0\pararsid7485074 \fs24\lang1033\langfe1033\cgrid\langnp1033\langfenp1033 {\b\f39\insrsid7485074\charrsid7485074 Processes v1.0}{\f39\insrsid7485074\charrsid7485074 .0.1 +\par }{\f39\fs20\insrsid7485074 +\par }\pard \qj \li0\ri0\widctlpar\aspalpha\aspnum\faauto\adjustright\rin0\lin0\itap0\pararsid15940516 {\f39\fs20\insrsid15940516 This software binaries and source-code are free for any kind of use, including commercial use. }{ +\f39\fs20\insrsid7485074\charrsid7485074 There is no restriction and no guaranty for using}{\f39\fs20\insrsid7485074\charrsid7485074 t}{\f39\fs20\insrsid7485074\charrsid7485074 his software}{\f39\fs20\insrsid7485074\charrsid7485074 and/or it +s source-code. }{\f39\fs20\insrsid15940516 +\par I}{\f39\fs20\insrsid7485074\charrsid7485074 f you use the plug}{\f39\fs20\insrsid7485074\charrsid7485074 -}{\f39\fs20\insrsid7485074\charrsid7485074 in }{\f39\fs20\insrsid7485074\charrsid7485074 and/}{\f39\fs20\insrsid7485074\charrsid7485074 or it}{ +\f39\fs20\insrsid7485074\charrsid7485074 s}{\f39\fs20\insrsid7485074\charrsid7485074 source-code, I would }{\f39\fs20\insrsid7485074\charrsid7485074 appreciate }{\f39\fs20\insrsid7485074\charrsid7485074 if my name is mentioned.}{ +\f39\fs20\insrsid7485074\charrsid7485074 +\par }\pard \qj \li0\ri0\widctlpar\aspalpha\aspnum\faauto\adjustright\rin0\lin0\itap0\pararsid7485074 {\f39\fs20\insrsid7485074\charrsid7485074 +\par }{\b\f39\fs20\insrsid7485074\charrsid7485074 Andrei Ciubotaru [Hardwired] +\par }{\f39\fs20\insrsid7485074\charrsid7485074 Lead Developer ICode&Ideas SRL (}{\field\flddirty{\*\fldinst {\f39\fs20\insrsid7485074\charrsid7485074 HYPERLINK "http://www.icode.ro/" }{\f39\fs20\insrsid7485074\charrsid7485074 {\*\datafield +00d0c9ea79f9bace118c8200aa004ba90b02000000170000001500000068007400740070003a002f002f007700770077002e00690063006f00640065002e0072006f002f000000e0c9ea79f9bace118c8200aa004ba90b2a00000068007400740070003a002f002f007700770077002e00690063006f00640065002e007200 +6f002f000000}}}{\fldrslt {\cs15\f39\fs20\ul\cf2\insrsid7485074\charrsid7485074 http://www.icode.ro/}}}{\f39\fs20\insrsid7485074\charrsid7485074 ) +\par }{\field{\*\fldinst {\f39\fs20\insrsid7485074 HYPERLINK "hardwiredteks@gmail.com" }{\f39\fs20\insrsid15940516\charrsid7485074 {\*\datafield +00d0c9ea79f9bace118c8200aa004ba90b02000000010000000303000000000000c00000000000004600001800000068617264776972656474656b7340676d61696c2e636f6d00ffffadde000000000000000000000000000000000000000000000000}}}{\fldrslt { +\cs15\f39\fs20\ul\cf2\insrsid7485074\charrsid7485074 hardwiredteks@gmail.com}}}{\f39\fs20\insrsid7485074\charrsid7485074 , }{\field{\*\fldinst {\f39\fs20\insrsid7485074 HYPERLINK "hardwired@icode.ro" }{\f39\fs20\insrsid15940516\charrsid7485074 +{\*\datafield 00d0c9ea79f9bace118c8200aa004ba90b02000000010000000303000000000000c0000000000000460000130000006861726477697265644069636f64652e726f00ffffadde000000000000000000000000000000000000000000000000}}}{\fldrslt { +\cs15\f39\fs20\ul\cf2\insrsid7485074\charrsid7485074 hardwired@icode.ro}}}{\f39\fs20\insrsid7485074\charrsid7485074 +\par }} \ No newline at end of file diff --git a/admin/win/nsi/nsis_processes/readme.txt b/admin/win/nsi/nsis_processes/readme.txt new file mode 100755 index 000000000..8529c39ad --- /dev/null +++ b/admin/win/nsi/nsis_processes/readme.txt @@ -0,0 +1,122 @@ +---------------------------------------------------------------- +---------------------------------------------------------------- +Processes (Processes.dll) +Version: 1.0.1.0 +Release: 24.february.2005 +Description: Nullsoft Installer (NSIS) plug-in for managing?! + Windows processes. + +Copyright: © 2004-2005 Hardwired. No rights reserved. + There is no restriction and no guaranty for using + this software. + +Author: Andrei Ciubotaru [Hardwired] + Lead Developer ICode&Ideas SRL (http://www.icode.ro/) + hardwiredteks@gmail.com, hardwired@icode.ro + +---------------------------------------------------------------- +---------------------------------------------------------------- +INTRODUCTION + + The Need For Plug-in - I need it for the one of my installers. + + Briefly: Use it when you need to find\kill a process when +installing\uninstalling some application. Also, use it when you +need to test the presence of a device driver. + + +SUPPORT + + Supported platforms are: WinNT,Win2K,WinXP and Win2003 Server. + + +DESCRIPTION + + Processes::FindProcess ;without ".exe" + + Searches the currently running processes for the given + process name. + + return: 1 - the process was found + 0 - the process was not found + + Processes::KillProcess ; without ".exe" + + Searches the currently running processes for the given + process name. If the process is found then the it gets + killed. + + return: 1 - the process was found and killed + 0 - the process was not found or the process + cannot be killed (insuficient rights) + + Processes::FindDevice + + Searches the installed devices drivers for the given + device base name. + (important: I said BASE NAME not FILENAME) + + return: 1 - the device driver was found + 0 - the device driver was not found + + +USAGE + + First of all, does not matter where you use it. Ofcourse, the +routines must be called inside of a Section/Function scope. + + Processes::FindProcess "process_name" + Pop $R0 + + StrCmp $R0 "1" make_my_day noooooo + + make_my_day: + ... + + noooooo: + ... + + + Processes::KillProcess "process_name" + Pop $R0 + + StrCmp $R0 "1" dead_meat why_wont_you_die + + dead_meat: + ... + + why_wont_you_die: + ... + + + Processes::FindDevice "device_base_name" + Pop $R0 + + StrCmp $R0 "1" blabla more_blabla + + blabla: + ... + + more_blabla: + ... + + +THANKS + + Sunil Kamath for inspiring me. I wanted to use its FindProcDLL +but my requirements made it imposible. + + Nullsoft for creating this very powerfull installer. One big, +free and full-featured (hmmm... and guiless for the moment) mean +install machine!:) + + ME for being such a great coder... + ... HAHAHAHAHAHAHA! + +ONE MORE THING + + If you use the plugin or it's source-code, I would apreciate +if my name is mentioned. + +---------------------------------------------------------------- +---------------------------------------------------------------- diff --git a/admin/win/nsi/nsis_processes/src/StdAfx.cpp b/admin/win/nsi/nsis_processes/src/StdAfx.cpp new file mode 100755 index 000000000..f38accc8a --- /dev/null +++ b/admin/win/nsi/nsis_processes/src/StdAfx.cpp @@ -0,0 +1,8 @@ +// stdafx.cpp : source file that includes just the standard includes +// KillProcDLL.pch will be the pre-compiled header +// stdafx.obj will contain the pre-compiled type information + +#include "stdafx.h" + +// TODO: reference any additional headers you need in STDAFX.H +// and not in this file diff --git a/admin/win/nsi/nsis_processes/src/StdAfx.h b/admin/win/nsi/nsis_processes/src/StdAfx.h new file mode 100755 index 000000000..dd49f99b9 --- /dev/null +++ b/admin/win/nsi/nsis_processes/src/StdAfx.h @@ -0,0 +1,34 @@ +// stdafx.h : include file for standard system include files, +// or project specific include files that are used frequently, but +// are changed infrequently +// + +#if !defined(AFX_STDAFX_H__780690DC_E128_403D_BC07_780D1B2CC101__INCLUDED_) +#define AFX_STDAFX_H__780690DC_E128_403D_BC07_780D1B2CC101__INCLUDED_ + +#if _MSC_VER > 1000 +#pragma once +#endif // _MSC_VER > 1000 + +#define WIN32_LEAN_AND_MEAN // Exclude rarely-used stuff from Windows headers + +#include + +#include // String management... + +//From exam28.cpp +#include +//#include + +#ifdef BORLANDC + #include + #include +#endif + +//To make it a NSIS Plug-In +#include "exdll.h" + +//{{AFX_INSERT_LOCATION}} +// Microsoft Visual C++ will insert additional declarations immediately before the previous line. + +#endif // !defined(AFX_STDAFX_H__780690DC_E128_403D_BC07_780D1B2CC101__INCLUDED_) diff --git a/admin/win/nsi/nsis_processes/src/exdll.c b/admin/win/nsi/nsis_processes/src/exdll.c new file mode 100755 index 000000000..7092cb840 --- /dev/null +++ b/admin/win/nsi/nsis_processes/src/exdll.c @@ -0,0 +1,37 @@ +#include +#include "exdll.h" + +HINSTANCE g_hInstance; + +HWND g_hwndParent; + +void __declspec(dllexport) myFunction(HWND hwndParent, int string_size, + char *variables, stack_t **stacktop) +{ + g_hwndParent=hwndParent; + + EXDLL_INIT(); + + + // note if you want parameters from the stack, pop them off in order. + // i.e. if you are called via exdll::myFunction file.dat poop.dat + // calling popstring() the first time would give you file.dat, + // and the second time would give you poop.dat. + // you should empty the stack of your parameters, and ONLY your + // parameters. + + // do your stuff here + { + char buf[1024]; + wsprintf(buf,"$0=%s\n",getuservariable(INST_0)); + MessageBox(g_hwndParent,buf,0,MB_OK); + } +} + + + +BOOL WINAPI _DllMainCRTStartup(HANDLE hInst, ULONG ul_reason_for_call, LPVOID lpReserved) +{ + g_hInstance=hInst; + return TRUE; +} diff --git a/admin/win/nsi/nsis_processes/src/exdll.h b/admin/win/nsi/nsis_processes/src/exdll.h new file mode 100755 index 000000000..777d93be5 --- /dev/null +++ b/admin/win/nsi/nsis_processes/src/exdll.h @@ -0,0 +1,136 @@ +#ifndef _EXDLL_H_ +#define _EXDLL_H_ + + + + + +// +// only include this file from one place in your DLL. +// (it is all static, if you use it in two places it will fail) +// +#define EXDLL_INIT() { \ + g_stringsize = string_size; \ + g_stacktop = stacktop; \ + g_variables = variables; } + + + + +// +// For page showing plug-ins +// +#define WM_NOTIFY_OUTER_NEXT (WM_USER+0x8) +#define WM_NOTIFY_CUSTOM_READY (WM_USER+0xd) +#define NOTIFY_BYE_BYE 'x' + +typedef struct _stack_t +{ + struct _stack_t *next; + char text[1]; // this should be the length of string_size +} stack_t; + + +static unsigned int g_stringsize; +static stack_t **g_stacktop; +static char *g_variables; + +enum +{ +INST_0, // $0 +INST_1, // $1 +INST_2, // $2 +INST_3, // $3 +INST_4, // $4 +INST_5, // $5 +INST_6, // $6 +INST_7, // $7 +INST_8, // $8 +INST_9, // $9 +INST_R0, // $R0 +INST_R1, // $R1 +INST_R2, // $R2 +INST_R3, // $R3 +INST_R4, // $R4 +INST_R5, // $R5 +INST_R6, // $R6 +INST_R7, // $R7 +INST_R8, // $R8 +INST_R9, // $R9 +INST_CMDLINE, // $CMDLINE +INST_INSTDIR, // $INSTDIR +INST_OUTDIR, // $OUTDIR +INST_EXEDIR, // $EXEDIR +INST_LANG, // $LANGUAGE +__INST_LAST +}; + + + + + +// +// utility functions (not required but often useful) +// +static int popstring( char *str ) +{ + stack_t *th; + + + if( !g_stacktop || + !*g_stacktop ) + return 1; + + th = (*g_stacktop); + lstrcpy( str, th->text ); + *g_stacktop = th->next; + GlobalFree( (HGLOBAL)th ); + + return 0; +} + + + + +static void pushstring( char *str ) +{ + stack_t *th; + + + if( !g_stacktop ) + return; + + th = (stack_t*)GlobalAlloc( GPTR, sizeof(stack_t) + g_stringsize ); + lstrcpyn( th->text, str, g_stringsize ); + th->next = *g_stacktop; + *g_stacktop = th; +} + + + + + +static char *getuservariable( int varnum ) +{ + if( varnum < 0 || + varnum >= __INST_LAST ) + return NULL; + + return (g_variables + varnum*g_stringsize); +} + + + + + +static void setuservariable( int varnum, char *var ) +{ + if( var != NULL && + varnum >= 0 && + varnum < __INST_LAST ) + lstrcpy( g_variables + varnum*g_stringsize, var ); +} + + + +#endif//_EXDLL_H_ \ No newline at end of file diff --git a/admin/win/nsi/nsis_processes/src/processes.cpp b/admin/win/nsi/nsis_processes/src/processes.cpp new file mode 100755 index 000000000..c15f8f94a --- /dev/null +++ b/admin/win/nsi/nsis_processes/src/processes.cpp @@ -0,0 +1,411 @@ +#include "stdafx.h" +#include "processes.h" +#include "string.h" + + + + + + +//------------------------------------------------------------------------------------------- +// global variables +lpfEnumProcesses EnumProcesses; +lpfEnumProcessModules EnumProcessModules; +lpfGetModuleBaseName GetModuleBaseName; +lpfEnumDeviceDrivers EnumDeviceDrivers; +lpfGetDeviceDriverBaseName GetDeviceDriverBaseName; + +HINSTANCE g_hInstance; +HWND g_hwndParent; +HINSTANCE g_hInstLib; + + + + + +//------------------------------------------------------------------------------------------- +// main DLL entry +BOOL WINAPI _DllMainCRTStartup( HANDLE hInst, + ULONG ul_reason_for_call, + LPVOID lpReserved ) +{ + g_hInstance = (struct HINSTANCE__ *)hInst; + + return TRUE; +} + + + + + +//------------------------------------------------------------------------------------------- +// loads the psapi routines +bool LoadPSAPIRoutines( void ) +{ + if( NULL == (g_hInstLib = LoadLibraryA( "PSAPI.DLL" )) ) + return false; + + EnumProcesses = (lpfEnumProcesses) GetProcAddress( g_hInstLib, "EnumProcesses" ); + EnumProcessModules = (lpfEnumProcessModules) GetProcAddress( g_hInstLib, "EnumProcessModules" ); + GetModuleBaseName = (lpfGetModuleBaseName) GetProcAddress( g_hInstLib, "GetModuleBaseNameA" ); + EnumDeviceDrivers = (lpfEnumDeviceDrivers) GetProcAddress( g_hInstLib, "EnumDeviceDrivers" ); + GetDeviceDriverBaseName = (lpfGetDeviceDriverBaseName) GetProcAddress( g_hInstLib, "GetDeviceDriverBaseNameA" ); + + if( ( NULL == EnumProcesses ) || + ( NULL == EnumProcessModules ) || + ( NULL == EnumDeviceDrivers ) || + ( NULL == GetModuleBaseName ) || + ( NULL == GetDeviceDriverBaseName ) ) + { + FreeLibrary( g_hInstLib ); + + return false; + } + + return true; +} + + + + + +//------------------------------------------------------------------------------------------- +// free the psapi routines +bool FreePSAPIRoutines( void ) +{ + EnumProcesses = NULL; + EnumProcessModules = NULL; + GetModuleBaseName = NULL; + EnumDeviceDrivers = NULL; + + if( FALSE == FreeLibrary( g_hInstLib ) ) + return false; + + return true; +} + + + + + +//------------------------------------------------------------------------------------------- +// find a process by name +// return value: true - process was found +// false - process not found +bool FindProc( char *szProcess ) +{ + char szProcessName[ 1024 ]; + char szCurrentProcessName[ 1024 ]; + DWORD dPID[ 1024 ]; + DWORD dPIDSize( 1024 ); + DWORD dSize( 1024 ); + HANDLE hProcess; + HMODULE phModule[ 1024 ]; + + + // + // make the name lower case + // + memset( szProcessName, 0, 1024*sizeof(char) ); + sprintf( szProcessName, "%s", szProcess ); + strlwr( szProcessName ); + + // + // load PSAPI routines + // + if( false == LoadPSAPIRoutines() ) + return false; + + // + // enumerate processes names + // + if( FALSE == EnumProcesses( dPID, dSize, &dPIDSize ) ) + { + FreePSAPIRoutines(); + + return false; + } + + // + // walk trough and compare see if the process is running + // + for( int k( dPIDSize / sizeof( DWORD ) ); k >= 0; k-- ) + { + memset( szCurrentProcessName, 0, 1024*sizeof(char) ); + + if( NULL != ( hProcess = OpenProcess( PROCESS_ALL_ACCESS, FALSE, dPID[ k ] ) ) ) + { + if( TRUE == EnumProcessModules( hProcess, phModule, sizeof(HMODULE)*1024, &dPIDSize ) ) + if( GetModuleBaseName( hProcess, phModule[ 0 ], szCurrentProcessName, 1024 ) > 0 ) + { + strlwr( szCurrentProcessName ); + + if( NULL != strstr( szCurrentProcessName, szProcessName ) ) + { + FreePSAPIRoutines(); + CloseHandle( hProcess ); + + return true; + } + } + + CloseHandle( hProcess ); + } + } + + // + // free PSAPI routines + // + FreePSAPIRoutines(); + + return false; +} + + + + + +//------------------------------------------------------------------------------------------- +// kills a process by name +// return value: true - process was found +// false - process not found +bool KillProc( char *szProcess ) +{ + char szProcessName[ 1024 ]; + char szCurrentProcessName[ 1024 ]; + DWORD dPID[ 1024 ]; + DWORD dPIDSize( 1024 ); + DWORD dSize( 1024 ); + HANDLE hProcess; + HMODULE phModule[ 1024 ]; + + + // + // make the name lower case + // + memset( szProcessName, 0, 1024*sizeof(char) ); + sprintf( szProcessName, "%s", szProcess ); + strlwr( szProcessName ); + + // + // load PSAPI routines + // + if( false == LoadPSAPIRoutines() ) + return false; + + // + // enumerate processes names + // + if( FALSE == EnumProcesses( dPID, dSize, &dPIDSize ) ) + { + FreePSAPIRoutines(); + + return false; + } + + // + // walk trough and compare see if the process is running + // + for( int k( dPIDSize / sizeof( DWORD ) ); k >= 0; k-- ) + { + memset( szCurrentProcessName, 0, 1024*sizeof(char) ); + + if( NULL != ( hProcess = OpenProcess( PROCESS_ALL_ACCESS, FALSE, dPID[ k ] ) ) ) + { + if( TRUE == EnumProcessModules( hProcess, phModule, sizeof(HMODULE)*1024, &dPIDSize ) ) + if( GetModuleBaseName( hProcess, phModule[ 0 ], szCurrentProcessName, 1024 ) > 0 ) + { + strlwr( szCurrentProcessName ); + + if( NULL != strstr( szCurrentProcessName, szProcessName ) ) + { + FreePSAPIRoutines(); + + // + // kill process + // + if( false == TerminateProcess( hProcess, 0 ) ) + { + CloseHandle( hProcess ); + + return true; + } + + // + // refresh systray + // + UpdateWindow( FindWindow( NULL, "Shell_TrayWnd" ) ); + + // + // refresh desktop window + // + UpdateWindow( GetDesktopWindow() ); + + CloseHandle( hProcess ); + + return true; + } + } + + CloseHandle( hProcess ); + } + } + + // + // free PSAPI routines + // + FreePSAPIRoutines(); + + return false; +} + + + + + +//------------------------------------------------------------------------------------------- +bool FindDev( char *szDriverName ) +{ + char szDeviceName[ 1024 ]; + char szCurrentDeviceName[ 1024 ]; + LPVOID lpDevices[ 1024 ]; + DWORD dDevicesSize( 1024 ); + DWORD dSize( 1024 ); + TCHAR tszCurrentDeviceName[ 1024 ]; + DWORD dNameSize( 1024 ); + + + // + // make the name lower case + // + memset( szDeviceName, 0, 1024*sizeof(char) ); + sprintf( szDeviceName, "%s", strlwr( szDriverName ) ); + + // + // load PSAPI routines + // + if( false == LoadPSAPIRoutines() ) + return false; + + // + // enumerate devices + // + if( FALSE == EnumDeviceDrivers( lpDevices, dSize, &dDevicesSize ) ) + { + FreePSAPIRoutines(); + + return false; + } + + // + // walk trough and compare see if the device driver exists + // + for( int k( dDevicesSize / sizeof( LPVOID ) ); k >= 0; k-- ) + { + memset( szCurrentDeviceName, 0, 1024*sizeof(char) ); + memset( tszCurrentDeviceName, 0, 1024*sizeof(TCHAR) ); + + if( 0 != GetDeviceDriverBaseName( lpDevices[ k ], tszCurrentDeviceName, dNameSize ) ) + { + sprintf( szCurrentDeviceName, "%S", tszCurrentDeviceName ); + + if( 0 == strcmp( strlwr( szCurrentDeviceName ), szDeviceName ) ) + { + FreePSAPIRoutines(); + + return true; + } + } + } + + // + // free PSAPI routines + // + FreePSAPIRoutines(); + + return false; +} + + + + + +//------------------------------------------------------------------------------------------- +extern "C" __declspec(dllexport) void FindProcess( HWND hwndParent, + int string_size, + char *variables, + stack_t **stacktop ) +{ + char szParameter[ 1024 ]; + + + g_hwndParent = hwndParent; + + EXDLL_INIT(); + { + popstring( szParameter ); + + if( true == FindProc( szParameter ) ) + wsprintf( szParameter, "1" ); + else + wsprintf( szParameter, "0" ); + + setuservariable( INST_R0, szParameter ); + } +} + + + + + +//------------------------------------------------------------------------------------------- +extern "C" __declspec(dllexport) void KillProcess( HWND hwndParent, + int string_size, + char *variables, + stack_t **stacktop ) +{ + char szParameter[ 1024 ]; + + + g_hwndParent = hwndParent; + + EXDLL_INIT(); + { + popstring( szParameter ); + + if( true == KillProc( szParameter ) ) + wsprintf( szParameter, "1" ); + else + wsprintf( szParameter, "0" ); + + setuservariable( INST_R0, szParameter ); + } +} + + + + + +//------------------------------------------------------------------------------------------- +extern "C" __declspec(dllexport) void FindDevice( HWND hwndParent, + int string_size, + char *variables, + stack_t **stacktop ) +{ + char szParameter[ 1024 ]; + + + g_hwndParent = hwndParent; + + EXDLL_INIT(); + { + popstring( szParameter ); + + if( true == FindDev( szParameter ) ) + wsprintf( szParameter, "1" ); + else + wsprintf( szParameter, "0" ); + + setuservariable( INST_R0, szParameter ); + } +} diff --git a/admin/win/nsi/nsis_processes/src/processes.h b/admin/win/nsi/nsis_processes/src/processes.h new file mode 100755 index 000000000..9bd069101 --- /dev/null +++ b/admin/win/nsi/nsis_processes/src/processes.h @@ -0,0 +1,49 @@ +#pragma once + + + + + +//------------------------------------------------------------------------------------------- +// PSAPI function pointers +typedef BOOL (WINAPI *lpfEnumProcesses) ( DWORD *, DWORD, DWORD * ); +typedef BOOL (WINAPI *lpfEnumProcessModules) ( HANDLE, HMODULE *, DWORD, LPDWORD ); +typedef DWORD (WINAPI *lpfGetModuleBaseName) ( HANDLE, HMODULE, LPTSTR, DWORD ); +typedef BOOL (WINAPI *lpfEnumDeviceDrivers) ( LPVOID *, DWORD, LPDWORD ); +typedef BOOL (WINAPI *lpfGetDeviceDriverBaseName)( LPVOID, LPTSTR, DWORD ); + + + + + + +//------------------------------------------------------------------------------------------- +// Internal use routines +bool LoadPSAPIRoutines( void ); +bool FreePSAPIRoutines( void ); + +bool FindProc( char *szProcess ); +bool KillProc( char *szProcess ); + +bool FindDev( char *szDriverName ); + + + + + +//------------------------------------------------------------------------------------------- +// Exported routines +extern "C" __declspec(dllexport) void FindProcess( HWND hwndParent, + int string_size, + char *variables, + stack_t **stacktop ); + +extern "C" __declspec(dllexport) void KillProcess( HWND hwndParent, + int string_size, + char *variables, + stack_t **stacktop ); + +extern "C" __declspec(dllexport) void FindDevice( HWND hwndParent, + int string_size, + char *variables, + stack_t **stacktop ); diff --git a/admin/win/nsi/nsis_processes/src/processes.ncb b/admin/win/nsi/nsis_processes/src/processes.ncb new file mode 100755 index 0000000000000000000000000000000000000000..c1a5f281fe57fcc5570c40953586a2799befc159 GIT binary patch literal 44032 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 literal 0 HcmV?d00001 diff --git a/admin/win/nsi/nsis_processes/src/processes.rc b/admin/win/nsi/nsis_processes/src/processes.rc new file mode 100755 index 000000000..c6e62a3c8 --- /dev/null +++ b/admin/win/nsi/nsis_processes/src/processes.rc @@ -0,0 +1,103 @@ +// Microsoft Visual C++ generated resource script. +// +#include "resource.h" + +#define APSTUDIO_READONLY_SYMBOLS +///////////////////////////////////////////////////////////////////////////// +// +// Generated from the TEXTINCLUDE 2 resource. +// +#include "afxres.h" + +///////////////////////////////////////////////////////////////////////////// +#undef APSTUDIO_READONLY_SYMBOLS + +///////////////////////////////////////////////////////////////////////////// +// English (U.S.) resources + +#if !defined(AFX_RESOURCE_DLL) || defined(AFX_TARG_ENU) +#ifdef _WIN32 +LANGUAGE LANG_ENGLISH, SUBLANG_ENGLISH_US +#pragma code_page(1252) +#endif //_WIN32 + +#ifdef APSTUDIO_INVOKED +///////////////////////////////////////////////////////////////////////////// +// +// TEXTINCLUDE +// + +1 TEXTINCLUDE +BEGIN + "resource.h\0" +END + +2 TEXTINCLUDE +BEGIN + "#include ""afxres.h""\r\n" + "\0" +END + +3 TEXTINCLUDE +BEGIN + "\r\n" + "\0" +END + +#endif // APSTUDIO_INVOKED + + +///////////////////////////////////////////////////////////////////////////// +// +// Version +// + +VS_VERSION_INFO VERSIONINFO + FILEVERSION 1,0,0,1 + PRODUCTVERSION 1,0,0,1 + FILEFLAGSMASK 0x17L +#ifdef _DEBUG + FILEFLAGS 0x1L +#else + FILEFLAGS 0x0L +#endif + FILEOS 0x4L + FILETYPE 0x2L + FILESUBTYPE 0x0L +BEGIN + BLOCK "StringFileInfo" + BEGIN + BLOCK "040904b0" + BEGIN + VALUE "Comments", "NSIS Plug-in for Windows process management. Only WinNT, Win2K, WinXP and Win2003 Server supported." + VALUE "CompanyName", "Andrei Ciubotaru [Hardwired]" + VALUE "FileDescription", "Windows Processes Management" + VALUE "FileVersion", "1, 0, 0, 1" + VALUE "InternalName", "Processes" + VALUE "LegalCopyright", "Copyright (c) 2004 Hardwired. No rights reserved." + VALUE "OriginalFilename", "Processes.dll" + VALUE "ProductName", "Processes" + VALUE "ProductVersion", "1, 0, 0, 1" + END + END + BLOCK "VarFileInfo" + BEGIN + VALUE "Translation", 0x409, 1200 + END +END + +#endif // English (U.S.) resources +///////////////////////////////////////////////////////////////////////////// + + + +#ifndef APSTUDIO_INVOKED +///////////////////////////////////////////////////////////////////////////// +// +// Generated from the TEXTINCLUDE 3 resource. +// + + +///////////////////////////////////////////////////////////////////////////// +#endif // not APSTUDIO_INVOKED + diff --git a/admin/win/nsi/nsis_processes/src/processes.sln b/admin/win/nsi/nsis_processes/src/processes.sln new file mode 100755 index 000000000..73fc989e2 --- /dev/null +++ b/admin/win/nsi/nsis_processes/src/processes.sln @@ -0,0 +1,21 @@ +Microsoft Visual Studio Solution File, Format Version 8.00 +Project("{8BC9CEB8-8B4A-11D0-8D11-00A0C91BC942}") = "processes", "processes.vcproj", "{3438467F-A719-46DC-93E5-137A8B691727}" + ProjectSection(ProjectDependencies) = postProject + EndProjectSection +EndProject +Global + GlobalSection(SolutionConfiguration) = preSolution + Debug = Debug + Release = Release + EndGlobalSection + GlobalSection(ProjectConfiguration) = postSolution + {3438467F-A719-46DC-93E5-137A8B691727}.Debug.ActiveCfg = Debug|Win32 + {3438467F-A719-46DC-93E5-137A8B691727}.Debug.Build.0 = Debug|Win32 + {3438467F-A719-46DC-93E5-137A8B691727}.Release.ActiveCfg = Release|Win32 + {3438467F-A719-46DC-93E5-137A8B691727}.Release.Build.0 = Release|Win32 + EndGlobalSection + GlobalSection(ExtensibilityGlobals) = postSolution + EndGlobalSection + GlobalSection(ExtensibilityAddIns) = postSolution + EndGlobalSection +EndGlobal diff --git a/admin/win/nsi/nsis_processes/src/processes.txt b/admin/win/nsi/nsis_processes/src/processes.txt new file mode 100755 index 000000000..51d11902a --- /dev/null +++ b/admin/win/nsi/nsis_processes/src/processes.txt @@ -0,0 +1,122 @@ +---------------------------------------------------------------- +---------------------------------------------------------------- +Processes (Processes.dll) +Version: 1.0.0.1 +Release: 12.december.2004 +Description:Nullsoft Installer (NSIS) plug-in for managing?! + Windows processes. + +Copyright: © 2004 Hardwired. No rights reserved. + There is no restriction and no guaranty for using + this software. + +Author: Andrei Ciubotaru [Hardwired] + Lead Developer ICode&Ideas SRL (http://www.icode.ro) + hardwiredteks@gmail.com, hardwired@icode.ro + +---------------------------------------------------------------- +---------------------------------------------------------------- +INTRODUCTION + + The Need For Plug-in - I need it for the one of my installers. + + Briefly: Use it when you need to find\kill a process when +installing\uninstalling some application. Also, use it when you +need to test the presence of a device driver. + + +SUPPORT + + Supported platforms are: WinNT,Win2K,WinXP and Win2003 Server. + + +DESCRIPTION + + Processes::FindProcess + + Searches the currently running processes for the given + process name. + + return: 1 - the process was found + 0 - the process was not found + + Processes::KillProcess + + Searches the currently running processes for the given + process name. If the process is found then the it gets + killed. + + return: 1 - the process was found and killed + 0 - the process was not found or the process + cannot be killed (insuficient rights) + + Processes::FindDevice + + Searches the installed devices drivers for the given + device base name. + (important: I said BASE NAME not FILENAME) + + return: 1 - the device driver was found + 0 - the device driver was not found + + +USAGE + + First of all, does not matter where you use it. Ofcourse, the +routines must be called inside of a Section/Function scope. + + Processes::FindProcess "process_name.exe" + Pop $R0 + + StrCmp $R0 "1" make_my_day noooooo + + make_my_day: + ... + + noooooo: + ... + + + Processes::KillProcess "process_name.exe" + Pop $R0 + + StrCmp $R0 "1" dead_meat why_wont_you_die + + dead_meat: + ... + + why_wont_you_die: + ... + + + Processes::FindDevice "device_base_name" + Pop $R0 + + StrCmp $R0 "1" blabla more_blabla + + blabla: + ... + + more_blabla: + ... + + +THANKS + + Sunil Kamath for inspiring me. I wanted to use its FindProcDLL +but my requirements made it imposible. + + Nullsoft for creating this very powerfull installer. One big, +free and full-featured (hmmm... and guiless for the moment) mean +install machine!:) + + ME for being such a great coder... + ... HAHAHAHAHAHAHA! + +ONE MORE THING + + If you use the plugin or it's source-code, I would apreciate +if my name is mentioned. + +---------------------------------------------------------------- +---------------------------------------------------------------- diff --git a/admin/win/nsi/nsis_processes/src/processes.vcproj b/admin/win/nsi/nsis_processes/src/processes.vcproj new file mode 100755 index 000000000..245cbc99f --- /dev/null +++ b/admin/win/nsi/nsis_processes/src/processes.vcproj @@ -0,0 +1,222 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/admin/win/nsi/nsis_processes/src/resource.h b/admin/win/nsi/nsis_processes/src/resource.h new file mode 100755 index 000000000..506377e21 --- /dev/null +++ b/admin/win/nsi/nsis_processes/src/resource.h @@ -0,0 +1,15 @@ +//{{NO_DEPENDENCIES}} +// Microsoft Visual C++ generated include file. +// Used by processes.rc +// + +// Next default values for new objects +// +#ifdef APSTUDIO_INVOKED +#ifndef APSTUDIO_READONLY_SYMBOLS +#define _APS_NEXT_RESOURCE_VALUE 101 +#define _APS_NEXT_COMMAND_VALUE 40001 +#define _APS_NEXT_CONTROL_VALUE 1001 +#define _APS_NEXT_SYMED_VALUE 101 +#endif +#endif diff --git a/admin/win/nsi/page_header.bmp b/admin/win/nsi/page_header.bmp old mode 100755 new mode 100644 diff --git a/admin/win/nsi/revision.txt b/admin/win/nsi/revision.txt old mode 100644 new mode 100755 index 56749c830..c4fbb1cfa --- a/admin/win/nsi/revision.txt +++ b/admin/win/nsi/revision.txt @@ -1 +1 @@ -96 \ No newline at end of file +97 \ No newline at end of file diff --git a/admin/win/nsi/tomahawk.ini b/admin/win/nsi/tomahawk.ini old mode 100755 new mode 100644 diff --git a/admin/win/nsi/tomahawk.nsi b/admin/win/nsi/tomahawk.nsi old mode 100755 new mode 100644 index f6e49cfd3..c2d2a9bd1 --- a/admin/win/nsi/tomahawk.nsi +++ b/admin/win/nsi/tomahawk.nsi @@ -159,6 +159,45 @@ Function LaunchTomahawkAsUser FunctionEnd !endif +############################################################################## +# # +# PROCESS HANDLING FUNCTIONS AND MACROS # +# # +############################################################################## + +!macro CheckForProcess processName gotoWhenFound gotoWhenNotFound + Processes::FindProcess ${processName} + StrCmp $R0 "0" ${gotoWhenNotFound} ${gotoWhenFound} +!macroend + +!macro ConfirmEndProcess processName + MessageBox MB_YESNO|MB_ICONEXCLAMATION \ + "Found ${processName} process(s) which need to be stopped.$\nDo you want the installer to stop these for you?" \ + IDYES process_${processName}_kill IDNO process_${processName}_ended + process_${processName}_kill: + DetailPrint "Killing ${processName} processes." + Processes::KillProcess ${processName} + Sleep 1500 + StrCmp $R0 "1" process_${processName}_ended + DetailPrint "Process to kill not found!" + process_${processName}_ended: +!macroend + +!macro CheckAndConfirmEndProcess processName + !insertmacro CheckForProcess ${processName} 0 no_process_${processName}_to_end + !insertmacro ConfirmEndProcess ${processName} + no_process_${processName}_to_end: +!macroend + +!macro EnsureTomahawkShutdown un + Function ${un}EnsureTomahawkShutdown + !insertmacro CheckAndConfirmEndProcess "tomahawk.exe" + FunctionEnd +!macroend + +!insertmacro EnsureTomahawkShutdown "" +!insertmacro EnsureTomahawkShutdown "un." + ############################################################################## # # # RE-INSTALLER FUNCTIONS # @@ -561,6 +600,9 @@ Function .onInit StrCmp $R0 "" SkipSetInstDir StrCpy $INSTDIR $R0 SkipSetInstDir: + + ;Shutdown Tomahawk in case Add/Remove re-installer option used. + Call EnsureTomahawkShutdown FunctionEnd Function .onInstSuccess diff --git a/admin/win/nsi/welcome.bmp b/admin/win/nsi/welcome.bmp old mode 100755 new mode 100644 diff --git a/admin/win/page_header.bmp b/admin/win/page_header.bmp deleted file mode 100755 index c50025a22b810a7e755768c16e73ce7f1ec97ea1..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 25818 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 KfYm_fH1L1OIOjJ2 diff --git a/admin/win/welcome.bmp b/admin/win/welcome.bmp deleted file mode 100755 index 580504f50354f345e789f921266ebcdd518037ff..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 154542 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 From 543c3125accbbedbf9adf5897d27d7e9c90a40ff Mon Sep 17 00:00:00 2001 From: Christian Muehlhaeuser Date: Thu, 31 Mar 2011 04:20:57 +0200 Subject: [PATCH 218/329] * Cherry-picked latest tomahawk.nsi changes from master. --- admin/win/README.txt | 0 admin/win/nsi/RELEASE_NOTES.txt | 0 admin/win/nsi/installer.ico | Bin .../win/nsi/nsis_processes/bin/Processes.dll | Bin 0 -> 36352 bytes admin/win/nsi/nsis_processes/license.rtf | 35 ++ admin/win/nsi/nsis_processes/readme.txt | 122 ++++++ admin/win/nsi/nsis_processes/src/StdAfx.cpp | 8 + admin/win/nsi/nsis_processes/src/StdAfx.h | 34 ++ admin/win/nsi/nsis_processes/src/exdll.c | 37 ++ admin/win/nsi/nsis_processes/src/exdll.h | 136 ++++++ .../win/nsi/nsis_processes/src/processes.cpp | 411 ++++++++++++++++++ admin/win/nsi/nsis_processes/src/processes.h | 49 +++ .../win/nsi/nsis_processes/src/processes.ncb | Bin 0 -> 44032 bytes admin/win/nsi/nsis_processes/src/processes.rc | 103 +++++ .../win/nsi/nsis_processes/src/processes.sln | 21 + .../win/nsi/nsis_processes/src/processes.txt | 122 ++++++ .../nsi/nsis_processes/src/processes.vcproj | 222 ++++++++++ admin/win/nsi/nsis_processes/src/resource.h | 15 + admin/win/nsi/page_header.bmp | Bin admin/win/nsi/revision.txt | 2 +- admin/win/nsi/tomahawk.ini | 0 admin/win/nsi/tomahawk.nsi | 49 +++ admin/win/nsi/welcome.bmp | Bin 23 files changed, 1365 insertions(+), 1 deletion(-) delete mode 100755 admin/win/README.txt mode change 100755 => 100644 admin/win/nsi/RELEASE_NOTES.txt mode change 100755 => 100644 admin/win/nsi/installer.ico create mode 100755 admin/win/nsi/nsis_processes/bin/Processes.dll create mode 100755 admin/win/nsi/nsis_processes/license.rtf create mode 100755 admin/win/nsi/nsis_processes/readme.txt create mode 100755 admin/win/nsi/nsis_processes/src/StdAfx.cpp create mode 100755 admin/win/nsi/nsis_processes/src/StdAfx.h create mode 100755 admin/win/nsi/nsis_processes/src/exdll.c create mode 100755 admin/win/nsi/nsis_processes/src/exdll.h create mode 100755 admin/win/nsi/nsis_processes/src/processes.cpp create mode 100755 admin/win/nsi/nsis_processes/src/processes.h create mode 100755 admin/win/nsi/nsis_processes/src/processes.ncb create mode 100755 admin/win/nsi/nsis_processes/src/processes.rc create mode 100755 admin/win/nsi/nsis_processes/src/processes.sln create mode 100755 admin/win/nsi/nsis_processes/src/processes.txt create mode 100755 admin/win/nsi/nsis_processes/src/processes.vcproj create mode 100755 admin/win/nsi/nsis_processes/src/resource.h mode change 100755 => 100644 admin/win/nsi/page_header.bmp mode change 100644 => 100755 admin/win/nsi/revision.txt mode change 100755 => 100644 admin/win/nsi/tomahawk.ini mode change 100755 => 100644 admin/win/nsi/tomahawk.nsi mode change 100755 => 100644 admin/win/nsi/welcome.bmp diff --git a/admin/win/README.txt b/admin/win/README.txt deleted file mode 100755 index e69de29bb..000000000 diff --git a/admin/win/nsi/RELEASE_NOTES.txt b/admin/win/nsi/RELEASE_NOTES.txt old mode 100755 new mode 100644 diff --git a/admin/win/nsi/installer.ico b/admin/win/nsi/installer.ico old mode 100755 new mode 100644 diff --git a/admin/win/nsi/nsis_processes/bin/Processes.dll b/admin/win/nsi/nsis_processes/bin/Processes.dll new file mode 100755 index 0000000000000000000000000000000000000000..e532bf8bb55a5b7cadfe6d6e05058ae3f446739a GIT binary patch literal 36352 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≦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 literal 0 HcmV?d00001 diff --git a/admin/win/nsi/nsis_processes/license.rtf b/admin/win/nsi/nsis_processes/license.rtf new file mode 100755 index 000000000..2ce5a58c9 --- /dev/null +++ b/admin/win/nsi/nsis_processes/license.rtf @@ -0,0 +1,35 @@ +{\rtf1\ansi\ansicpg1252\uc1\deff0\stshfdbch0\stshfloch0\stshfhich0\stshfbi0\deflang1033\deflangfe1033{\fonttbl{\f0\froman\fcharset0\fprq2{\*\panose 02020603050405020304}Times New Roman;}{\f39\fswiss\fcharset0\fprq2{\*\panose 020b0604030504040204}Verdana;} +{\f172\froman\fcharset238\fprq2 Times New Roman CE;}{\f173\froman\fcharset204\fprq2 Times New Roman Cyr;}{\f175\froman\fcharset161\fprq2 Times New Roman Greek;}{\f176\froman\fcharset162\fprq2 Times New Roman Tur;} +{\f177\froman\fcharset177\fprq2 Times New Roman (Hebrew);}{\f178\froman\fcharset178\fprq2 Times New Roman (Arabic);}{\f179\froman\fcharset186\fprq2 Times New Roman Baltic;}{\f180\froman\fcharset163\fprq2 Times New Roman (Vietnamese);} +{\f562\fswiss\fcharset238\fprq2 Verdana CE;}{\f563\fswiss\fcharset204\fprq2 Verdana Cyr;}{\f565\fswiss\fcharset161\fprq2 Verdana Greek;}{\f566\fswiss\fcharset162\fprq2 Verdana Tur;}{\f569\fswiss\fcharset186\fprq2 Verdana Baltic;} +{\f570\fswiss\fcharset163\fprq2 Verdana (Vietnamese);}}{\colortbl;\red0\green0\blue0;\red0\green0\blue255;\red0\green255\blue255;\red0\green255\blue0;\red255\green0\blue255;\red255\green0\blue0;\red255\green255\blue0;\red255\green255\blue255; +\red0\green0\blue128;\red0\green128\blue128;\red0\green128\blue0;\red128\green0\blue128;\red128\green0\blue0;\red128\green128\blue0;\red128\green128\blue128;\red192\green192\blue192;}{\stylesheet{ +\ql \li0\ri0\widctlpar\aspalpha\aspnum\faauto\adjustright\rin0\lin0\itap0 \fs24\lang1033\langfe1033\cgrid\langnp1033\langfenp1033 \snext0 Normal;}{\*\cs10 \additive \ssemihidden Default Paragraph Font;}{\* +\ts11\tsrowd\trftsWidthB3\trpaddl108\trpaddr108\trpaddfl3\trpaddft3\trpaddfb3\trpaddfr3\trcbpat1\trcfpat1\tscellwidthfts0\tsvertalt\tsbrdrt\tsbrdrl\tsbrdrb\tsbrdrr\tsbrdrdgl\tsbrdrdgr\tsbrdrh\tsbrdrv +\ql \li0\ri0\widctlpar\aspalpha\aspnum\faauto\adjustright\rin0\lin0\itap0 \fs20\lang1024\langfe1024\cgrid\langnp1024\langfenp1024 \snext11 \ssemihidden Normal Table;}{\*\cs15 \additive \ul\cf2 \sbasedon10 \styrsid7485074 Hyperlink;}} +{\*\latentstyles\lsdstimax156\lsdlockeddef0}{\*\rsidtbl \rsid6712196\rsid7485074\rsid11352300\rsid15940516}{\*\generator Microsoft Word 11.0.5604;}{\info{\title Processes v1}{\author Hardwired}{\operator Hardwired}{\creatim\yr2004\mo12\dy12\hr23\min42} +{\revtim\yr2004\mo12\dy12\hr23\min51}{\version2}{\edmins9}{\nofpages1}{\nofwords80}{\nofchars458}{\nofcharsws537}{\vern24689}}\widowctrl\ftnbj\aenddoc\noxlattoyen\expshrtn\noultrlspc\dntblnsbdb\nospaceforul\formshade\horzdoc\dgmargin\dghspace180 +\dgvspace180\dghorigin1800\dgvorigin1440\dghshow1\dgvshow1 +\jexpand\viewkind1\viewscale100\pgbrdrhead\pgbrdrfoot\splytwnine\ftnlytwnine\htmautsp\nolnhtadjtbl\useltbaln\alntblind\lytcalctblwd\lyttblrtgr\lnbrkrule\nobrkwrptbl\snaptogridincell\allowfieldendsel\wrppunct +\asianbrkrule\rsidroot7485074\newtblstyruls\nogrowautofit \fet0\sectd \linex0\endnhere\sectlinegrid360\sectdefaultcl\sftnbj {\*\pnseclvl1\pnucrm\pnstart1\pnindent720\pnhang {\pntxta .}}{\*\pnseclvl2\pnucltr\pnstart1\pnindent720\pnhang {\pntxta .}} +{\*\pnseclvl3\pndec\pnstart1\pnindent720\pnhang {\pntxta .}}{\*\pnseclvl4\pnlcltr\pnstart1\pnindent720\pnhang {\pntxta )}}{\*\pnseclvl5\pndec\pnstart1\pnindent720\pnhang {\pntxtb (}{\pntxta )}}{\*\pnseclvl6\pnlcltr\pnstart1\pnindent720\pnhang {\pntxtb (} +{\pntxta )}}{\*\pnseclvl7\pnlcrm\pnstart1\pnindent720\pnhang {\pntxtb (}{\pntxta )}}{\*\pnseclvl8\pnlcltr\pnstart1\pnindent720\pnhang {\pntxtb (}{\pntxta )}}{\*\pnseclvl9\pnlcrm\pnstart1\pnindent720\pnhang {\pntxtb (}{\pntxta )}}\pard\plain +\qj \li0\ri0\widctlpar\aspalpha\aspnum\faauto\adjustright\rin0\lin0\itap0\pararsid7485074 \fs24\lang1033\langfe1033\cgrid\langnp1033\langfenp1033 {\b\f39\insrsid7485074\charrsid7485074 Processes v1.0}{\f39\insrsid7485074\charrsid7485074 .0.1 +\par }{\f39\fs20\insrsid7485074 +\par }\pard \qj \li0\ri0\widctlpar\aspalpha\aspnum\faauto\adjustright\rin0\lin0\itap0\pararsid15940516 {\f39\fs20\insrsid15940516 This software binaries and source-code are free for any kind of use, including commercial use. }{ +\f39\fs20\insrsid7485074\charrsid7485074 There is no restriction and no guaranty for using}{\f39\fs20\insrsid7485074\charrsid7485074 t}{\f39\fs20\insrsid7485074\charrsid7485074 his software}{\f39\fs20\insrsid7485074\charrsid7485074 and/or it +s source-code. }{\f39\fs20\insrsid15940516 +\par I}{\f39\fs20\insrsid7485074\charrsid7485074 f you use the plug}{\f39\fs20\insrsid7485074\charrsid7485074 -}{\f39\fs20\insrsid7485074\charrsid7485074 in }{\f39\fs20\insrsid7485074\charrsid7485074 and/}{\f39\fs20\insrsid7485074\charrsid7485074 or it}{ +\f39\fs20\insrsid7485074\charrsid7485074 s}{\f39\fs20\insrsid7485074\charrsid7485074 source-code, I would }{\f39\fs20\insrsid7485074\charrsid7485074 appreciate }{\f39\fs20\insrsid7485074\charrsid7485074 if my name is mentioned.}{ +\f39\fs20\insrsid7485074\charrsid7485074 +\par }\pard \qj \li0\ri0\widctlpar\aspalpha\aspnum\faauto\adjustright\rin0\lin0\itap0\pararsid7485074 {\f39\fs20\insrsid7485074\charrsid7485074 +\par }{\b\f39\fs20\insrsid7485074\charrsid7485074 Andrei Ciubotaru [Hardwired] +\par }{\f39\fs20\insrsid7485074\charrsid7485074 Lead Developer ICode&Ideas SRL (}{\field\flddirty{\*\fldinst {\f39\fs20\insrsid7485074\charrsid7485074 HYPERLINK "http://www.icode.ro/" }{\f39\fs20\insrsid7485074\charrsid7485074 {\*\datafield +00d0c9ea79f9bace118c8200aa004ba90b02000000170000001500000068007400740070003a002f002f007700770077002e00690063006f00640065002e0072006f002f000000e0c9ea79f9bace118c8200aa004ba90b2a00000068007400740070003a002f002f007700770077002e00690063006f00640065002e007200 +6f002f000000}}}{\fldrslt {\cs15\f39\fs20\ul\cf2\insrsid7485074\charrsid7485074 http://www.icode.ro/}}}{\f39\fs20\insrsid7485074\charrsid7485074 ) +\par }{\field{\*\fldinst {\f39\fs20\insrsid7485074 HYPERLINK "hardwiredteks@gmail.com" }{\f39\fs20\insrsid15940516\charrsid7485074 {\*\datafield +00d0c9ea79f9bace118c8200aa004ba90b02000000010000000303000000000000c00000000000004600001800000068617264776972656474656b7340676d61696c2e636f6d00ffffadde000000000000000000000000000000000000000000000000}}}{\fldrslt { +\cs15\f39\fs20\ul\cf2\insrsid7485074\charrsid7485074 hardwiredteks@gmail.com}}}{\f39\fs20\insrsid7485074\charrsid7485074 , }{\field{\*\fldinst {\f39\fs20\insrsid7485074 HYPERLINK "hardwired@icode.ro" }{\f39\fs20\insrsid15940516\charrsid7485074 +{\*\datafield 00d0c9ea79f9bace118c8200aa004ba90b02000000010000000303000000000000c0000000000000460000130000006861726477697265644069636f64652e726f00ffffadde000000000000000000000000000000000000000000000000}}}{\fldrslt { +\cs15\f39\fs20\ul\cf2\insrsid7485074\charrsid7485074 hardwired@icode.ro}}}{\f39\fs20\insrsid7485074\charrsid7485074 +\par }} \ No newline at end of file diff --git a/admin/win/nsi/nsis_processes/readme.txt b/admin/win/nsi/nsis_processes/readme.txt new file mode 100755 index 000000000..8529c39ad --- /dev/null +++ b/admin/win/nsi/nsis_processes/readme.txt @@ -0,0 +1,122 @@ +---------------------------------------------------------------- +---------------------------------------------------------------- +Processes (Processes.dll) +Version: 1.0.1.0 +Release: 24.february.2005 +Description: Nullsoft Installer (NSIS) plug-in for managing?! + Windows processes. + +Copyright: © 2004-2005 Hardwired. No rights reserved. + There is no restriction and no guaranty for using + this software. + +Author: Andrei Ciubotaru [Hardwired] + Lead Developer ICode&Ideas SRL (http://www.icode.ro/) + hardwiredteks@gmail.com, hardwired@icode.ro + +---------------------------------------------------------------- +---------------------------------------------------------------- +INTRODUCTION + + The Need For Plug-in - I need it for the one of my installers. + + Briefly: Use it when you need to find\kill a process when +installing\uninstalling some application. Also, use it when you +need to test the presence of a device driver. + + +SUPPORT + + Supported platforms are: WinNT,Win2K,WinXP and Win2003 Server. + + +DESCRIPTION + + Processes::FindProcess ;without ".exe" + + Searches the currently running processes for the given + process name. + + return: 1 - the process was found + 0 - the process was not found + + Processes::KillProcess ; without ".exe" + + Searches the currently running processes for the given + process name. If the process is found then the it gets + killed. + + return: 1 - the process was found and killed + 0 - the process was not found or the process + cannot be killed (insuficient rights) + + Processes::FindDevice + + Searches the installed devices drivers for the given + device base name. + (important: I said BASE NAME not FILENAME) + + return: 1 - the device driver was found + 0 - the device driver was not found + + +USAGE + + First of all, does not matter where you use it. Ofcourse, the +routines must be called inside of a Section/Function scope. + + Processes::FindProcess "process_name" + Pop $R0 + + StrCmp $R0 "1" make_my_day noooooo + + make_my_day: + ... + + noooooo: + ... + + + Processes::KillProcess "process_name" + Pop $R0 + + StrCmp $R0 "1" dead_meat why_wont_you_die + + dead_meat: + ... + + why_wont_you_die: + ... + + + Processes::FindDevice "device_base_name" + Pop $R0 + + StrCmp $R0 "1" blabla more_blabla + + blabla: + ... + + more_blabla: + ... + + +THANKS + + Sunil Kamath for inspiring me. I wanted to use its FindProcDLL +but my requirements made it imposible. + + Nullsoft for creating this very powerfull installer. One big, +free and full-featured (hmmm... and guiless for the moment) mean +install machine!:) + + ME for being such a great coder... + ... HAHAHAHAHAHAHA! + +ONE MORE THING + + If you use the plugin or it's source-code, I would apreciate +if my name is mentioned. + +---------------------------------------------------------------- +---------------------------------------------------------------- diff --git a/admin/win/nsi/nsis_processes/src/StdAfx.cpp b/admin/win/nsi/nsis_processes/src/StdAfx.cpp new file mode 100755 index 000000000..f38accc8a --- /dev/null +++ b/admin/win/nsi/nsis_processes/src/StdAfx.cpp @@ -0,0 +1,8 @@ +// stdafx.cpp : source file that includes just the standard includes +// KillProcDLL.pch will be the pre-compiled header +// stdafx.obj will contain the pre-compiled type information + +#include "stdafx.h" + +// TODO: reference any additional headers you need in STDAFX.H +// and not in this file diff --git a/admin/win/nsi/nsis_processes/src/StdAfx.h b/admin/win/nsi/nsis_processes/src/StdAfx.h new file mode 100755 index 000000000..dd49f99b9 --- /dev/null +++ b/admin/win/nsi/nsis_processes/src/StdAfx.h @@ -0,0 +1,34 @@ +// stdafx.h : include file for standard system include files, +// or project specific include files that are used frequently, but +// are changed infrequently +// + +#if !defined(AFX_STDAFX_H__780690DC_E128_403D_BC07_780D1B2CC101__INCLUDED_) +#define AFX_STDAFX_H__780690DC_E128_403D_BC07_780D1B2CC101__INCLUDED_ + +#if _MSC_VER > 1000 +#pragma once +#endif // _MSC_VER > 1000 + +#define WIN32_LEAN_AND_MEAN // Exclude rarely-used stuff from Windows headers + +#include + +#include // String management... + +//From exam28.cpp +#include +//#include + +#ifdef BORLANDC + #include + #include +#endif + +//To make it a NSIS Plug-In +#include "exdll.h" + +//{{AFX_INSERT_LOCATION}} +// Microsoft Visual C++ will insert additional declarations immediately before the previous line. + +#endif // !defined(AFX_STDAFX_H__780690DC_E128_403D_BC07_780D1B2CC101__INCLUDED_) diff --git a/admin/win/nsi/nsis_processes/src/exdll.c b/admin/win/nsi/nsis_processes/src/exdll.c new file mode 100755 index 000000000..7092cb840 --- /dev/null +++ b/admin/win/nsi/nsis_processes/src/exdll.c @@ -0,0 +1,37 @@ +#include +#include "exdll.h" + +HINSTANCE g_hInstance; + +HWND g_hwndParent; + +void __declspec(dllexport) myFunction(HWND hwndParent, int string_size, + char *variables, stack_t **stacktop) +{ + g_hwndParent=hwndParent; + + EXDLL_INIT(); + + + // note if you want parameters from the stack, pop them off in order. + // i.e. if you are called via exdll::myFunction file.dat poop.dat + // calling popstring() the first time would give you file.dat, + // and the second time would give you poop.dat. + // you should empty the stack of your parameters, and ONLY your + // parameters. + + // do your stuff here + { + char buf[1024]; + wsprintf(buf,"$0=%s\n",getuservariable(INST_0)); + MessageBox(g_hwndParent,buf,0,MB_OK); + } +} + + + +BOOL WINAPI _DllMainCRTStartup(HANDLE hInst, ULONG ul_reason_for_call, LPVOID lpReserved) +{ + g_hInstance=hInst; + return TRUE; +} diff --git a/admin/win/nsi/nsis_processes/src/exdll.h b/admin/win/nsi/nsis_processes/src/exdll.h new file mode 100755 index 000000000..777d93be5 --- /dev/null +++ b/admin/win/nsi/nsis_processes/src/exdll.h @@ -0,0 +1,136 @@ +#ifndef _EXDLL_H_ +#define _EXDLL_H_ + + + + + +// +// only include this file from one place in your DLL. +// (it is all static, if you use it in two places it will fail) +// +#define EXDLL_INIT() { \ + g_stringsize = string_size; \ + g_stacktop = stacktop; \ + g_variables = variables; } + + + + +// +// For page showing plug-ins +// +#define WM_NOTIFY_OUTER_NEXT (WM_USER+0x8) +#define WM_NOTIFY_CUSTOM_READY (WM_USER+0xd) +#define NOTIFY_BYE_BYE 'x' + +typedef struct _stack_t +{ + struct _stack_t *next; + char text[1]; // this should be the length of string_size +} stack_t; + + +static unsigned int g_stringsize; +static stack_t **g_stacktop; +static char *g_variables; + +enum +{ +INST_0, // $0 +INST_1, // $1 +INST_2, // $2 +INST_3, // $3 +INST_4, // $4 +INST_5, // $5 +INST_6, // $6 +INST_7, // $7 +INST_8, // $8 +INST_9, // $9 +INST_R0, // $R0 +INST_R1, // $R1 +INST_R2, // $R2 +INST_R3, // $R3 +INST_R4, // $R4 +INST_R5, // $R5 +INST_R6, // $R6 +INST_R7, // $R7 +INST_R8, // $R8 +INST_R9, // $R9 +INST_CMDLINE, // $CMDLINE +INST_INSTDIR, // $INSTDIR +INST_OUTDIR, // $OUTDIR +INST_EXEDIR, // $EXEDIR +INST_LANG, // $LANGUAGE +__INST_LAST +}; + + + + + +// +// utility functions (not required but often useful) +// +static int popstring( char *str ) +{ + stack_t *th; + + + if( !g_stacktop || + !*g_stacktop ) + return 1; + + th = (*g_stacktop); + lstrcpy( str, th->text ); + *g_stacktop = th->next; + GlobalFree( (HGLOBAL)th ); + + return 0; +} + + + + +static void pushstring( char *str ) +{ + stack_t *th; + + + if( !g_stacktop ) + return; + + th = (stack_t*)GlobalAlloc( GPTR, sizeof(stack_t) + g_stringsize ); + lstrcpyn( th->text, str, g_stringsize ); + th->next = *g_stacktop; + *g_stacktop = th; +} + + + + + +static char *getuservariable( int varnum ) +{ + if( varnum < 0 || + varnum >= __INST_LAST ) + return NULL; + + return (g_variables + varnum*g_stringsize); +} + + + + + +static void setuservariable( int varnum, char *var ) +{ + if( var != NULL && + varnum >= 0 && + varnum < __INST_LAST ) + lstrcpy( g_variables + varnum*g_stringsize, var ); +} + + + +#endif//_EXDLL_H_ \ No newline at end of file diff --git a/admin/win/nsi/nsis_processes/src/processes.cpp b/admin/win/nsi/nsis_processes/src/processes.cpp new file mode 100755 index 000000000..c15f8f94a --- /dev/null +++ b/admin/win/nsi/nsis_processes/src/processes.cpp @@ -0,0 +1,411 @@ +#include "stdafx.h" +#include "processes.h" +#include "string.h" + + + + + + +//------------------------------------------------------------------------------------------- +// global variables +lpfEnumProcesses EnumProcesses; +lpfEnumProcessModules EnumProcessModules; +lpfGetModuleBaseName GetModuleBaseName; +lpfEnumDeviceDrivers EnumDeviceDrivers; +lpfGetDeviceDriverBaseName GetDeviceDriverBaseName; + +HINSTANCE g_hInstance; +HWND g_hwndParent; +HINSTANCE g_hInstLib; + + + + + +//------------------------------------------------------------------------------------------- +// main DLL entry +BOOL WINAPI _DllMainCRTStartup( HANDLE hInst, + ULONG ul_reason_for_call, + LPVOID lpReserved ) +{ + g_hInstance = (struct HINSTANCE__ *)hInst; + + return TRUE; +} + + + + + +//------------------------------------------------------------------------------------------- +// loads the psapi routines +bool LoadPSAPIRoutines( void ) +{ + if( NULL == (g_hInstLib = LoadLibraryA( "PSAPI.DLL" )) ) + return false; + + EnumProcesses = (lpfEnumProcesses) GetProcAddress( g_hInstLib, "EnumProcesses" ); + EnumProcessModules = (lpfEnumProcessModules) GetProcAddress( g_hInstLib, "EnumProcessModules" ); + GetModuleBaseName = (lpfGetModuleBaseName) GetProcAddress( g_hInstLib, "GetModuleBaseNameA" ); + EnumDeviceDrivers = (lpfEnumDeviceDrivers) GetProcAddress( g_hInstLib, "EnumDeviceDrivers" ); + GetDeviceDriverBaseName = (lpfGetDeviceDriverBaseName) GetProcAddress( g_hInstLib, "GetDeviceDriverBaseNameA" ); + + if( ( NULL == EnumProcesses ) || + ( NULL == EnumProcessModules ) || + ( NULL == EnumDeviceDrivers ) || + ( NULL == GetModuleBaseName ) || + ( NULL == GetDeviceDriverBaseName ) ) + { + FreeLibrary( g_hInstLib ); + + return false; + } + + return true; +} + + + + + +//------------------------------------------------------------------------------------------- +// free the psapi routines +bool FreePSAPIRoutines( void ) +{ + EnumProcesses = NULL; + EnumProcessModules = NULL; + GetModuleBaseName = NULL; + EnumDeviceDrivers = NULL; + + if( FALSE == FreeLibrary( g_hInstLib ) ) + return false; + + return true; +} + + + + + +//------------------------------------------------------------------------------------------- +// find a process by name +// return value: true - process was found +// false - process not found +bool FindProc( char *szProcess ) +{ + char szProcessName[ 1024 ]; + char szCurrentProcessName[ 1024 ]; + DWORD dPID[ 1024 ]; + DWORD dPIDSize( 1024 ); + DWORD dSize( 1024 ); + HANDLE hProcess; + HMODULE phModule[ 1024 ]; + + + // + // make the name lower case + // + memset( szProcessName, 0, 1024*sizeof(char) ); + sprintf( szProcessName, "%s", szProcess ); + strlwr( szProcessName ); + + // + // load PSAPI routines + // + if( false == LoadPSAPIRoutines() ) + return false; + + // + // enumerate processes names + // + if( FALSE == EnumProcesses( dPID, dSize, &dPIDSize ) ) + { + FreePSAPIRoutines(); + + return false; + } + + // + // walk trough and compare see if the process is running + // + for( int k( dPIDSize / sizeof( DWORD ) ); k >= 0; k-- ) + { + memset( szCurrentProcessName, 0, 1024*sizeof(char) ); + + if( NULL != ( hProcess = OpenProcess( PROCESS_ALL_ACCESS, FALSE, dPID[ k ] ) ) ) + { + if( TRUE == EnumProcessModules( hProcess, phModule, sizeof(HMODULE)*1024, &dPIDSize ) ) + if( GetModuleBaseName( hProcess, phModule[ 0 ], szCurrentProcessName, 1024 ) > 0 ) + { + strlwr( szCurrentProcessName ); + + if( NULL != strstr( szCurrentProcessName, szProcessName ) ) + { + FreePSAPIRoutines(); + CloseHandle( hProcess ); + + return true; + } + } + + CloseHandle( hProcess ); + } + } + + // + // free PSAPI routines + // + FreePSAPIRoutines(); + + return false; +} + + + + + +//------------------------------------------------------------------------------------------- +// kills a process by name +// return value: true - process was found +// false - process not found +bool KillProc( char *szProcess ) +{ + char szProcessName[ 1024 ]; + char szCurrentProcessName[ 1024 ]; + DWORD dPID[ 1024 ]; + DWORD dPIDSize( 1024 ); + DWORD dSize( 1024 ); + HANDLE hProcess; + HMODULE phModule[ 1024 ]; + + + // + // make the name lower case + // + memset( szProcessName, 0, 1024*sizeof(char) ); + sprintf( szProcessName, "%s", szProcess ); + strlwr( szProcessName ); + + // + // load PSAPI routines + // + if( false == LoadPSAPIRoutines() ) + return false; + + // + // enumerate processes names + // + if( FALSE == EnumProcesses( dPID, dSize, &dPIDSize ) ) + { + FreePSAPIRoutines(); + + return false; + } + + // + // walk trough and compare see if the process is running + // + for( int k( dPIDSize / sizeof( DWORD ) ); k >= 0; k-- ) + { + memset( szCurrentProcessName, 0, 1024*sizeof(char) ); + + if( NULL != ( hProcess = OpenProcess( PROCESS_ALL_ACCESS, FALSE, dPID[ k ] ) ) ) + { + if( TRUE == EnumProcessModules( hProcess, phModule, sizeof(HMODULE)*1024, &dPIDSize ) ) + if( GetModuleBaseName( hProcess, phModule[ 0 ], szCurrentProcessName, 1024 ) > 0 ) + { + strlwr( szCurrentProcessName ); + + if( NULL != strstr( szCurrentProcessName, szProcessName ) ) + { + FreePSAPIRoutines(); + + // + // kill process + // + if( false == TerminateProcess( hProcess, 0 ) ) + { + CloseHandle( hProcess ); + + return true; + } + + // + // refresh systray + // + UpdateWindow( FindWindow( NULL, "Shell_TrayWnd" ) ); + + // + // refresh desktop window + // + UpdateWindow( GetDesktopWindow() ); + + CloseHandle( hProcess ); + + return true; + } + } + + CloseHandle( hProcess ); + } + } + + // + // free PSAPI routines + // + FreePSAPIRoutines(); + + return false; +} + + + + + +//------------------------------------------------------------------------------------------- +bool FindDev( char *szDriverName ) +{ + char szDeviceName[ 1024 ]; + char szCurrentDeviceName[ 1024 ]; + LPVOID lpDevices[ 1024 ]; + DWORD dDevicesSize( 1024 ); + DWORD dSize( 1024 ); + TCHAR tszCurrentDeviceName[ 1024 ]; + DWORD dNameSize( 1024 ); + + + // + // make the name lower case + // + memset( szDeviceName, 0, 1024*sizeof(char) ); + sprintf( szDeviceName, "%s", strlwr( szDriverName ) ); + + // + // load PSAPI routines + // + if( false == LoadPSAPIRoutines() ) + return false; + + // + // enumerate devices + // + if( FALSE == EnumDeviceDrivers( lpDevices, dSize, &dDevicesSize ) ) + { + FreePSAPIRoutines(); + + return false; + } + + // + // walk trough and compare see if the device driver exists + // + for( int k( dDevicesSize / sizeof( LPVOID ) ); k >= 0; k-- ) + { + memset( szCurrentDeviceName, 0, 1024*sizeof(char) ); + memset( tszCurrentDeviceName, 0, 1024*sizeof(TCHAR) ); + + if( 0 != GetDeviceDriverBaseName( lpDevices[ k ], tszCurrentDeviceName, dNameSize ) ) + { + sprintf( szCurrentDeviceName, "%S", tszCurrentDeviceName ); + + if( 0 == strcmp( strlwr( szCurrentDeviceName ), szDeviceName ) ) + { + FreePSAPIRoutines(); + + return true; + } + } + } + + // + // free PSAPI routines + // + FreePSAPIRoutines(); + + return false; +} + + + + + +//------------------------------------------------------------------------------------------- +extern "C" __declspec(dllexport) void FindProcess( HWND hwndParent, + int string_size, + char *variables, + stack_t **stacktop ) +{ + char szParameter[ 1024 ]; + + + g_hwndParent = hwndParent; + + EXDLL_INIT(); + { + popstring( szParameter ); + + if( true == FindProc( szParameter ) ) + wsprintf( szParameter, "1" ); + else + wsprintf( szParameter, "0" ); + + setuservariable( INST_R0, szParameter ); + } +} + + + + + +//------------------------------------------------------------------------------------------- +extern "C" __declspec(dllexport) void KillProcess( HWND hwndParent, + int string_size, + char *variables, + stack_t **stacktop ) +{ + char szParameter[ 1024 ]; + + + g_hwndParent = hwndParent; + + EXDLL_INIT(); + { + popstring( szParameter ); + + if( true == KillProc( szParameter ) ) + wsprintf( szParameter, "1" ); + else + wsprintf( szParameter, "0" ); + + setuservariable( INST_R0, szParameter ); + } +} + + + + + +//------------------------------------------------------------------------------------------- +extern "C" __declspec(dllexport) void FindDevice( HWND hwndParent, + int string_size, + char *variables, + stack_t **stacktop ) +{ + char szParameter[ 1024 ]; + + + g_hwndParent = hwndParent; + + EXDLL_INIT(); + { + popstring( szParameter ); + + if( true == FindDev( szParameter ) ) + wsprintf( szParameter, "1" ); + else + wsprintf( szParameter, "0" ); + + setuservariable( INST_R0, szParameter ); + } +} diff --git a/admin/win/nsi/nsis_processes/src/processes.h b/admin/win/nsi/nsis_processes/src/processes.h new file mode 100755 index 000000000..9bd069101 --- /dev/null +++ b/admin/win/nsi/nsis_processes/src/processes.h @@ -0,0 +1,49 @@ +#pragma once + + + + + +//------------------------------------------------------------------------------------------- +// PSAPI function pointers +typedef BOOL (WINAPI *lpfEnumProcesses) ( DWORD *, DWORD, DWORD * ); +typedef BOOL (WINAPI *lpfEnumProcessModules) ( HANDLE, HMODULE *, DWORD, LPDWORD ); +typedef DWORD (WINAPI *lpfGetModuleBaseName) ( HANDLE, HMODULE, LPTSTR, DWORD ); +typedef BOOL (WINAPI *lpfEnumDeviceDrivers) ( LPVOID *, DWORD, LPDWORD ); +typedef BOOL (WINAPI *lpfGetDeviceDriverBaseName)( LPVOID, LPTSTR, DWORD ); + + + + + + +//------------------------------------------------------------------------------------------- +// Internal use routines +bool LoadPSAPIRoutines( void ); +bool FreePSAPIRoutines( void ); + +bool FindProc( char *szProcess ); +bool KillProc( char *szProcess ); + +bool FindDev( char *szDriverName ); + + + + + +//------------------------------------------------------------------------------------------- +// Exported routines +extern "C" __declspec(dllexport) void FindProcess( HWND hwndParent, + int string_size, + char *variables, + stack_t **stacktop ); + +extern "C" __declspec(dllexport) void KillProcess( HWND hwndParent, + int string_size, + char *variables, + stack_t **stacktop ); + +extern "C" __declspec(dllexport) void FindDevice( HWND hwndParent, + int string_size, + char *variables, + stack_t **stacktop ); diff --git a/admin/win/nsi/nsis_processes/src/processes.ncb b/admin/win/nsi/nsis_processes/src/processes.ncb new file mode 100755 index 0000000000000000000000000000000000000000..c1a5f281fe57fcc5570c40953586a2799befc159 GIT binary patch literal 44032 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 literal 0 HcmV?d00001 diff --git a/admin/win/nsi/nsis_processes/src/processes.rc b/admin/win/nsi/nsis_processes/src/processes.rc new file mode 100755 index 000000000..c6e62a3c8 --- /dev/null +++ b/admin/win/nsi/nsis_processes/src/processes.rc @@ -0,0 +1,103 @@ +// Microsoft Visual C++ generated resource script. +// +#include "resource.h" + +#define APSTUDIO_READONLY_SYMBOLS +///////////////////////////////////////////////////////////////////////////// +// +// Generated from the TEXTINCLUDE 2 resource. +// +#include "afxres.h" + +///////////////////////////////////////////////////////////////////////////// +#undef APSTUDIO_READONLY_SYMBOLS + +///////////////////////////////////////////////////////////////////////////// +// English (U.S.) resources + +#if !defined(AFX_RESOURCE_DLL) || defined(AFX_TARG_ENU) +#ifdef _WIN32 +LANGUAGE LANG_ENGLISH, SUBLANG_ENGLISH_US +#pragma code_page(1252) +#endif //_WIN32 + +#ifdef APSTUDIO_INVOKED +///////////////////////////////////////////////////////////////////////////// +// +// TEXTINCLUDE +// + +1 TEXTINCLUDE +BEGIN + "resource.h\0" +END + +2 TEXTINCLUDE +BEGIN + "#include ""afxres.h""\r\n" + "\0" +END + +3 TEXTINCLUDE +BEGIN + "\r\n" + "\0" +END + +#endif // APSTUDIO_INVOKED + + +///////////////////////////////////////////////////////////////////////////// +// +// Version +// + +VS_VERSION_INFO VERSIONINFO + FILEVERSION 1,0,0,1 + PRODUCTVERSION 1,0,0,1 + FILEFLAGSMASK 0x17L +#ifdef _DEBUG + FILEFLAGS 0x1L +#else + FILEFLAGS 0x0L +#endif + FILEOS 0x4L + FILETYPE 0x2L + FILESUBTYPE 0x0L +BEGIN + BLOCK "StringFileInfo" + BEGIN + BLOCK "040904b0" + BEGIN + VALUE "Comments", "NSIS Plug-in for Windows process management. Only WinNT, Win2K, WinXP and Win2003 Server supported." + VALUE "CompanyName", "Andrei Ciubotaru [Hardwired]" + VALUE "FileDescription", "Windows Processes Management" + VALUE "FileVersion", "1, 0, 0, 1" + VALUE "InternalName", "Processes" + VALUE "LegalCopyright", "Copyright (c) 2004 Hardwired. No rights reserved." + VALUE "OriginalFilename", "Processes.dll" + VALUE "ProductName", "Processes" + VALUE "ProductVersion", "1, 0, 0, 1" + END + END + BLOCK "VarFileInfo" + BEGIN + VALUE "Translation", 0x409, 1200 + END +END + +#endif // English (U.S.) resources +///////////////////////////////////////////////////////////////////////////// + + + +#ifndef APSTUDIO_INVOKED +///////////////////////////////////////////////////////////////////////////// +// +// Generated from the TEXTINCLUDE 3 resource. +// + + +///////////////////////////////////////////////////////////////////////////// +#endif // not APSTUDIO_INVOKED + diff --git a/admin/win/nsi/nsis_processes/src/processes.sln b/admin/win/nsi/nsis_processes/src/processes.sln new file mode 100755 index 000000000..73fc989e2 --- /dev/null +++ b/admin/win/nsi/nsis_processes/src/processes.sln @@ -0,0 +1,21 @@ +Microsoft Visual Studio Solution File, Format Version 8.00 +Project("{8BC9CEB8-8B4A-11D0-8D11-00A0C91BC942}") = "processes", "processes.vcproj", "{3438467F-A719-46DC-93E5-137A8B691727}" + ProjectSection(ProjectDependencies) = postProject + EndProjectSection +EndProject +Global + GlobalSection(SolutionConfiguration) = preSolution + Debug = Debug + Release = Release + EndGlobalSection + GlobalSection(ProjectConfiguration) = postSolution + {3438467F-A719-46DC-93E5-137A8B691727}.Debug.ActiveCfg = Debug|Win32 + {3438467F-A719-46DC-93E5-137A8B691727}.Debug.Build.0 = Debug|Win32 + {3438467F-A719-46DC-93E5-137A8B691727}.Release.ActiveCfg = Release|Win32 + {3438467F-A719-46DC-93E5-137A8B691727}.Release.Build.0 = Release|Win32 + EndGlobalSection + GlobalSection(ExtensibilityGlobals) = postSolution + EndGlobalSection + GlobalSection(ExtensibilityAddIns) = postSolution + EndGlobalSection +EndGlobal diff --git a/admin/win/nsi/nsis_processes/src/processes.txt b/admin/win/nsi/nsis_processes/src/processes.txt new file mode 100755 index 000000000..51d11902a --- /dev/null +++ b/admin/win/nsi/nsis_processes/src/processes.txt @@ -0,0 +1,122 @@ +---------------------------------------------------------------- +---------------------------------------------------------------- +Processes (Processes.dll) +Version: 1.0.0.1 +Release: 12.december.2004 +Description:Nullsoft Installer (NSIS) plug-in for managing?! + Windows processes. + +Copyright: © 2004 Hardwired. No rights reserved. + There is no restriction and no guaranty for using + this software. + +Author: Andrei Ciubotaru [Hardwired] + Lead Developer ICode&Ideas SRL (http://www.icode.ro) + hardwiredteks@gmail.com, hardwired@icode.ro + +---------------------------------------------------------------- +---------------------------------------------------------------- +INTRODUCTION + + The Need For Plug-in - I need it for the one of my installers. + + Briefly: Use it when you need to find\kill a process when +installing\uninstalling some application. Also, use it when you +need to test the presence of a device driver. + + +SUPPORT + + Supported platforms are: WinNT,Win2K,WinXP and Win2003 Server. + + +DESCRIPTION + + Processes::FindProcess + + Searches the currently running processes for the given + process name. + + return: 1 - the process was found + 0 - the process was not found + + Processes::KillProcess + + Searches the currently running processes for the given + process name. If the process is found then the it gets + killed. + + return: 1 - the process was found and killed + 0 - the process was not found or the process + cannot be killed (insuficient rights) + + Processes::FindDevice + + Searches the installed devices drivers for the given + device base name. + (important: I said BASE NAME not FILENAME) + + return: 1 - the device driver was found + 0 - the device driver was not found + + +USAGE + + First of all, does not matter where you use it. Ofcourse, the +routines must be called inside of a Section/Function scope. + + Processes::FindProcess "process_name.exe" + Pop $R0 + + StrCmp $R0 "1" make_my_day noooooo + + make_my_day: + ... + + noooooo: + ... + + + Processes::KillProcess "process_name.exe" + Pop $R0 + + StrCmp $R0 "1" dead_meat why_wont_you_die + + dead_meat: + ... + + why_wont_you_die: + ... + + + Processes::FindDevice "device_base_name" + Pop $R0 + + StrCmp $R0 "1" blabla more_blabla + + blabla: + ... + + more_blabla: + ... + + +THANKS + + Sunil Kamath for inspiring me. I wanted to use its FindProcDLL +but my requirements made it imposible. + + Nullsoft for creating this very powerfull installer. One big, +free and full-featured (hmmm... and guiless for the moment) mean +install machine!:) + + ME for being such a great coder... + ... HAHAHAHAHAHAHA! + +ONE MORE THING + + If you use the plugin or it's source-code, I would apreciate +if my name is mentioned. + +---------------------------------------------------------------- +---------------------------------------------------------------- diff --git a/admin/win/nsi/nsis_processes/src/processes.vcproj b/admin/win/nsi/nsis_processes/src/processes.vcproj new file mode 100755 index 000000000..245cbc99f --- /dev/null +++ b/admin/win/nsi/nsis_processes/src/processes.vcproj @@ -0,0 +1,222 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/admin/win/nsi/nsis_processes/src/resource.h b/admin/win/nsi/nsis_processes/src/resource.h new file mode 100755 index 000000000..506377e21 --- /dev/null +++ b/admin/win/nsi/nsis_processes/src/resource.h @@ -0,0 +1,15 @@ +//{{NO_DEPENDENCIES}} +// Microsoft Visual C++ generated include file. +// Used by processes.rc +// + +// Next default values for new objects +// +#ifdef APSTUDIO_INVOKED +#ifndef APSTUDIO_READONLY_SYMBOLS +#define _APS_NEXT_RESOURCE_VALUE 101 +#define _APS_NEXT_COMMAND_VALUE 40001 +#define _APS_NEXT_CONTROL_VALUE 1001 +#define _APS_NEXT_SYMED_VALUE 101 +#endif +#endif diff --git a/admin/win/nsi/page_header.bmp b/admin/win/nsi/page_header.bmp old mode 100755 new mode 100644 diff --git a/admin/win/nsi/revision.txt b/admin/win/nsi/revision.txt old mode 100644 new mode 100755 index 56749c830..c4fbb1cfa --- a/admin/win/nsi/revision.txt +++ b/admin/win/nsi/revision.txt @@ -1 +1 @@ -96 \ No newline at end of file +97 \ No newline at end of file diff --git a/admin/win/nsi/tomahawk.ini b/admin/win/nsi/tomahawk.ini old mode 100755 new mode 100644 diff --git a/admin/win/nsi/tomahawk.nsi b/admin/win/nsi/tomahawk.nsi old mode 100755 new mode 100644 index 4bdb57fa4..c2d2a9bd1 --- a/admin/win/nsi/tomahawk.nsi +++ b/admin/win/nsi/tomahawk.nsi @@ -159,6 +159,45 @@ Function LaunchTomahawkAsUser FunctionEnd !endif +############################################################################## +# # +# PROCESS HANDLING FUNCTIONS AND MACROS # +# # +############################################################################## + +!macro CheckForProcess processName gotoWhenFound gotoWhenNotFound + Processes::FindProcess ${processName} + StrCmp $R0 "0" ${gotoWhenNotFound} ${gotoWhenFound} +!macroend + +!macro ConfirmEndProcess processName + MessageBox MB_YESNO|MB_ICONEXCLAMATION \ + "Found ${processName} process(s) which need to be stopped.$\nDo you want the installer to stop these for you?" \ + IDYES process_${processName}_kill IDNO process_${processName}_ended + process_${processName}_kill: + DetailPrint "Killing ${processName} processes." + Processes::KillProcess ${processName} + Sleep 1500 + StrCmp $R0 "1" process_${processName}_ended + DetailPrint "Process to kill not found!" + process_${processName}_ended: +!macroend + +!macro CheckAndConfirmEndProcess processName + !insertmacro CheckForProcess ${processName} 0 no_process_${processName}_to_end + !insertmacro ConfirmEndProcess ${processName} + no_process_${processName}_to_end: +!macroend + +!macro EnsureTomahawkShutdown un + Function ${un}EnsureTomahawkShutdown + !insertmacro CheckAndConfirmEndProcess "tomahawk.exe" + FunctionEnd +!macroend + +!insertmacro EnsureTomahawkShutdown "" +!insertmacro EnsureTomahawkShutdown "un." + ############################################################################## # # # RE-INSTALLER FUNCTIONS # @@ -554,6 +593,16 @@ Function .onInit StrCmp $R0 0 +3 MessageBox MB_OK|MB_ICONEXCLAMATION "The installer is already running." Abort + + ;Use available InstallLocation when possible. This is useful in the uninstaller + ;via re-install, which would otherwise use a default location - a bug. + ReadRegStr $R0 HKLM "Software\Microsoft\Windows\CurrentVersion\Uninstall\Tomahawk" "InstallLocation" + StrCmp $R0 "" SkipSetInstDir + StrCpy $INSTDIR $R0 + SkipSetInstDir: + + ;Shutdown Tomahawk in case Add/Remove re-installer option used. + Call EnsureTomahawkShutdown FunctionEnd Function .onInstSuccess diff --git a/admin/win/nsi/welcome.bmp b/admin/win/nsi/welcome.bmp old mode 100755 new mode 100644 From a55124ed753939d131446a799695318374819d4f Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Julian=20B=C3=A4ume?= Date: Thu, 31 Mar 2011 00:00:49 +0800 Subject: [PATCH 219/329] ship the translations within the binary MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit build qm-files for translation create resource file for translations, contains german translations, for now. this is some kind of hack to make it work. rcc doesn’t allow out-of-source builds, so the commands need to be specified by hand, to copy things around between source and binary directories. --- lang/tomahawk_i18n.qrc | 5 +++++ lang/translations.cmake | 25 +++++++++++++++++++++++++ src/CMakeLists.txt | 4 +++- 3 files changed, 33 insertions(+), 1 deletion(-) create mode 100644 lang/tomahawk_i18n.qrc create mode 100644 lang/translations.cmake diff --git a/lang/tomahawk_i18n.qrc b/lang/tomahawk_i18n.qrc new file mode 100644 index 000000000..a309b5d51 --- /dev/null +++ b/lang/tomahawk_i18n.qrc @@ -0,0 +1,5 @@ + + +tomahawk_de.qm + + diff --git a/lang/translations.cmake b/lang/translations.cmake new file mode 100644 index 000000000..a5b92f2f4 --- /dev/null +++ b/lang/translations.cmake @@ -0,0 +1,25 @@ +FILE (GLOB TS_FILES ${CMAKE_SOURCE_DIR}/lang/*.ts) +QT4_ADD_TRANSLATION(QM_FILES ${TS_FILES}) + +## HACK HACK HACK - around rcc limitations to allow out of source-tree building +SET( trans_file tomahawk_i18n ) +SET( trans_srcfile ${CMAKE_SOURCE_DIR}/lang/${trans_file}.qrc) +SET( trans_infile ${CMAKE_CURRENT_BINARY_DIR}/${trans_file}.qrc) +SET( trans_outfile ${CMAKE_CURRENT_BINARY_DIR}/qrc_${trans_file}.cxx) + +# Copy the QRC file to the output directory +ADD_CUSTOM_COMMAND( + OUTPUT ${trans_infile} + COMMAND ${CMAKE_COMMAND} -E copy ${trans_srcfile} ${trans_infile} + MAIN_DEPENDENCY ${trans_srcfile} +) + +# Run the resource compiler (rcc_options should already be set) +ADD_CUSTOM_COMMAND( + OUTPUT ${trans_outfile} + COMMAND ${QT_RCC_EXECUTABLE} + ARGS ${rcc_options} -name ${trans_file} -o ${trans_outfile} ${trans_infile} + MAIN_DEPENDENCY ${trans_infile} + DEPENDS ${QM_FILES} +) + diff --git a/src/CMakeLists.txt b/src/CMakeLists.txt index 259756b05..69081156a 100644 --- a/src/CMakeLists.txt +++ b/src/CMakeLists.txt @@ -186,7 +186,9 @@ qt4_wrap_cpp( tomahawkMoc ${tomahawkHeaders} ) configure_file(${CMAKE_CURRENT_SOURCE_DIR}/config.h.in ${CMAKE_CURRENT_BINARY_DIR}/config.h) -SET( final_src ${final_src} ${tomahawkMoc} ${tomahawkSources} ${tomahawkHeaders} ) +include( ${CMAKE_SOURCE_DIR}/lang/translations.cmake ) + +SET( final_src ${final_src} ${tomahawkMoc} ${tomahawkSources} ${tomahawkHeaders} ${trans_outfile}) IF( "${gui}" STREQUAL "no" ) ELSE() From 59391e3841cc80d3e3fc9f8f951b01ab04340173 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Julian=20B=C3=A4ume?= Date: Thu, 31 Mar 2011 09:06:13 +0800 Subject: [PATCH 220/329] load translation based on system locale this loads the resource that contains translations for the locale the user has set. --- src/main.cpp | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/src/main.cpp b/src/main.cpp index 916ffbb2e..02a3ae80d 100644 --- a/src/main.cpp +++ b/src/main.cpp @@ -17,6 +17,7 @@ */ #include "tomahawk/tomahawkapp.h" +#include #ifdef Q_WS_MAC #include "tomahawkapp_mac.h" @@ -41,6 +42,12 @@ main( int argc, char *argv[] ) try { TomahawkApp a( argc, argv ); + + QString locale = QLocale::system().name(); + + QTranslator translator; + translator.load(QString(":/lang/tomahawk_") + locale); + a.installTranslator(&translator); return a.exec(); } catch( const std::runtime_error& e ) From 0c4e304ee53c34eb6752e71eeff1a0c2ff37f3c2 Mon Sep 17 00:00:00 2001 From: Jeff Mitchell Date: Thu, 31 Mar 2011 12:18:49 -0400 Subject: [PATCH 221/329] Make debug output a little more helpful --- src/sip/twitter/twitter.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/sip/twitter/twitter.cpp b/src/sip/twitter/twitter.cpp index 772c9abdb..dd10d5d2c 100644 --- a/src/sip/twitter/twitter.cpp +++ b/src/sip/twitter/twitter.cpp @@ -599,7 +599,7 @@ TwitterPlugin::makeConnection( const QString &screenName, const QHash< QString, qDebug() << Q_FUNC_INFO; if ( m_attemptedConnects.contains( screenName ) && m_attemptedConnects[screenName] ) { - qDebug() << "Already attempted to connect to this peer with no change in their status, not trying again for now"; + qDebug() << "Already attempted to connect to " << screenName << " with no change in their status, not trying again for now"; return; } if ( !peerData.contains( "host" ) || !peerData.contains( "port" ) || !peerData.contains( "pkey" ) || !peerData.contains( "node" ) ) From 47451e4fba708a334f5394127fe6a5533e14f6a4 Mon Sep 17 00:00:00 2001 From: Jeff Mitchell Date: Thu, 31 Mar 2011 15:12:08 -0400 Subject: [PATCH 222/329] Add more debugging and remove the don't-try-to-reconnect behavior --- src/libtomahawk/network/servent.cpp | 3 +++ src/sip/twitter/twitter.cpp | 10 +--------- src/sip/twitter/twitter.h | 1 - 3 files changed, 4 insertions(+), 10 deletions(-) diff --git a/src/libtomahawk/network/servent.cpp b/src/libtomahawk/network/servent.cpp index 1d54d0897..083e67d92 100644 --- a/src/libtomahawk/network/servent.cpp +++ b/src/libtomahawk/network/servent.cpp @@ -792,8 +792,11 @@ Servent::isIPWhitelisted( QHostAddress ip ) bool Servent::connectedToSession( const QString& session ) { + qDebug() << Q_FUNC_INFO; + qDebug() << "Checking against " << session; foreach( ControlConnection* cc, m_controlconnections ) { + qDebug() << "Checking session " << cc->id(); if( cc->id() == session ) return true; } diff --git a/src/sip/twitter/twitter.cpp b/src/sip/twitter/twitter.cpp index dd10d5d2c..7947042ae 100644 --- a/src/sip/twitter/twitter.cpp +++ b/src/sip/twitter/twitter.cpp @@ -178,7 +178,6 @@ TwitterPlugin::disconnectPlugin() delete m_twitterAuth.data(); m_cachedPeers.empty(); - m_attemptedConnects.empty(); m_isOnline = false; } @@ -571,7 +570,6 @@ TwitterPlugin::registerOffer( const QString &screenName, const QHash< QString, Q { m_cachedPeers[screenName] = QVariant::fromValue< QHash< QString, QVariant > >( _peerData ); TomahawkSettings::instance()->setTwitterCachedPeers( m_cachedPeers ); - m_attemptedConnects[screenName] = false; } if ( m_isOnline && _peerData.contains( "host" ) && _peerData.contains( "port" ) && _peerData.contains( "pkey" ) ) @@ -597,14 +595,9 @@ void TwitterPlugin::makeConnection( const QString &screenName, const QHash< QString, QVariant > &peerData ) { qDebug() << Q_FUNC_INFO; - if ( m_attemptedConnects.contains( screenName ) && m_attemptedConnects[screenName] ) - { - qDebug() << "Already attempted to connect to " << screenName << " with no change in their status, not trying again for now"; - return; - } if ( !peerData.contains( "host" ) || !peerData.contains( "port" ) || !peerData.contains( "pkey" ) || !peerData.contains( "node" ) ) { - qDebug() << "TwitterPlugin could not find host and/or port and/or pkey for peer " << screenName; + qDebug() << "TwitterPlugin could not find host and/or port and/or pkey and/or node for peer " << screenName; return; } QString friendlyName = QString( '@' + screenName ); @@ -614,7 +607,6 @@ TwitterPlugin::makeConnection( const QString &screenName, const QHash< QString, peerData["pkey"].toString(), friendlyName, peerData["node"].toString() ); - m_attemptedConnects[screenName] = true; } void diff --git a/src/sip/twitter/twitter.h b/src/sip/twitter/twitter.h index 4e8a98a31..c0da00d7f 100644 --- a/src/sip/twitter/twitter.h +++ b/src/sip/twitter/twitter.h @@ -108,7 +108,6 @@ private: qint64 m_cachedMentionsSinceId; qint64 m_cachedDirectMessagesSinceId; QHash< QString, QVariant > m_cachedPeers; - QHash< QString, bool > m_attemptedConnects; QSet m_keyCache; bool m_finishedFriends; bool m_finishedMentions; From de92b0b726e54c8de8a484a7cbfb793b1b3c027b Mon Sep 17 00:00:00 2001 From: Jeff Mitchell Date: Thu, 31 Mar 2011 22:08:32 -0400 Subject: [PATCH 223/329] Initial work on a dir watcher for the scanner --- .../database/databasecommand_deletefiles.h | 6 ++++++ src/scanmanager.cpp | 18 ++++++++++++++++++ src/scanmanager.h | 5 +++++ 3 files changed, 29 insertions(+) diff --git a/src/libtomahawk/database/databasecommand_deletefiles.h b/src/libtomahawk/database/databasecommand_deletefiles.h index 4668d0249..53d16f314 100644 --- a/src/libtomahawk/database/databasecommand_deletefiles.h +++ b/src/libtomahawk/database/databasecommand_deletefiles.h @@ -45,6 +45,12 @@ public: setSource( source ); } + explicit DatabaseCommand_DeleteFiles( const QVariantList& ids, const Tomahawk::source_ptr& source, QObject* parent = 0 ) + : DatabaseCommandLoggable( parent ), m_ids( ids ) + { + setSource( source ); + } + virtual QString commandname() const { return "deletefiles"; } virtual void exec( DatabaseImpl* ); diff --git a/src/scanmanager.cpp b/src/scanmanager.cpp index 017dc8171..81d11969e 100644 --- a/src/scanmanager.cpp +++ b/src/scanmanager.cpp @@ -21,6 +21,7 @@ #include #include #include +#include #include "musicscanner.h" #include "tomahawksettings.h" @@ -40,13 +41,20 @@ ScanManager::ScanManager( QObject* parent ) : QObject( parent ) , m_scanner( 0 ) , m_musicScannerThreadController( 0 ) + , m_dirWatcher( 0 ) { s_instance = this; + m_dirWatcher = new QFileSystemWatcher( parent ); + connect( TomahawkSettings::instance(), SIGNAL( changed() ), SLOT( onSettingsChanged() ) ); + connect( m_dirWatcher, SIGNAL( directoryChanged( const QString & ) ), SLOT( handleChangedDir( const QString & ) ) ); if ( TomahawkSettings::instance()->hasScannerPath() ) m_currScannerPath = TomahawkSettings::instance()->scannerPath(); + + m_dirWatcher->addPaths( m_currScannerPath ); + qDebug() << "filewatcher dirs = " << m_dirWatcher->directories(); } @@ -83,6 +91,8 @@ ScanManager::onSettingsChanged() m_currScannerPath != TomahawkSettings::instance()->scannerPath() ) { m_currScannerPath = TomahawkSettings::instance()->scannerPath(); + m_dirWatcher->removePaths( m_dirWatcher->directories() ); + m_dirWatcher->addPaths( m_currScannerPath ); runManualScan( m_currScannerPath ); } } @@ -107,6 +117,14 @@ ScanManager::runManualScan( const QStringList& path ) } +void +ScanManager::handleChangedDir( const QString &path ) +{ + qDebug() << Q_FUNC_INFO; + qDebug() << "Dir changed: " << path; +} + + void ScanManager::scannerFinished() { diff --git a/src/scanmanager.h b/src/scanmanager.h index a20d9990f..999b629b5 100644 --- a/src/scanmanager.h +++ b/src/scanmanager.h @@ -26,6 +26,7 @@ class MusicScanner; class QThread; +class QFileSystemWatcher; class ScanManager : public QObject { @@ -42,6 +43,9 @@ public: signals: void finished(); +public slots: + void handleChangedDir( const QString &path ); + private slots: void scannerQuit(); void scannerFinished(); @@ -55,6 +59,7 @@ private: MusicScanner* m_scanner; QThread* m_musicScannerThreadController; QStringList m_currScannerPath; + QFileSystemWatcher* m_dirWatcher; }; #endif From 06f0dd87682d7154a1ebc3f3dc782ec2a0ce9b3f Mon Sep 17 00:00:00 2001 From: Jeff Mitchell Date: Fri, 1 Apr 2011 17:05:40 -0400 Subject: [PATCH 224/329] More work on dir watchers -- it's mostly there except for the actual DB modifications --- .../database/databasecommand_dirmtimes.cpp | 26 ++++++-- .../database/databasecommand_dirmtimes.h | 7 ++ src/musicscanner.cpp | 3 + src/musicscanner.h | 2 + src/scanmanager.cpp | 64 ++++++++++++++++++- src/scanmanager.h | 8 ++- 6 files changed, 99 insertions(+), 11 deletions(-) diff --git a/src/libtomahawk/database/databasecommand_dirmtimes.cpp b/src/libtomahawk/database/databasecommand_dirmtimes.cpp index 829a609c8..1bedc8402 100644 --- a/src/libtomahawk/database/databasecommand_dirmtimes.cpp +++ b/src/libtomahawk/database/databasecommand_dirmtimes.cpp @@ -38,21 +38,33 @@ DatabaseCommand_DirMtimes::execSelect( DatabaseImpl* dbi ) { QMap mtimes; TomahawkSqlQuery query = dbi->newquery(); - if( m_prefix.isEmpty() ) + if( m_prefix.isEmpty() && m_prefixes.isEmpty() ) query.exec( "SELECT name, mtime FROM dirs_scanned" ); + else if( m_prefixes.isEmpty() ) + execSelectPath( dbi, m_prefix, mtimes ); else { - query.prepare( QString( "SELECT name, mtime " - "FROM dirs_scanned " - "WHERE name LIKE '%1%'" ).arg( m_prefix.replace( '\'',"''" ) ) ); - query.exec(); + if( !m_prefix.isEmpty() ) + execSelectPath( dbi, m_prefix, mtimes ); + foreach( QString path, m_prefixes ) + execSelectPath( dbi, path, mtimes ); } + emit done( mtimes ); +} + +void +DatabaseCommand_DirMtimes::execSelectPath( DatabaseImpl *dbi, QString &path, QMap &mtimes ) +{ + TomahawkSqlQuery query = dbi->newquery(); + query.prepare( QString( "SELECT name, mtime " + "FROM dirs_scanned " + "WHERE name LIKE '%1%'" ).arg( path.replace( '\'',"''" ) ) ); + query.exec(); + while( query.next() ) { mtimes.insert( query.value( 0 ).toString(), query.value( 1 ).toUInt() ); } - - emit done( mtimes ); } diff --git a/src/libtomahawk/database/databasecommand_dirmtimes.h b/src/libtomahawk/database/databasecommand_dirmtimes.h index f35a78437..3ff81607b 100644 --- a/src/libtomahawk/database/databasecommand_dirmtimes.h +++ b/src/libtomahawk/database/databasecommand_dirmtimes.h @@ -37,6 +37,10 @@ public: explicit DatabaseCommand_DirMtimes( const QString& prefix = "", QObject* parent = 0 ) : DatabaseCommand( parent ), m_prefix( prefix ), m_update( false ) {} + + explicit DatabaseCommand_DirMtimes( const QStringList& prefixes = QStringList(), QObject* parent = 0 ) + : DatabaseCommand( parent ), m_prefixes( prefixes ), m_update( false ) + {} explicit DatabaseCommand_DirMtimes( QMap tosave, QObject* parent = 0 ) : DatabaseCommand( parent ), m_update( true ), m_tosave( tosave ) @@ -52,9 +56,12 @@ signals: public slots: private: + void execSelectPath( DatabaseImpl *dbi, QString &path, QMap &mtimes ); + void execSelect( DatabaseImpl* dbi ); void execUpdate( DatabaseImpl* dbi ); QString m_prefix; + QStringList m_prefixes; bool m_update; QMap m_tosave; }; diff --git a/src/musicscanner.cpp b/src/musicscanner.cpp index a97171416..fe8eb29f6 100644 --- a/src/musicscanner.cpp +++ b/src/musicscanner.cpp @@ -185,9 +185,12 @@ MusicScanner::listerFinished( const QMap& newmtimes ) { qDebug() << "Removing stale dir:" << path; Database::instance()->enqueue( QSharedPointer( new DatabaseCommand_DeleteFiles( path, SourceList::instance()->getLocal() ) ) ); + emit removeWatchedDir( path ); } } + emit addWatchedDirs( newmtimes.keys() ); + // save mtimes, then quit thread DatabaseCommand_DirMtimes* cmd = new DatabaseCommand_DirMtimes( newmtimes ); connect( cmd, SIGNAL( finished() ), SLOT( deleteLister() ) ); diff --git a/src/musicscanner.h b/src/musicscanner.h index c4fc5bb5b..dd2725334 100644 --- a/src/musicscanner.h +++ b/src/musicscanner.h @@ -76,6 +76,8 @@ signals: //void fileScanned( QVariantMap ); void finished(); void batchReady( const QVariantList& ); + void addWatchedDirs( const QStringList & ); + void removeWatchedDir( const QString & ); private: QVariant readFile( const QFileInfo& fi ); diff --git a/src/scanmanager.cpp b/src/scanmanager.cpp index 81d11969e..e1d5f028c 100644 --- a/src/scanmanager.cpp +++ b/src/scanmanager.cpp @@ -22,11 +22,15 @@ #include #include #include +#include #include "musicscanner.h" #include "tomahawksettings.h" #include "tomahawkutils.h" +#include "database/database.h" +#include "database/databasecommand_dirmtimes.h" + ScanManager* ScanManager::s_instance = 0; @@ -53,8 +57,8 @@ ScanManager::ScanManager( QObject* parent ) if ( TomahawkSettings::instance()->hasScannerPath() ) m_currScannerPath = TomahawkSettings::instance()->scannerPath(); - m_dirWatcher->addPaths( m_currScannerPath ); - qDebug() << "filewatcher dirs = " << m_dirWatcher->directories(); + qDebug() << "loading initial directories to watch"; + QTimer::singleShot( 1000, this, SLOT( startupWatchPaths() ) ); } @@ -98,6 +102,36 @@ ScanManager::onSettingsChanged() } +void +ScanManager::startupWatchPaths() +{ + qDebug() << Q_FUNC_INFO; + + if( !Database::instance() ) + { + QTimer::singleShot( 1000, this, SLOT( startupWatchPaths() ) ); + return; + } + + DatabaseCommand_DirMtimes* cmd = new DatabaseCommand_DirMtimes( m_currScannerPath ); + connect( cmd, SIGNAL( done( QMap ) ), + SLOT( setInitialPaths( QMap ) ) ); + Database::instance()->enqueue( QSharedPointer(cmd) ); +} + + +void +ScanManager::setInitialPaths( QMap pathMap ) +{ + qDebug() << Q_FUNC_INFO; + foreach( QString path, pathMap.keys() ) + { + qDebug() << "Adding " << path << " to watcher"; + m_dirWatcher->addPath( path ); + } +} + + void ScanManager::runManualScan( const QStringList& path ) { @@ -109,6 +143,8 @@ ScanManager::runManualScan( const QStringList& path ) m_scanner = new MusicScanner( path ); m_scanner->moveToThread( m_musicScannerThreadController ); connect( m_scanner, SIGNAL( finished() ), SLOT( scannerFinished() ) ); + connect( m_scanner, SIGNAL( addWatchedDirs( const QStringList & ) ), SLOT( addWatchedDirs( const QStringList & ) ) ); + connect( m_scanner, SIGNAL( removeWatchedDir( const QString & ) ), SLOT( removeWatchedDir( const QString & ) ) ); m_musicScannerThreadController->start( QThread::IdlePriority ); QMetaObject::invokeMethod( m_scanner, "startScan" ); } @@ -116,9 +152,31 @@ ScanManager::runManualScan( const QStringList& path ) qDebug() << "Could not run manual scan, old scan still running"; } +void +ScanManager::addWatchedDirs( const QStringList& paths ) +{ + qDebug() << Q_FUNC_INFO; + QStringList currentWatchedPaths = m_dirWatcher->directories(); + foreach( QString path, paths ) + { + if( !currentWatchedPaths.contains( path ) ) + { + qDebug() << "adding " << path << " to watched dirs"; + m_dirWatcher->addPath( path ); + } + } +} void -ScanManager::handleChangedDir( const QString &path ) +ScanManager::removeWatchedDir( const QString& path ) +{ + qDebug() << Q_FUNC_INFO; + qDebug() << "removing " << path << " from watched dirs"; + m_dirWatcher->removePath( path ); +} + +void +ScanManager::handleChangedDir( const QString& path ) { qDebug() << Q_FUNC_INFO; qDebug() << "Dir changed: " << path; diff --git a/src/scanmanager.h b/src/scanmanager.h index 999b629b5..232627058 100644 --- a/src/scanmanager.h +++ b/src/scanmanager.h @@ -21,6 +21,7 @@ #include #include +#include #include "dllmacro.h" @@ -44,12 +45,17 @@ signals: void finished(); public slots: - void handleChangedDir( const QString &path ); + void handleChangedDir( const QString& path ); + void addWatchedDirs( const QStringList& paths ); + void removeWatchedDir( const QString& path ); + void setInitialPaths( QMap pathMap ); private slots: void scannerQuit(); void scannerFinished(); void scannerDestroyed( QObject* scanner ); + + void startupWatchPaths(); void onSettingsChanged(); From d818a7f6979adaa00cf9adee3e74ba2e8807953e Mon Sep 17 00:00:00 2001 From: Christian Muehlhaeuser Date: Sat, 2 Apr 2011 06:10:05 +0200 Subject: [PATCH 225/329] * Added ready() signal to Database class. --- src/libtomahawk/database/database.cpp | 3 +++ src/libtomahawk/database/database.h | 3 +++ src/libtomahawk/database/databaseimpl.cpp | 2 -- 3 files changed, 6 insertions(+), 2 deletions(-) diff --git a/src/libtomahawk/database/database.cpp b/src/libtomahawk/database/database.cpp index 46e61badb..691a9db3b 100644 --- a/src/libtomahawk/database/database.cpp +++ b/src/libtomahawk/database/database.cpp @@ -37,6 +37,9 @@ Database::Database( const QString& dbname, QObject* parent ) { s_instance = this; + connect( m_impl, SIGNAL( indexReady() ), SIGNAL( indexReady() ) ); + connect( m_impl, SIGNAL( indexReady() ), SIGNAL( ready() ) ); + m_workerRW->start(); } diff --git a/src/libtomahawk/database/database.h b/src/libtomahawk/database/database.h index 498f5239b..4dad4882e 100644 --- a/src/libtomahawk/database/database.h +++ b/src/libtomahawk/database/database.h @@ -41,6 +41,7 @@ class DLLEXPORT Database : public QObject { Q_OBJECT + public: static Database* instance(); @@ -54,6 +55,8 @@ public: signals: void indexReady(); // search index + void ready(); + void newJobRO( QSharedPointer ); void newJobRW( QSharedPointer ); diff --git a/src/libtomahawk/database/databaseimpl.cpp b/src/libtomahawk/database/databaseimpl.cpp index 485c7a5ef..ff96c3d7d 100644 --- a/src/libtomahawk/database/databaseimpl.cpp +++ b/src/libtomahawk/database/databaseimpl.cpp @@ -46,8 +46,6 @@ DatabaseImpl::DatabaseImpl( const QString& dbname, Database* parent ) , m_lastalbid( 0 ) , m_lasttrkid( 0 ) { - connect( this, SIGNAL( indexReady() ), parent, SIGNAL( indexReady() ) ); - db = QSqlDatabase::addDatabase( "QSQLITE", "tomahawk" ); db.setDatabaseName( dbname ); if ( !db.open() ) From 3bc496eaafbcd5a3417150fb707ebd0d564b6825 Mon Sep 17 00:00:00 2001 From: Jeff Mitchell Date: Sat, 2 Apr 2011 00:18:29 -0400 Subject: [PATCH 226/329] Implement watched folders and scan-on-startup. Folders are scanned after 10 seconds without a change. Also handles deferring scans of directories if attempted during an ongoing scan, both for recursive and non-recursive scans. Fixes TWK-30 / THK-30. --- src/libtomahawk/aclsystem.cpp | 2 +- src/libtomahawk/database/database.cpp | 2 + src/libtomahawk/database/database.h | 6 ++ src/libtomahawk/database/databaseimpl.h | 2 + src/libtomahawk/tomahawksettings.cpp | 35 +++++++-- src/libtomahawk/tomahawksettings.h | 9 ++- src/musicscanner.cpp | 17 +++-- src/musicscanner.h | 20 +++-- src/scanmanager.cpp | 97 ++++++++++++++++++++----- src/scanmanager.h | 18 +++-- src/settingsdialog.cpp | 6 +- src/settingsdialog.ui | 15 +++- src/tomahawkapp.cpp | 2 +- src/tomahawkwindow.cpp | 4 +- 14 files changed, 185 insertions(+), 50 deletions(-) diff --git a/src/libtomahawk/aclsystem.cpp b/src/libtomahawk/aclsystem.cpp index 7b7d70e1f..15c75a407 100644 --- a/src/libtomahawk/aclsystem.cpp +++ b/src/libtomahawk/aclsystem.cpp @@ -124,7 +124,7 @@ void ACLSystem::authorizePath( const QString& dbid, const QString& path, ACLSystem::ACL type ) { TomahawkSettings *s = TomahawkSettings::instance(); - if( !s->scannerPath().contains( path ) ) + if( !s->scannerPaths().contains( path ) ) { qDebug() << "path selected is not in our scanner path!"; return; diff --git a/src/libtomahawk/database/database.cpp b/src/libtomahawk/database/database.cpp index 691a9db3b..4436f6cfa 100644 --- a/src/libtomahawk/database/database.cpp +++ b/src/libtomahawk/database/database.cpp @@ -32,6 +32,7 @@ Database::instance() Database::Database( const QString& dbname, QObject* parent ) : QObject( parent ) + , m_ready( false ) , m_impl( new DatabaseImpl( dbname, this ) ) , m_workerRW( new DatabaseWorker( m_impl, this, true ) ) { @@ -39,6 +40,7 @@ Database::Database( const QString& dbname, QObject* parent ) connect( m_impl, SIGNAL( indexReady() ), SIGNAL( indexReady() ) ); connect( m_impl, SIGNAL( indexReady() ), SIGNAL( ready() ) ); + connect( m_impl, SIGNAL( indexReady() ), SLOT( setIsReadyTrue() ) ); m_workerRW->start(); } diff --git a/src/libtomahawk/database/database.h b/src/libtomahawk/database/database.h index 4dad4882e..a6890fbb6 100644 --- a/src/libtomahawk/database/database.h +++ b/src/libtomahawk/database/database.h @@ -52,6 +52,8 @@ public: const bool indexReady() const { return m_indexReady; } void loadIndex(); + + bool isReady() const { return m_ready; } signals: void indexReady(); // search index @@ -63,7 +65,11 @@ signals: public slots: void enqueue( QSharedPointer lc ); +private slots: + void setIsReadyTrue() { m_ready = true; } + private: + bool m_ready; DatabaseImpl* m_impl; DatabaseWorker* m_workerRW; QHash< QString, DatabaseWorker* > m_workers; diff --git a/src/libtomahawk/database/databaseimpl.h b/src/libtomahawk/database/databaseimpl.h index 579850b7e..e2965f553 100644 --- a/src/libtomahawk/database/databaseimpl.h +++ b/src/libtomahawk/database/databaseimpl.h @@ -84,6 +84,8 @@ signals: public slots: private: + bool m_ready; + bool updateSchema( int currentver ); QSqlDatabase db; diff --git a/src/libtomahawk/tomahawksettings.cpp b/src/libtomahawk/tomahawksettings.cpp index 82d04d75c..bdbe3c171 100644 --- a/src/libtomahawk/tomahawksettings.cpp +++ b/src/libtomahawk/tomahawksettings.cpp @@ -66,29 +66,50 @@ TomahawkSettings::~TomahawkSettings() QStringList -TomahawkSettings::scannerPath() const +TomahawkSettings::scannerPaths() { + //FIXME: After enough time, remove this hack (and make const) #ifndef TOMAHAWK_HEADLESS - return value( "scannerpath", QDesktopServices::storageLocation( QDesktopServices::MusicLocation ) ).toStringList(); + if( value( "scannerpaths" ).isNull() ) + setValue( "scannerpaths", value( "scannerpath" ) ); + return value( "scannerpaths", QDesktopServices::storageLocation( QDesktopServices::MusicLocation ) ).toStringList(); #else - return value( "scannerpath", "" ).toStringList(); + if( value( "scannerpaths" ).isNull() ) + setValue( "scannerpaths", value( "scannerpath" ) ); + return value( "scannerpaths", "" ).toStringList(); #endif } void -TomahawkSettings::setScannerPath( const QStringList& path ) +TomahawkSettings::setScannerPaths( const QStringList& paths ) { - setValue( "scannerpath", path ); + setValue( "scannerpaths", paths ); } bool -TomahawkSettings::hasScannerPath() const +TomahawkSettings::hasScannerPaths() const { - return contains( "scannerpath" ); + //FIXME: After enough time, remove this hack + return contains( "scannerpaths" ) || contains( "scannerpath" ); } + +bool +TomahawkSettings::watchForChanges() const +{ + return value( "watchForChanges", true ).toBool(); +} + + +void +TomahawkSettings::setWatchForChanges( bool watch ) +{ + setValue( "watchForChanges", watch ); +} + + void TomahawkSettings::setAcceptedLegalWarning( bool accept ) { diff --git a/src/libtomahawk/tomahawksettings.h b/src/libtomahawk/tomahawksettings.h index cc2b2923f..42daa91e3 100644 --- a/src/libtomahawk/tomahawksettings.h +++ b/src/libtomahawk/tomahawksettings.h @@ -41,9 +41,12 @@ public: void applyChanges() { emit changed(); } /// General settings - QStringList scannerPath() const; /// QDesktopServices::MusicLocation by default - void setScannerPath( const QStringList& path ); - bool hasScannerPath() const; + QStringList scannerPaths(); /// QDesktopServices::MusicLocation by default + void setScannerPaths( const QStringList& paths ); + bool hasScannerPaths() const; + + bool watchForChanges() const; + void setWatchForChanges( bool watch ); bool acceptedLegalWarning() const; void setAcceptedLegalWarning( bool accept ); diff --git a/src/musicscanner.cpp b/src/musicscanner.cpp index fe8eb29f6..1662b452c 100644 --- a/src/musicscanner.cpp +++ b/src/musicscanner.cpp @@ -31,14 +31,16 @@ using namespace Tomahawk; void DirLister::go() { - scanDir( m_dir, 0 ); + foreach( QString dir, m_dirs ) + scanDir( QDir( dir, 0 ), 0, ( m_recursive ? DirLister::Recursive : DirLister::NonRecursive ) ); emit finished( m_newdirmtimes ); } void -DirLister::scanDir( QDir dir, int depth ) +DirLister::scanDir( QDir dir, int depth, DirLister::Mode mode ) { + qDebug() << "DirLister::scanDir scanning: " << dir.absolutePath(); QFileInfoList dirs; const uint mtime = QFileInfo( dir.absolutePath() ).lastModified().toUTC().toTime_t(); m_newdirmtimes.insert( dir.absolutePath(), mtime ); @@ -65,14 +67,18 @@ DirLister::scanDir( QDir dir, int depth ) foreach( const QFileInfo& di, dirs ) { - scanDir( di.absoluteFilePath(), depth + 1 ); + if( mode == DirLister::Recursive || !m_dirmtimes.contains( di.absolutePath() ) ) + scanDir( di.absoluteFilePath(), depth + 1, DirLister::Recursive ); + else //should be the non-recursive case since the second test above should only happen with a new dir + scanDir( di.absoluteFilePath(), depth + 1, DirLister::MTimeOnly ); } } -MusicScanner::MusicScanner( const QStringList& dirs, quint32 bs ) +MusicScanner::MusicScanner( const QStringList& dirs, bool recursive, quint32 bs ) : QObject() , m_dirs( dirs ) + , m_recursive( recursive ) , m_batchsize( bs ) , m_dirLister( 0 ) , m_dirListerThreadController( 0 ) @@ -150,8 +156,7 @@ MusicScanner::scan() m_dirListerThreadController = new QThread( this ); - //FIXME: MULTIPLECOLLECTIONDIRS - m_dirLister = new DirLister( QDir( m_dirs.first(), 0 ), m_dirmtimes ); + m_dirLister = new DirLister( m_dirs, m_dirmtimes, m_recursive ); m_dirLister->moveToThread( m_dirListerThreadController ); connect( m_dirLister, SIGNAL( fileToScan( QFileInfo ) ), diff --git a/src/musicscanner.h b/src/musicscanner.h index dd2725334..77fb62071 100644 --- a/src/musicscanner.h +++ b/src/musicscanner.h @@ -39,8 +39,15 @@ class DirLister : public QObject Q_OBJECT public: - DirLister( QDir d, QMap& mtimes ) - : QObject(), m_dir( d ), m_dirmtimes( mtimes ) + + enum Mode { + NonRecursive, + Recursive, + MTimeOnly + }; + + DirLister( QStringList dirs, QMap& mtimes, bool recursive ) + : QObject(), m_dirs( dirs ), m_dirmtimes( mtimes ), m_recursive( recursive ) { qDebug() << Q_FUNC_INFO; } @@ -56,11 +63,13 @@ signals: private slots: void go(); - void scanDir( QDir dir, int depth ); + void scanDir( QDir dir, int depth, DirLister::Mode mode ); private: - QDir m_dir; + QStringList m_dirs; QMap m_dirmtimes; + bool m_recursive; + QMap m_newdirmtimes; }; @@ -69,7 +78,7 @@ class MusicScanner : public QObject Q_OBJECT public: - MusicScanner( const QStringList& dirs, quint32 bs = 0 ); + MusicScanner( const QStringList& dirs, bool recursive = true, quint32 bs = 0 ); ~MusicScanner(); signals: @@ -105,6 +114,7 @@ private: QMap m_newdirmtimes; QList m_scannedfiles; + bool m_recursive; quint32 m_batchsize; DirLister* m_dirLister; diff --git a/src/scanmanager.cpp b/src/scanmanager.cpp index e1d5f028c..5fda0a7ec 100644 --- a/src/scanmanager.cpp +++ b/src/scanmanager.cpp @@ -45,20 +45,33 @@ ScanManager::ScanManager( QObject* parent ) : QObject( parent ) , m_scanner( 0 ) , m_musicScannerThreadController( 0 ) + , m_currScannerPaths() , m_dirWatcher( 0 ) + , m_queuedScanTimer( 0 ) + , m_deferredScanTimer( 0 ) + , m_queuedChangedDirs() + , m_deferredDirs() { s_instance = this; - m_dirWatcher = new QFileSystemWatcher( parent ); + m_queuedScanTimer = new QTimer( this ); + m_queuedScanTimer->setSingleShot( true ); + m_deferredScanTimer = new QTimer( this ); + m_deferredScanTimer->setSingleShot( false ); + m_deferredScanTimer->setInterval( 1000 ); + m_dirWatcher = new QFileSystemWatcher( this ); connect( TomahawkSettings::instance(), SIGNAL( changed() ), SLOT( onSettingsChanged() ) ); + connect( m_queuedScanTimer, SIGNAL( timeout() ), SLOT( queuedScanTimeout() ) ); + connect( m_deferredScanTimer, SIGNAL( timeout() ), SLOT( deferredScanTimeout() ) ); connect( m_dirWatcher, SIGNAL( directoryChanged( const QString & ) ), SLOT( handleChangedDir( const QString & ) ) ); - if ( TomahawkSettings::instance()->hasScannerPath() ) - m_currScannerPath = TomahawkSettings::instance()->scannerPath(); + if ( TomahawkSettings::instance()->hasScannerPaths() ) + m_currScannerPaths = TomahawkSettings::instance()->scannerPaths(); qDebug() << "loading initial directories to watch"; QTimer::singleShot( 1000, this, SLOT( startupWatchPaths() ) ); + m_deferredScanTimer->start(); } @@ -91,14 +104,18 @@ ScanManager::~ScanManager() void ScanManager::onSettingsChanged() { - if ( TomahawkSettings::instance()->hasScannerPath() && - m_currScannerPath != TomahawkSettings::instance()->scannerPath() ) + if ( TomahawkSettings::instance()->hasScannerPaths() && + m_currScannerPaths != TomahawkSettings::instance()->scannerPaths() ) { - m_currScannerPath = TomahawkSettings::instance()->scannerPath(); + m_currScannerPaths = TomahawkSettings::instance()->scannerPaths(); m_dirWatcher->removePaths( m_dirWatcher->directories() ); - m_dirWatcher->addPaths( m_currScannerPath ); - runManualScan( m_currScannerPath ); + m_dirWatcher->addPaths( m_currScannerPaths ); + runManualScan( m_currScannerPaths ); } + + if( TomahawkSettings::instance()->watchForChanges() && + !m_queuedChangedDirs.isEmpty() ) + runManualScan( m_queuedChangedDirs, false ); } @@ -107,21 +124,21 @@ ScanManager::startupWatchPaths() { qDebug() << Q_FUNC_INFO; - if( !Database::instance() ) + if( !Database::instance() || ( Database::instance() && !Database::instance()->isReady() ) ) { QTimer::singleShot( 1000, this, SLOT( startupWatchPaths() ) ); return; } - DatabaseCommand_DirMtimes* cmd = new DatabaseCommand_DirMtimes( m_currScannerPath ); - connect( cmd, SIGNAL( done( QMap ) ), - SLOT( setInitialPaths( QMap ) ) ); - Database::instance()->enqueue( QSharedPointer(cmd) ); + DatabaseCommand_DirMtimes* cmd = new DatabaseCommand_DirMtimes( m_currScannerPaths ); + connect( cmd, SIGNAL( done( QMap< QString, unsigned int > ) ), + SLOT( setInitialPaths( QMap< QString, unsigned int > ) ) ); + Database::instance()->enqueue( QSharedPointer< DatabaseCommand >( cmd ) ); } void -ScanManager::setInitialPaths( QMap pathMap ) +ScanManager::setInitialPaths( QMap< QString, unsigned int > pathMap ) { qDebug() << Q_FUNC_INFO; foreach( QString path, pathMap.keys() ) @@ -129,27 +146,45 @@ ScanManager::setInitialPaths( QMap pathMap ) qDebug() << "Adding " << path << " to watcher"; m_dirWatcher->addPath( path ); } + runManualScan( TomahawkSettings::instance()->scannerPaths() ); } void -ScanManager::runManualScan( const QStringList& path ) +ScanManager::runManualScan( const QStringList& paths, bool recursive ) { qDebug() << Q_FUNC_INFO; if ( !m_musicScannerThreadController && !m_scanner ) //still running if these are not zero { m_musicScannerThreadController = new QThread( this ); - m_scanner = new MusicScanner( path ); + QStringList allPaths = paths; + foreach( QString path, m_deferredDirs[recursive] ) + { + if( !allPaths.contains( path ) ) + allPaths << path; + } + m_scanner = new MusicScanner( paths, recursive ); m_scanner->moveToThread( m_musicScannerThreadController ); connect( m_scanner, SIGNAL( finished() ), SLOT( scannerFinished() ) ); connect( m_scanner, SIGNAL( addWatchedDirs( const QStringList & ) ), SLOT( addWatchedDirs( const QStringList & ) ) ); connect( m_scanner, SIGNAL( removeWatchedDir( const QString & ) ), SLOT( removeWatchedDir( const QString & ) ) ); m_musicScannerThreadController->start( QThread::IdlePriority ); QMetaObject::invokeMethod( m_scanner, "startScan" ); + m_deferredDirs[recursive].clear(); } else - qDebug() << "Could not run manual scan, old scan still running"; + { + qDebug() << "Could not run manual scan, old scan still running; deferring paths"; + foreach( QString path, paths ) + { + if( !m_deferredDirs[recursive].contains( path ) ) + { + qDebug() << "Deferring path " << path; + m_deferredDirs[recursive] << path; + } + } + } } void @@ -180,6 +215,34 @@ ScanManager::handleChangedDir( const QString& path ) { qDebug() << Q_FUNC_INFO; qDebug() << "Dir changed: " << path; + m_queuedChangedDirs << path; + if( TomahawkSettings::instance()->watchForChanges() ) + m_queuedScanTimer->start( 10000 ); +} + + +void +ScanManager::queuedScanTimeout() +{ + qDebug() << Q_FUNC_INFO; + runManualScan( m_queuedChangedDirs, false ); + m_queuedChangedDirs.clear(); +} + + +void +ScanManager::deferredScanTimeout() +{ + if( !m_deferredDirs[true].isEmpty() ) + { + qDebug() << "Running scan for deferred recursive paths"; + runManualScan( m_deferredDirs[true], true ); + } + else if( !m_deferredDirs[false].isEmpty() ) + { + qDebug() << "Running scan for deferred non-recursive paths"; + runManualScan( m_deferredDirs[false], false ); + } } diff --git a/src/scanmanager.h b/src/scanmanager.h index 232627058..fdec0cf15 100644 --- a/src/scanmanager.h +++ b/src/scanmanager.h @@ -19,15 +19,17 @@ #ifndef SCANMANAGER_H #define SCANMANAGER_H +#include +#include #include #include -#include #include "dllmacro.h" class MusicScanner; class QThread; class QFileSystemWatcher; +class QTimer; class ScanManager : public QObject { @@ -38,17 +40,16 @@ public: explicit ScanManager( QObject* parent = 0 ); virtual ~ScanManager(); - - void runManualScan( const QStringList& path ); signals: void finished(); public slots: + void runManualScan( const QStringList& paths, bool recursive = true ); void handleChangedDir( const QString& path ); void addWatchedDirs( const QStringList& paths ); void removeWatchedDir( const QString& path ); - void setInitialPaths( QMap pathMap ); + void setInitialPaths( QMap< QString, unsigned int > pathMap ); private slots: void scannerQuit(); @@ -56,6 +57,8 @@ private slots: void scannerDestroyed( QObject* scanner ); void startupWatchPaths(); + void queuedScanTimeout(); + void deferredScanTimeout(); void onSettingsChanged(); @@ -64,8 +67,13 @@ private: MusicScanner* m_scanner; QThread* m_musicScannerThreadController; - QStringList m_currScannerPath; + QStringList m_currScannerPaths; QFileSystemWatcher* m_dirWatcher; + + QTimer* m_queuedScanTimer; + QTimer* m_deferredScanTimer; + QStringList m_queuedChangedDirs; + QHash< bool, QStringList > m_deferredDirs; }; #endif diff --git a/src/settingsdialog.cpp b/src/settingsdialog.cpp index 58fbd1f96..b8808b621 100644 --- a/src/settingsdialog.cpp +++ b/src/settingsdialog.cpp @@ -87,7 +87,8 @@ SettingsDialog::SettingsDialog( QWidget *parent ) // MUSIC SCANNER //FIXME: MULTIPLECOLLECTIONDIRS - ui->lineEditMusicPath->setText( s->scannerPath().first() ); + ui->lineEditMusicPath->setText( s->scannerPaths().first() ); + ui->checkBoxWatchForChanges->setChecked( s->watchForChanges() ); // LAST FM ui->checkBoxEnableLastfm->setChecked( s->scrobblingEnabled() ); @@ -134,7 +135,8 @@ SettingsDialog::~SettingsDialog() s->setExternalHostname( ui->staticHostName->text() ); s->setExternalPort( ui->staticPort->value() ); - s->setScannerPath( QStringList( ui->lineEditMusicPath->text() ) ); + s->setScannerPaths( QStringList( ui->lineEditMusicPath->text() ) ); + s->setWatchForChanges( ui->checkBoxWatchForChanges->isChecked() ); s->setScrobblingEnabled( ui->checkBoxEnableLastfm->isChecked() ); s->setLastFmUsername( ui->lineEditLastfmUsername->text() ); diff --git a/src/settingsdialog.ui b/src/settingsdialog.ui index c3f302da8..0558568b6 100644 --- a/src/settingsdialog.ui +++ b/src/settingsdialog.ui @@ -66,7 +66,7 @@ - e.g. user@example.com + e.g. user@example.com
@@ -454,6 +454,19 @@ + + + + + 0 + 0 + + + + Watch for changes + + + diff --git a/src/tomahawkapp.cpp b/src/tomahawkapp.cpp index 46c002c4a..a148d9a02 100644 --- a/src/tomahawkapp.cpp +++ b/src/tomahawkapp.cpp @@ -285,7 +285,7 @@ TomahawkApp::TomahawkApp( int& argc, char *argv[] ) } #ifndef TOMAHAWK_HEADLESS - if ( !TomahawkSettings::instance()->hasScannerPath() ) + if ( !TomahawkSettings::instance()->hasScannerPaths() ) { m_mainwindow->showSettingsDialog(); } diff --git a/src/tomahawkwindow.cpp b/src/tomahawkwindow.cpp index 843d8b525..15b355d28 100644 --- a/src/tomahawkwindow.cpp +++ b/src/tomahawkwindow.cpp @@ -319,8 +319,8 @@ TomahawkWindow::showSettingsDialog() void TomahawkWindow::updateCollectionManually() { - if ( TomahawkSettings::instance()->hasScannerPath() ) - ScanManager::instance()->runManualScan( TomahawkSettings::instance()->scannerPath() ); + if ( TomahawkSettings::instance()->hasScannerPaths() ) + ScanManager::instance()->runManualScan( TomahawkSettings::instance()->scannerPaths() ); } From 569c23a30e819ca75aa99a93c79f423bfa8f3283 Mon Sep 17 00:00:00 2001 From: Jeff Mitchell Date: Sat, 2 Apr 2011 00:56:36 -0400 Subject: [PATCH 227/329] Whitespacing --- src/scanmanager.cpp | 3 +++ 1 file changed, 3 insertions(+) diff --git a/src/scanmanager.cpp b/src/scanmanager.cpp index 5fda0a7ec..5f848966e 100644 --- a/src/scanmanager.cpp +++ b/src/scanmanager.cpp @@ -187,6 +187,7 @@ ScanManager::runManualScan( const QStringList& paths, bool recursive ) } } + void ScanManager::addWatchedDirs( const QStringList& paths ) { @@ -202,6 +203,7 @@ ScanManager::addWatchedDirs( const QStringList& paths ) } } + void ScanManager::removeWatchedDir( const QString& path ) { @@ -210,6 +212,7 @@ ScanManager::removeWatchedDir( const QString& path ) m_dirWatcher->removePath( path ); } + void ScanManager::handleChangedDir( const QString& path ) { From 2214b750be0316f86e280c6a851ccc244b767a36 Mon Sep 17 00:00:00 2001 From: Jeff Mitchell Date: Sat, 2 Apr 2011 00:58:43 -0400 Subject: [PATCH 228/329] Changelogify --- ChangeLog | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/ChangeLog b/ChangeLog index c3a791448..6cf41a2ca 100644 --- a/ChangeLog +++ b/ChangeLog @@ -1,3 +1,8 @@ +Version 0.1.0: + * Watch folders for changes and automatically update your collection. This + is on by default; you can turn it off on the Local Music tab in the + settings dialog. + Version 0.0.3: * Fix crashes in Twitter authentication. For reals now. * Properly honor the chosen port number if a static host and port are From 01f2fe6adc3b1932d0829b2f038d68782b8616b1 Mon Sep 17 00:00:00 2001 From: Jeff Mitchell Date: Sat, 2 Apr 2011 01:17:48 -0400 Subject: [PATCH 229/329] Fix missing condition, wrong method, and put more info in the ChangeLog --- ChangeLog | 5 ++++- src/musicscanner.cpp | 10 +++++----- 2 files changed, 9 insertions(+), 6 deletions(-) diff --git a/ChangeLog b/ChangeLog index 6cf41a2ca..f3363512e 100644 --- a/ChangeLog +++ b/ChangeLog @@ -1,7 +1,10 @@ Version 0.1.0: * Watch folders for changes and automatically update your collection. This is on by default; you can turn it off on the Local Music tab in the - settings dialog. + settings dialog. Note that this triggers only on files or folders being + added to or removed from folders; it is not watch individual files as + most OSes can't support enough file watches to handle a normal-sized + music collection. Version 0.0.3: * Fix crashes in Twitter authentication. For reals now. diff --git a/src/musicscanner.cpp b/src/musicscanner.cpp index 1662b452c..6ccbf14ae 100644 --- a/src/musicscanner.cpp +++ b/src/musicscanner.cpp @@ -40,7 +40,7 @@ DirLister::go() void DirLister::scanDir( QDir dir, int depth, DirLister::Mode mode ) { - qDebug() << "DirLister::scanDir scanning: " << dir.absolutePath(); + qDebug() << "DirLister::scanDir scanning: " << dir.absolutePath() << " with mode " << mode; QFileInfoList dirs; const uint mtime = QFileInfo( dir.absolutePath() ).lastModified().toUTC().toTime_t(); m_newdirmtimes.insert( dir.absolutePath(), mtime ); @@ -49,9 +49,9 @@ DirLister::scanDir( QDir dir, int depth, DirLister::Mode mode ) { // dont scan this dir, unchanged since last time. } - else + else if( mode != DirLister::MTimeOnly ) { - if ( m_dirmtimes.contains( dir.absolutePath() ) ) + if( m_dirmtimes.contains( dir.absolutePath() ) ) Database::instance()->enqueue( QSharedPointer( new DatabaseCommand_DeleteFiles( dir, SourceList::instance()->getLocal() ) ) ); dir.setFilter( QDir::Files | QDir::Readable | QDir::NoDotAndDotDot ); @@ -64,10 +64,10 @@ DirLister::scanDir( QDir dir, int depth, DirLister::Mode mode ) } dir.setFilter( QDir::Dirs | QDir::Readable | QDir::NoDotAndDotDot ); dirs = dir.entryInfoList(); - + foreach( const QFileInfo& di, dirs ) { - if( mode == DirLister::Recursive || !m_dirmtimes.contains( di.absolutePath() ) ) + if( mode == DirLister::Recursive || !m_dirmtimes.contains( di.absoluteFilePath() ) ) scanDir( di.absoluteFilePath(), depth + 1, DirLister::Recursive ); else //should be the non-recursive case since the second test above should only happen with a new dir scanDir( di.absoluteFilePath(), depth + 1, DirLister::MTimeOnly ); From 6af30bfd2f4c7ee15fa2be39ec655cd827f7732f Mon Sep 17 00:00:00 2001 From: Christian Muehlhaeuser Date: Sat, 2 Apr 2011 06:25:16 +0200 Subject: [PATCH 230/329] * Cleaned up style. --- src/main.cpp | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/src/main.cpp b/src/main.cpp index 02a3ae80d..b5c84dae6 100644 --- a/src/main.cpp +++ b/src/main.cpp @@ -39,6 +39,7 @@ main( int argc, char *argv[] ) AEInstallEventHandler( 'GURL', 'GURL', h, 0, false ); #endif + try { TomahawkApp a( argc, argv ); @@ -46,8 +47,8 @@ main( int argc, char *argv[] ) QString locale = QLocale::system().name(); QTranslator translator; - translator.load(QString(":/lang/tomahawk_") + locale); - a.installTranslator(&translator); + translator.load( QString( ":/lang/tomahawk_" ) + locale ); + a.installTranslator( &translator ); return a.exec(); } catch( const std::runtime_error& e ) From 0a593aeb0556a9e071d74ddb78db531395e1d5be Mon Sep 17 00:00:00 2001 From: Christian Muehlhaeuser Date: Sat, 2 Apr 2011 10:24:20 +0200 Subject: [PATCH 231/329] * Only keep track of the 50 most recent tracks in WelcomeWidget. Don't auto-resolve playback-logs any longer. --- .../database/databasecommand_logplayback.cpp | 3 ++- src/libtomahawk/playlist/playlistmodel.cpp | 10 +++++--- src/libtomahawk/playlist/playlistmodel.h | 1 + src/libtomahawk/playlist/trackmodel.cpp | 14 +++++++++++ src/libtomahawk/playlist/trackmodel.h | 2 ++ src/libtomahawk/widgets/welcomewidget.cpp | 24 +++++++++++++++++-- src/libtomahawk/widgets/welcomewidget.h | 3 +++ 7 files changed, 51 insertions(+), 6 deletions(-) diff --git a/src/libtomahawk/database/databasecommand_logplayback.cpp b/src/libtomahawk/database/databasecommand_logplayback.cpp index 9254ea979..6443b2216 100644 --- a/src/libtomahawk/database/databasecommand_logplayback.cpp +++ b/src/libtomahawk/database/databasecommand_logplayback.cpp @@ -44,7 +44,8 @@ DatabaseCommand_LogPlayback::postCommitHook() connect( this, SIGNAL( trackPlayed( Tomahawk::query_ptr ) ), source().data(), SLOT( onPlaybackFinished( Tomahawk::query_ptr ) ), Qt::QueuedConnection ); - Tomahawk::query_ptr q = Tomahawk::Query::get( m_artist, m_track, QString(), uuid() ); + // do not auto resolve this track + Tomahawk::query_ptr q = Tomahawk::Query::get( m_artist, m_track, QString() ); if ( m_action == Finished ) { diff --git a/src/libtomahawk/playlist/playlistmodel.cpp b/src/libtomahawk/playlist/playlistmodel.cpp index 8e4d24ae6..2ab23bf51 100644 --- a/src/libtomahawk/playlist/playlistmodel.cpp +++ b/src/libtomahawk/playlist/playlistmodel.cpp @@ -396,12 +396,16 @@ PlaylistModel::playlistEntries() const } +void +PlaylistModel::remove( unsigned int row, bool moreToCome ) +{ + removeIndex( index( row, 0, QModelIndex() ), moreToCome ); +} + + void PlaylistModel::removeIndex( const QModelIndex& index, bool moreToCome ) { - if ( isReadOnly() ) - return; - TrackModel::removeIndex( index ); if ( !moreToCome && !m_playlist.isNull() ) diff --git a/src/libtomahawk/playlist/playlistmodel.h b/src/libtomahawk/playlist/playlistmodel.h index e69bf59e1..bc743a18a 100644 --- a/src/libtomahawk/playlist/playlistmodel.h +++ b/src/libtomahawk/playlist/playlistmodel.h @@ -62,6 +62,7 @@ public: void insert( unsigned int row, const Tomahawk::query_ptr& query ); + void remove( unsigned int row, bool moreToCome = false ); virtual void removeIndex( const QModelIndex& index, bool moreToCome = false ); signals: diff --git a/src/libtomahawk/playlist/trackmodel.cpp b/src/libtomahawk/playlist/trackmodel.cpp index eceadd401..e3fd2083a 100644 --- a/src/libtomahawk/playlist/trackmodel.cpp +++ b/src/libtomahawk/playlist/trackmodel.cpp @@ -26,6 +26,7 @@ #include "utils/tomahawkutils.h" #include "album.h" +#include "pipeline.h" using namespace Tomahawk; @@ -370,3 +371,16 @@ TrackModel::onPlaybackStopped() oldEntry->setIsPlaying( false ); } } + + +void +TrackModel::ensureResolved() +{ + for( int i = 0; i < rowCount( QModelIndex() ); i++ ) + { + query_ptr query = itemFromIndex( index( i, 0, QModelIndex() ) )->query(); + + if ( !query->numResults() ) + Pipeline::instance()->resolve( query ); + } +} diff --git a/src/libtomahawk/playlist/trackmodel.h b/src/libtomahawk/playlist/trackmodel.h index d9cec03d8..b3a5e0c24 100644 --- a/src/libtomahawk/playlist/trackmodel.h +++ b/src/libtomahawk/playlist/trackmodel.h @@ -76,6 +76,8 @@ public: virtual PlaylistInterface::RepeatMode repeatMode() const { return PlaylistInterface::NoRepeat; } virtual bool shuffled() const { return false; } + virtual void ensureResolved(); + virtual void append( const Tomahawk::query_ptr& query ) = 0; PlItem* itemFromIndex( const QModelIndex& index ) const; diff --git a/src/libtomahawk/widgets/welcomewidget.cpp b/src/libtomahawk/widgets/welcomewidget.cpp index cc4460895..d7ad62938 100644 --- a/src/libtomahawk/widgets/welcomewidget.cpp +++ b/src/libtomahawk/widgets/welcomewidget.cpp @@ -31,7 +31,9 @@ #include -#define FILTER_TIMEOUT 280 +#define HISTORY_TRACK_ITEMS 50 +#define HISTORY_PLAYLIST_ITEMS 10 +#define HISTORY_RESOLVING_TIMEOUT 2500 WelcomeWidget::WelcomeWidget( QWidget* parent ) @@ -46,7 +48,10 @@ WelcomeWidget::WelcomeWidget( QWidget* parent ) m_tracksModel = new PlaylistModel( ui->tracksView ); ui->tracksView->setModel( m_tracksModel ); - m_tracksModel->loadHistory( Tomahawk::source_ptr() ); + m_tracksModel->loadHistory( Tomahawk::source_ptr(), HISTORY_TRACK_ITEMS ); + + m_timer = new QTimer( this ); + connect( m_timer, SIGNAL( timeout() ), SLOT( checkQueries() ) ); connect( SourceList::instance(), SIGNAL( sourceAdded( Tomahawk::source_ptr ) ), SLOT( onSourceAdded( Tomahawk::source_ptr ) ) ); @@ -96,10 +101,25 @@ WelcomeWidget::onSourceAdded( const Tomahawk::source_ptr& source ) } +void +WelcomeWidget::checkQueries() +{ + m_timer->stop(); + m_tracksModel->ensureResolved(); +} + + void WelcomeWidget::onPlaybackFinished( const Tomahawk::query_ptr& query ) { m_tracksModel->insert( 0, query ); + + if ( m_tracksModel->trackCount() > HISTORY_TRACK_ITEMS ) + m_tracksModel->remove( HISTORY_TRACK_ITEMS ); + + if ( m_timer->isActive() ) + m_timer->stop(); + m_timer->start( HISTORY_RESOLVING_TIMEOUT ); } diff --git a/src/libtomahawk/widgets/welcomewidget.h b/src/libtomahawk/widgets/welcomewidget.h index 9ec4e249c..036e3d5ca 100644 --- a/src/libtomahawk/widgets/welcomewidget.h +++ b/src/libtomahawk/widgets/welcomewidget.h @@ -124,10 +124,13 @@ private slots: void onPlaylistActivated( QListWidgetItem* item ); void onPlaybackFinished( const Tomahawk::query_ptr& query ); + void checkQueries(); + private: Ui::WelcomeWidget *ui; PlaylistModel* m_tracksModel; + QTimer* m_timer; }; #endif // WELCOMEWIDGET_H From 5255fd2a7861490a68ea7cc477b585e2f637b4c2 Mon Sep 17 00:00:00 2001 From: Christian Muehlhaeuser Date: Sat, 2 Apr 2011 10:24:48 +0200 Subject: [PATCH 232/329] * Properly thread QtScriptResolver. --- src/resolvers/qtscriptresolver.cpp | 126 +++++++++++++++++++++-------- src/resolvers/qtscriptresolver.h | 39 +++++++-- 2 files changed, 125 insertions(+), 40 deletions(-) diff --git a/src/resolvers/qtscriptresolver.cpp b/src/resolvers/qtscriptresolver.cpp index d4c0cc5f5..a19147b14 100644 --- a/src/resolvers/qtscriptresolver.cpp +++ b/src/resolvers/qtscriptresolver.cpp @@ -27,19 +27,86 @@ QtScriptResolver::QtScriptResolver( const QString& scriptPath ) : Tomahawk::ExternalResolver( scriptPath ) - , m_engine( new ScriptEngine( this ) ) - , m_thread( new QThread( this ) ) , m_ready( false ) , m_stopped( false ) { qDebug() << Q_FUNC_INFO << scriptPath; + m_thread = new ScriptThread( scriptPath, this ); + connect( m_thread, SIGNAL( engineFound( QString, unsigned int, unsigned int, unsigned int ) ), + SLOT( onEngineFound( QString, unsigned int, unsigned int, unsigned int ) ) ); + m_thread->start(); - QFile scriptFile( scriptPath ); + connect( this, SIGNAL( destroyed( QObject* ) ), m_thread, SLOT( deleteLater() ) ); +} + + +QtScriptResolver::~QtScriptResolver() +{ + Tomahawk::Pipeline::instance()->removeResolver( this ); + delete m_thread; +} + + +void +QtScriptResolver::resolve( const Tomahawk::query_ptr& query ) +{ + m_thread->resolve( query ); +} + + +void +QtScriptResolver::onEngineFound( const QString& name, unsigned int weight, unsigned int timeout, unsigned int preference ) +{ + m_name = name; + m_weight = weight; + m_timeout = timeout; + m_preference = preference; + + qDebug() << "QTSCRIPT" << filePath() << "READY," << endl + << "name" << m_name << endl + << "weight" << m_weight << endl + << "timeout" << m_timeout << endl + << "preference" << m_preference; + + m_ready = true; + Tomahawk::Pipeline::instance()->addResolver( this ); +} + + +ScriptThread::ScriptThread( const QString& scriptPath, QtScriptResolver* parent ) + : QThread() + , m_parent( parent ) + , m_scriptPath( scriptPath ) +{ + moveToThread( this ); +} + + +void +ScriptThread::resolve( const Tomahawk::query_ptr& query ) +{ + m_engine->resolve( query ); +} + + +void +ScriptThread::run() +{ + QTimer::singleShot( 0, this, SLOT( initEngine() ) ); + exec(); +} + + +void +ScriptThread::initEngine() +{ + m_engine = new ScriptEngine( m_parent, this ); + QFile scriptFile( m_scriptPath ); if ( !scriptFile.open( QIODevice::ReadOnly ) ) { - qDebug() << Q_FUNC_INFO << "Failed loading JavaScript resolver:" << scriptPath; + qDebug() << Q_FUNC_INFO << "Failed loading JavaScript resolver:" << m_scriptPath; deleteLater(); return; } @@ -48,43 +115,32 @@ QtScriptResolver::QtScriptResolver( const QString& scriptPath ) m_engine->mainFrame()->evaluateJavaScript( scriptFile.readAll() ); scriptFile.close(); + QString name; + unsigned int weight, preference, timeout; QVariantMap m = m_engine->mainFrame()->evaluateJavaScript( "getSettings();" ).toMap(); - m_name = m.value( "name" ).toString(); - m_weight = m.value( "weight", 0 ).toUInt(); - m_timeout = m.value( "timeout", 25 ).toUInt() * 1000; - m_preference = m.value( "preference", 0 ).toUInt(); + name = m.value( "name" ).toString(); + weight = m.value( "weight", 0 ).toUInt(); + timeout = m.value( "timeout", 25 ).toUInt() * 1000; + preference = m.value( "preference", 0 ).toUInt(); - qDebug() << "QTSCRIPT" << filePath() << "READY," << endl - << "name" << m_name << endl - << "weight" << m_weight << endl - << "timeout" << m_timeout << endl - << "preference" << m_preference; - - m_engine->moveToThread( m_thread ); - m_ready = true; - Tomahawk::Pipeline::instance()->addResolver( this ); - - connect( this, SIGNAL( destroyed( QObject* ) ), m_thread, SLOT( deleteLater() ) ); -} - - -QtScriptResolver::~QtScriptResolver() -{ - Tomahawk::Pipeline::instance()->removeResolver( this ); - delete m_engine; -} - - -void -QtScriptResolver::resolve( const Tomahawk::query_ptr& query ) -{ - QMetaObject::invokeMethod( m_engine, "resolve", Qt::QueuedConnection, Q_ARG( Tomahawk::query_ptr, query ) ); + qDebug() << Q_FUNC_INFO << name << weight << timeout << preference; + emit engineFound( name, weight, timeout, preference ); } void ScriptEngine::resolve( const Tomahawk::query_ptr& query ) { + if ( QThread::currentThread() != thread() ) + { +// qDebug() << "Reinvoking in correct thread:" << Q_FUNC_INFO; + QMetaObject::invokeMethod( this, "resolve", + Qt::QueuedConnection, + Q_ARG(Tomahawk::query_ptr, query) + ); + return; + } + qDebug() << Q_FUNC_INFO << query->toString(); QString eval = QString( "resolve( '%1', '%2', '%3', '%4' );" ) .arg( query->id().replace( "'", "\\'" ) ) @@ -113,9 +169,9 @@ ScriptEngine::resolve( const Tomahawk::query_ptr& query ) rp->setBitrate( m.value( "bitrate" ).toUInt() ); rp->setUrl( m.value( "url" ).toString() ); rp->setSize( m.value( "size" ).toUInt() ); - rp->setScore( m.value( "score" ).toFloat() * ( (float)m_parent->weight() / 100.0 ) ); + rp->setScore( m.value( "score" ).toFloat() * ( (float)m_resolver->weight() / 100.0 ) ); rp->setRID( uuid() ); - rp->setFriendlySource( m_parent->name() ); + rp->setFriendlySource( m_resolver->name() ); if ( m.contains( "year" ) ) { diff --git a/src/resolvers/qtscriptresolver.h b/src/resolvers/qtscriptresolver.h index 05b55cc71..7c69fe103 100644 --- a/src/resolvers/qtscriptresolver.h +++ b/src/resolvers/qtscriptresolver.h @@ -26,9 +26,11 @@ #include #include #include +#include #include #include +class ScriptThread; class QtScriptResolver; class ScriptEngine : public QWebPage @@ -36,9 +38,10 @@ class ScriptEngine : public QWebPage Q_OBJECT public: - explicit ScriptEngine( QtScriptResolver* parent ) + explicit ScriptEngine( QtScriptResolver* resolver, ScriptThread* parent ) : QWebPage( (QObject*)parent ) , m_parent( parent ) + , m_resolver( resolver ) {} public slots: @@ -54,9 +57,35 @@ protected: { qDebug() << "JAVASCRIPT ERROR:" << message << lineNumber << sourceID; } private: - QtScriptResolver* m_parent; + ScriptThread* m_parent; + QtScriptResolver* m_resolver; }; + +class ScriptThread : public QThread +{ +Q_OBJECT + +public: + ScriptThread( const QString& scriptPath, QtScriptResolver* parent ); + + void run(); + + virtual void resolve( const Tomahawk::query_ptr& query ); + +signals: + void engineFound( const QString& name, unsigned int weight, unsigned int timeout, unsigned int preference ); + +private slots: + void initEngine(); + +private: + ScriptEngine* m_engine; + QtScriptResolver* m_parent; + QString m_scriptPath; +}; + + class QtScriptResolver : public Tomahawk::ExternalResolver { Q_OBJECT @@ -76,12 +105,12 @@ public slots: signals: void finished(); - + private slots: + void onEngineFound( const QString& name, unsigned int weight, unsigned int timeout, unsigned int preference ); private: - ScriptEngine* m_engine; - QThread* m_thread; + ScriptThread* m_thread; QString m_name; unsigned int m_weight, m_preference, m_timeout; From ea744456c9d5e47e50359a1bcce5e06989b6860c Mon Sep 17 00:00:00 2001 From: Jeff Mitchell Date: Sat, 2 Apr 2011 11:07:04 -0400 Subject: [PATCH 233/329] Don't start scanning on a fresh config/db --- src/scanmanager.cpp | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/src/scanmanager.cpp b/src/scanmanager.cpp index 5f848966e..945d42987 100644 --- a/src/scanmanager.cpp +++ b/src/scanmanager.cpp @@ -146,7 +146,8 @@ ScanManager::setInitialPaths( QMap< QString, unsigned int > pathMap ) qDebug() << "Adding " << path << " to watcher"; m_dirWatcher->addPath( path ); } - runManualScan( TomahawkSettings::instance()->scannerPaths() ); + if( TomahawkSettings::instance()->hasScannerPaths() ) + runManualScan( TomahawkSettings::instance()->scannerPaths() ); } From d2e867b9eeaf5082ad288a5356eb753ffa3cc292 Mon Sep 17 00:00:00 2001 From: Leo Franchi Date: Sat, 2 Apr 2011 13:44:47 -0400 Subject: [PATCH 234/329] switch to using KDSingleApplicationGuard instead of QtUniqueApp as it's broken on windows. KDSingleApplicationGuard is from KDTools and this is the GPL-licensed version. --- include/tomahawk/tomahawkapp.h | 5 +- src/headlesscheck.h | 8 +- src/libtomahawk/CMakeLists.txt | 12 +- .../kdlockedsharedmemorypointer.cpp | 475 +++++++++++++ .../kdlockedsharedmemorypointer.h | 115 ++++ .../kdsharedmemorylocker.cpp | 40 ++ .../kdsharedmemorylocker.h | 36 + .../kdsingleapplicationguard.cpp | 622 ++++++++++++++++++ .../kdsingleapplicationguard.h | 74 +++ .../kdtoolsglobal.cpp | 32 + .../kdsingleapplicationguard/kdtoolsglobal.h | 113 ++++ .../kdsingleapplicationguard/license-gpl | 349 ++++++++++ .../kdsingleapplicationguard/pimpl_ptr.cpp | 203 ++++++ .../kdsingleapplicationguard/pimpl_ptr.h | 44 ++ src/libtomahawk/qtsingleapp/qtlocalpeer.cpp | 199 ------ src/libtomahawk/qtsingleapp/qtlocalpeer.h | 72 -- src/libtomahawk/qtsingleapp/qtlockedfile.cpp | 192 ------ src/libtomahawk/qtsingleapp/qtlockedfile.h | 96 --- .../qtsingleapp/qtlockedfile_unix.cpp | 114 ---- .../qtsingleapp/qtlockedfile_win.cpp | 208 ------ .../qtsingleapp/qtsingleapplication.cpp | 344 ---------- .../qtsingleapp/qtsingleapplication.h | 84 --- .../qtsingleapp/qtsinglecoreapplication.cpp | 148 ----- .../qtsingleapp/qtsinglecoreapplication.h | 66 -- src/main.cpp | 26 +- src/tomahawkapp.cpp | 18 +- 26 files changed, 2137 insertions(+), 1558 deletions(-) create mode 100644 src/libtomahawk/kdsingleapplicationguard/kdlockedsharedmemorypointer.cpp create mode 100644 src/libtomahawk/kdsingleapplicationguard/kdlockedsharedmemorypointer.h create mode 100644 src/libtomahawk/kdsingleapplicationguard/kdsharedmemorylocker.cpp create mode 100644 src/libtomahawk/kdsingleapplicationguard/kdsharedmemorylocker.h create mode 100644 src/libtomahawk/kdsingleapplicationguard/kdsingleapplicationguard.cpp create mode 100644 src/libtomahawk/kdsingleapplicationguard/kdsingleapplicationguard.h create mode 100644 src/libtomahawk/kdsingleapplicationguard/kdtoolsglobal.cpp create mode 100644 src/libtomahawk/kdsingleapplicationguard/kdtoolsglobal.h create mode 100644 src/libtomahawk/kdsingleapplicationguard/license-gpl create mode 100644 src/libtomahawk/kdsingleapplicationguard/pimpl_ptr.cpp create mode 100644 src/libtomahawk/kdsingleapplicationguard/pimpl_ptr.h delete mode 100644 src/libtomahawk/qtsingleapp/qtlocalpeer.cpp delete mode 100644 src/libtomahawk/qtsingleapp/qtlocalpeer.h delete mode 100644 src/libtomahawk/qtsingleapp/qtlockedfile.cpp delete mode 100644 src/libtomahawk/qtsingleapp/qtlockedfile.h delete mode 100644 src/libtomahawk/qtsingleapp/qtlockedfile_unix.cpp delete mode 100644 src/libtomahawk/qtsingleapp/qtlockedfile_win.cpp delete mode 100644 src/libtomahawk/qtsingleapp/qtsingleapplication.cpp delete mode 100644 src/libtomahawk/qtsingleapp/qtsingleapplication.h delete mode 100644 src/libtomahawk/qtsingleapp/qtsinglecoreapplication.cpp delete mode 100644 src/libtomahawk/qtsingleapp/qtsinglecoreapplication.h diff --git a/include/tomahawk/tomahawkapp.h b/include/tomahawk/tomahawkapp.h index bf83f6e86..7775a6528 100644 --- a/include/tomahawk/tomahawkapp.h +++ b/include/tomahawk/tomahawkapp.h @@ -40,6 +40,7 @@ #include "network/servent.h" #include "utils/tomahawkutils.h" +#include "kdsingleapplicationguard/kdsingleapplicationguard.h" class AudioEngine; class Database; @@ -98,9 +99,11 @@ public: // because QApplication::arguments() is expensive bool scrubFriendlyName() const { return m_scrubFriendlyName; } +public slots: + void instanceStarted( KDSingleApplicationGuard::Instance ); + private slots: void setupSIP(); - void messageReceived( const QString& ); private: void initLocalCollection(); diff --git a/src/headlesscheck.h b/src/headlesscheck.h index 189f1127e..f82fcea33 100644 --- a/src/headlesscheck.h +++ b/src/headlesscheck.h @@ -21,14 +21,14 @@ #ifdef ENABLE_HEADLESS -#define TOMAHAWK_APPLICATION QtSingleCoreApplication +#define TOMAHAWK_APPLICATION QCoreApplication #define TOMAHAWK_HEADLESS -#include "qtsingleapp/qtsingleapplication.h" +#include > #else -#define TOMAHAWK_APPLICATION QtSingleApplication -#include "qtsingleapp/qtsingleapplication.h" +#define TOMAHAWK_APPLICATION QApplication +#include #include "tomahawkwindow.h" #endif diff --git a/src/libtomahawk/CMakeLists.txt b/src/libtomahawk/CMakeLists.txt index b7f214428..c7f3ac4e2 100644 --- a/src/libtomahawk/CMakeLists.txt +++ b/src/libtomahawk/CMakeLists.txt @@ -146,8 +146,10 @@ set( libSources widgets/overlaywidget.cpp widgets/infowidgets/sourceinfowidget.cpp - qtsingleapp/qtlocalpeer.cpp - qtsingleapp/qtsingleapplication.cpp + kdsingleapplicationguard/kdsingleapplicationguard.cpp + kdsingleapplicationguard/kdsharedmemorylocker.cpp + kdsingleapplicationguard/kdtoolsglobal.cpp + kdsingleapplicationguard/kdlockedsharedmemorypointer.cpp ) set( libHeaders @@ -289,8 +291,10 @@ set( libHeaders widgets/overlaywidget.h widgets/infowidgets/sourceinfowidget.h - qtsingleapp/qtlocalpeer.h - qtsingleapp/qtsingleapplication.h + kdsingleapplicationguard/kdsingleapplicationguard.h + kdsingleapplicationguard/kdsharedmemorylocker.h + kdsingleapplicationguard/kdtoolsglobal.h + kdsingleapplicationguard/kdlockedsharedmemorypointer.h ) set( libHeaders_NoMOC diff --git a/src/libtomahawk/kdsingleapplicationguard/kdlockedsharedmemorypointer.cpp b/src/libtomahawk/kdsingleapplicationguard/kdlockedsharedmemorypointer.cpp new file mode 100644 index 000000000..e1fe10a4d --- /dev/null +++ b/src/libtomahawk/kdsingleapplicationguard/kdlockedsharedmemorypointer.cpp @@ -0,0 +1,475 @@ +#include "kdlockedsharedmemorypointer.h" + +#if QT_VERSION >= 0x040400 || defined( DOXYGEN_RUN ) +#ifndef QT_NO_SHAREDMEMORY + +namespace kdtools +{ +} +using namespace kdtools; + +KDLockedSharedMemoryPointerBase::KDLockedSharedMemoryPointerBase( QSharedMemory * m ) + : locker( m ), + mem( m ) +{ + +} + +KDLockedSharedMemoryPointerBase::KDLockedSharedMemoryPointerBase( QSharedMemory & m ) + : locker( &m ), + mem( &m ) +{ + +} + +KDLockedSharedMemoryPointerBase::~KDLockedSharedMemoryPointerBase() {} + +void * KDLockedSharedMemoryPointerBase::get() { + return mem ? mem->data() : 0 ; +} + +const void * KDLockedSharedMemoryPointerBase::get() const { + return mem ? mem->data() : 0 ; +} + +size_t KDLockedSharedMemoryPointerBase::byteSize() const { + return mem->size(); +} + +/*! + \class KDLockedSharedMemoryPointer + \ingroup core raii smartptr + \brief Locking pointer for Qt shared memory segments + \since_c 2.1 + + (The exception safety of this class has not been evaluated yet.) + + KDLockedSharedMemoryPointer is a smart immutable pointer, which gives convenient and safe access to a QSharedMemory data segment. + The content of a KDLockedSharedMemoryPointer cannot be changed during it's lifetime. + + You can use this class like a normal pointer to the shared memory segment and be sure it's locked while accessing it. + \note You can only put simple types/structs/classes into it. structs and classes shall not contain any other pointers. See the + documentation of QSharedMemory for details. +*/ + +/*! + \fn KDLockedSharedMemoryPointer::KDLockedSharedMemoryPointer( QSharedMemory * mem ) + + Constructor. Constructs a KDLockedSharedMemory pointer which points to the data segment of \a mem. + The constructor locks \a mem. If the memory segment is already locked by another process, this constructor + blocks until the lock is released. + + \post data() == mem->data() and the memory segment has been locked +*/ + +/*! + \fn KDLockedSharedMemoryPointer::KDLockedSharedMemoryPointer( QSharedMemory & mem ) + + \overload + + \post data() == mem.data() and the memory segment has been locked +*/ + +/*! + \fn KDLockedSharedMemoryPointer::~KDLockedSharedMemoryPointer() + + Destructor. Unlocks the shared memory segment. + + \post The shared memory segment has been unlocked +*/ + +/*! + \fn T * KDLockedSharedMemoryPointer::get() + + \returns a pointer to the contained object. +*/ + +/*! + \fn const T * KDLockedSharedMemoryPointer::get() const + + \returns a const pointer to the contained object + \overload +*/ + +/*! + \fn T * KDLockedSharedMemoryPointer::data() + + Equivalent to get(), provided for consistency with Qt naming conventions. +*/ + +/*! + \fn const T * KDLockedSharedMemoryPointer::data() const + + \overload +*/ + +/*! + \fn T & KDLockedSharedMemoryPointer::operator*() + + Dereference operator. Returns \link get() *get()\endlink. +*/ + +/*! + \fn const T & KDLockedSharedMemoryPointer::operator*() const + + Dereference operator. Returns \link get() *get()\endlink. + \overload +*/ + +/*! + \fn T * KDLockedSharedMemoryPointer::operator->() + + Member-by-pointer operator. Returns get(). +*/ + +/*! + \fn const T * KDLockedSharedMemoryPointer::operator->() const + + Member-by-pointer operator. Returns get(). + \overload +*/ + +/*! + \class KDLockedSharedMemoryArray + \ingroup core raii smartptr + \brief Locking array pointer to Qt shared memory segments + \since_c 2.1 + + (The exception safety of this class has not been evaluated yet.) + + KDLockedSharedMemoryArray is a smart immutable pointer, which gives convenient and safe access to array data stored in a QSharedMemory + data segment. + The content of a KDLockedSharedMemoryArray cannot be changed during it's lifetime. + + You can use this class like a normal pointer to the shared memory segment and be sure it's locked while accessing it. + \note You can only put arrays of simple types/structs/classes into it. structs and classes shall not contain any other pointers. See the + documentation of QSharedMemory for details. + + \sa KDLockedSharedMemoryPointer +*/ + +/*! + \fn KDLockedSharedMemoryArray::KDLockedSharedMemoryArray( QSharedMemory* mem ) + Constructor. Constructs a KDLockedSharedMemoryArray which points to the data segment of \a mem. The constructor locks \a mem. If the memory + segment is already locked by another process, this constructor blocks until the lock is release. + + \post get() == mem->data() and the memory segment has been locked +*/ + +/*! + \fn KDLockedSharedMemoryArray::KDLockedSharedMemoryArray( QSharedMemory& mem ) + \overload + + \post get() == mem->data() and the memory segment has been locked +*/ + + +/*! + \typedef KDLockedSharedMemoryArray::size_type + Typedef for std::size_t. Provided for STL compatibility. +*/ + +/*! + \typedef KDLockedSharedMemoryArray::difference_type + Typedef for std::ptrdiff_t. Provided for STL compatibility. +*/ + +/*! + \typedef KDLockedSharedMemoryArray::iterator + Typedef for T*. Provided for STL compatibility. + \since_t 2.2 +*/ + +/*! + \typedef KDLockedSharedMemoryArray::const_iterator + Typedef for const T*. Provided for STL compatibility. + \since_t 2.2 +*/ + +/*! + \typedef KDLockedSharedMemoryArray::reverse_iterator + Typedef for std::reverse_iterator< \link KDLockedSharedMemoryArray::iterator iterator\endlink >. Provided for STL compatibility. + \since_t 2.2 +*/ + +/*! + \typedef KDLockedSharedMemoryArray::const_reverse_iterator + Typedef for std::reverse_iterator< \link KDLockedSharedMemoryArray::const_iterator const_iterator\endlink >. Provided for STL compatibility. + \since_t 2.2 +*/ + +/*! + \fn KDLockedSharedMemoryArray::iterator KDLockedSharedMemoryArray::begin() + Returns an \link KDLockedSharedMemoryArray::iterator iterator\endlink pointing to the first item of the array. + \since_f 2.2 +*/ + +/*! + \fn KDLockedSharedMemoryArray::const_iterator KDLockedSharedMemoryArray::begin() const + \overload + \since_f 2.2 +*/ + +/*! + \fn KDLockedSharedMemoryArray::iterator KDLockedSharedMemoryArray::end() + Returns an \link KDLockedSharedMemoryArray::iterator iterator\endlink pointing to the item after the last item of the array. + \since_f 2.2 +*/ + +/*! + \fn KDLockedSharedMemoryArray::const_iterator KDLockedSharedMemoryArray::end() const + \overload + \since_f 2.2 +*/ + +/*! + \fn KDLockedSharedMemoryArray::reverse_iterator KDLockedSharedMemoryArray::rbegin() + Returns an \link KDLockedSharedMemoryArray::reverse_iterator reverse_iterator\endlink pointing to the item after the last item of the array. + \since_f 2.2 +*/ + +/*! + \fn KDLockedSharedMemoryArray::const_reverse_iterator KDLockedSharedMemoryArray::rbegin() const + \overload + \since_f 2.2 +*/ + +/*! + \fn KDLockedSharedMemoryArray::reverse_iterator KDLockedSharedMemoryArray::rend() + Returns an \link KDLockedSharedMemoryArray::reverse_iterator reverse_iterator\endlink pointing to the first item of the array. + \since_f 2.2 +*/ + +/*! + \fn KDLockedSharedMemoryArray::const_reverse_iterator KDLockedSharedMemoryArray::rend() const + \overload + \since_f 2.2 +*/ + +/*! + \fn KDLockedSharedMemoryArray::size_type KDLockedSharedMemoryArray::size() const + Returns the size of this array. The size is calculated from the storage size of T and + the size of the shared memory segment. + \since_f 2.2 +*/ + +/*! + \fn T& KDLockedSharedMemoryArray::operator[]( difference_type n ) + Array access operator. Returns a reference to the item at index position \a n. +*/ + +/*! + \fn const T& KDLockedSharedMemoryArray::operator[]( difference_type n ) const + \overload +*/ + +/*! + \fn T& KDLockedSharedMemoryArray::front() + Returns a reference to the first item in the array. This is the same as operator[](0). +*/ + +/*! + \fn const T& KDLockedSharedMemoryArray::front() const + \overload +*/ + +/*! + \fn T& KDLockedSharedMemoryArray::back() + Returns a reference to the last item in the array. This is the same as operator[](size()-1). + \since_f 2.2 +*/ + +/*! + \fn const T& KDLockedSharedMemoryArray::back() const + \overload + \since_f 2.2 +*/ + + +#ifdef eKDTOOLSCORE_UNITTESTS + +#include + +#include +#include + +namespace +{ + struct TestStruct + { + TestStruct( uint nn = 0 ) + : n( nn ), + f( 0.0 ), + c( '\0' ), + b( false ) + { + } + uint n; + double f; + char c; + bool b; + }; + + bool operator==( const TestStruct& lhs, const TestStruct& rhs ) + { + return lhs.n == rhs.n && lhs.f == rhs.f && lhs.c == rhs.c && lhs.b == rhs.b; + } + + class TestThread : public QThread + { + public: + TestThread( const QString& key ) + : mem( key ) + { + mem.attach(); + } + + void run() + { + while( true ) + { + msleep( 100 ); + kdtools::KDLockedSharedMemoryPointer< TestStruct > p( &mem ); + if( !p->b ) + continue; + + p->n = 5; + p->f = 3.14; + p->c = 'A'; + p->b = false; + return; + } + } + + QSharedMemory mem; + }; + + bool isConst( TestStruct* ) + { + return false; + } + + bool isConst( const TestStruct* ) + { + return true; + } +} + + +KDAB_UNITTEST_SIMPLE( KDLockedSharedMemoryPointer, "kdcoretools" ) { + + const QString key = QUuid::createUuid(); + QSharedMemory mem( key ); + const bool created = mem.create( sizeof( TestStruct ) ); + assertTrue( created ); + if ( !created ) + return; // don't execute tests if shm coulnd't be created + + // On Windows, shared mem is only available in increments of page + // size (4k), so don't fail if the segment is larger: + const unsigned long mem_size = mem.size(); + assertGreaterOrEqual( mem_size, sizeof( TestStruct ) ); + + { + kdtools::KDLockedSharedMemoryPointer< TestStruct > p( &mem ); + assertTrue( p ); + *p = TestStruct(); + assertEqual( p->n, 0u ); + assertEqual( p->f, 0.0 ); + assertEqual( p->c, '\0' ); + assertFalse( p->b ); + } + + { + TestThread thread( key ); + assertEqual( thread.mem.key().toStdString(), key.toStdString() ); + assertEqual( static_cast< unsigned long >( thread.mem.size() ), mem_size ); + thread.start(); + + assertTrue( thread.isRunning() ); + thread.wait( 2000 ); + assertTrue( thread.isRunning() ); + + { + kdtools::KDLockedSharedMemoryPointer< TestStruct > p( &mem ); + p->b = true; + } + + thread.wait( 2000 ); + assertFalse( thread.isRunning() ); + } + + { + kdtools::KDLockedSharedMemoryPointer< TestStruct > p( &mem ); + assertEqual( p->n, 5u ); + assertEqual( p->f, 3.14 ); + assertEqual( p->c, 'A' ); + assertFalse( p->b ); + } + + { + kdtools::KDLockedSharedMemoryPointer< TestStruct > p( mem ); + assertEqual( mem.data(), p.get() ); + assertEqual( p.get(), p.operator->() ); + assertEqual( p.get(), &(*p) ); + assertEqual( p.get(), p.data() ); + assertFalse( isConst( p.get() ) ); + } + + { + const kdtools::KDLockedSharedMemoryPointer< TestStruct > p( &mem ); + assertEqual( mem.data(), p.get() ); + assertEqual( p.get(), p.operator->() ); + assertEqual( p.get(), &(*p) ); + assertEqual( p.get(), p.data() ); + assertTrue( isConst( p.get() ) ); + } + + { + QSharedMemory mem2( key + key ); + const bool created2 = mem2.create( 16 * sizeof( TestStruct ) ); + assertTrue( created2 ); + if ( !created2 ) + return; // don't execute tests if shm coulnd't be created + + kdtools::KDLockedSharedMemoryArray a( mem2 ); + assertTrue( a ); + assertEqual( a.get(), mem2.data() ); + assertEqual( &a[0], a.get() ); + + a[1] = a[0]; + assertTrue( a[0] == a[1] ); + + TestStruct ts; + ts.n = 5; + ts.f = 3.14; + a[0] = ts; + assertFalse( a[0] == a[1] ); + assertEqual( a.front().n, ts.n ); + assertEqual( a[0].f, ts.f ); + a[0].n = 10; + assertEqual( a.front().n, 10u ); + ts = a[0]; + assertEqual( ts.n, 10u ); + + std::vector< TestStruct > v; + for( uint i = 0; i < a.size(); ++i ) + v.push_back( TestStruct( i ) ); + + std::copy( v.begin(), v.end(), a.begin() ); + for( uint i = 0; i < a.size(); ++i ) + assertEqual( a[ i ].n, i ); + assertEqual( a.front().n, 0u ); + assertEqual( a.back().n, a.size() - 1 ); + + std::copy( v.begin(), v.end(), a.rbegin() ); + for( uint i = 0; i < a.size(); ++i ) + assertEqual( a[ i ].n, a.size() - 1 - i ); + assertEqual( a.front().n, a.size() - 1 ); + assertEqual( a.back().n, 0u ); + } + +} +#endif // KDTOOLSCORE_UNITTESTS +#endif // QT_NO_SHAREDMEMORY +#endif // QT_VERSION >= 0x040400 || defined( DOXYGEN_RUN ) diff --git a/src/libtomahawk/kdsingleapplicationguard/kdlockedsharedmemorypointer.h b/src/libtomahawk/kdsingleapplicationguard/kdlockedsharedmemorypointer.h new file mode 100644 index 000000000..df0ea4998 --- /dev/null +++ b/src/libtomahawk/kdsingleapplicationguard/kdlockedsharedmemorypointer.h @@ -0,0 +1,115 @@ +#ifndef __KDTOOLS__CORE__KDLOCKEDSHAREDMEMORYPOINTER_H__ +#define __KDTOOLS__CORE__KDLOCKEDSHAREDMEMORYPOINTER_H__ + +#include + +#if QT_VERSION >= 0x040400 || defined( DOXYGEN_RUN ) +#ifndef QT_NO_SHAREDMEMORY + +#include "kdsharedmemorylocker.h" +#include + +#include + +#ifndef DOXYGEN_RUN +namespace kdtools { +#endif + +class KDLockedSharedMemoryPointerBase { +protected: + explicit KDLockedSharedMemoryPointerBase( QSharedMemory * mem ); + explicit KDLockedSharedMemoryPointerBase( QSharedMemory & mem ); + ~KDLockedSharedMemoryPointerBase(); + + // PENDING(marc) do we really want const propagation here? I + // usually declare all my RAII objects const... + void * get(); + const void * get() const; + + KDAB_IMPLEMENT_SAFE_BOOL_OPERATOR( get() ) + + size_t byteSize() const; + +private: + KDSharedMemoryLocker locker; + QSharedMemory * const mem; +}; + +template< typename T> +class MAKEINCLUDES_EXPORT KDLockedSharedMemoryPointer : KDLockedSharedMemoryPointerBase { + KDAB_DISABLE_COPY( KDLockedSharedMemoryPointer ); +public: + explicit KDLockedSharedMemoryPointer( QSharedMemory * m ) + : KDLockedSharedMemoryPointerBase( m ) {} + explicit KDLockedSharedMemoryPointer( QSharedMemory & m ) + : KDLockedSharedMemoryPointerBase( m ) {} + + T * get() { return static_cast( KDLockedSharedMemoryPointerBase::get() ); } + const T * get() const { return static_cast( KDLockedSharedMemoryPointerBase::get() ); } + + T * data() { return static_cast( get() ); } + const T * data() const { return static_cast( get() ); } + + T & operator*() { assert( get() ); return *get(); } + const T & operator*() const { assert( get() ); return *get(); } + + T * operator->() { return get(); } + const T * operator->() const { return get(); } + + KDAB_USING_SAFE_BOOL_OPERATOR( KDLockedSharedMemoryPointerBase ) +}; + +template +class MAKEINCLUDES_EXPORT KDLockedSharedMemoryArray : KDLockedSharedMemoryPointerBase { + KDAB_DISABLE_COPY( KDLockedSharedMemoryArray ); +public: + explicit KDLockedSharedMemoryArray( QSharedMemory * m ) + : KDLockedSharedMemoryPointerBase( m ) {} + explicit KDLockedSharedMemoryArray( QSharedMemory & m ) + : KDLockedSharedMemoryPointerBase( m ) {} + + typedef std::size_t size_type; + typedef std::ptrdiff_t difference_type; + typedef T* iterator; + typedef const T* const_iterator; + typedef std::reverse_iterator< const_iterator > const_reverse_iterator; + typedef std::reverse_iterator< iterator > reverse_iterator; + + iterator begin() { return get(); } + const_iterator begin() const { return get(); } + + iterator end() { return begin() + size(); } + const_iterator end() const { return begin() + size(); } + + reverse_iterator rbegin() { return reverse_iterator( end() ); } + const_reverse_iterator rbegin() const { return reverse_iterator( end() ); } + + reverse_iterator rend() { return reverse_iterator( begin() ); } + const_reverse_iterator rend() const { return const_reverse_iterator( begin() ); } + + size_type size() const { return byteSize() / sizeof( T ); } + + T * get() { return static_cast( KDLockedSharedMemoryPointerBase::get() ); } + const T * get() const { return static_cast( KDLockedSharedMemoryPointerBase::get() ); } + + T & operator[]( difference_type n ) { assert( get() ); return *(get()+n); } + const T & operator[]( difference_type n ) const { assert( get() ); return *(get()+n); } + + T & front() { assert( get() ); return *get(); } + const T & front() const { assert( get() ); return *get(); } + + T & back() { assert( get() ); return *( get() + size() - 1 ); } + const T & back() const { assert( get() ); return *( get() + size() - 1 ); } + + KDAB_USING_SAFE_BOOL_OPERATOR( KDLockedSharedMemoryPointerBase ) +}; + +#ifndef DOXYGEN_RUN +} +#endif + +#endif /* QT_NO_SHAREDMEMORY */ + +#endif /* QT_VERSION >= 0x040400 || defined( DOXYGEN_RUN ) */ + +#endif /* __KDTOOLS__CORE__KDLOCKEDSHAREDMEMORYPOINTER_H__ */ diff --git a/src/libtomahawk/kdsingleapplicationguard/kdsharedmemorylocker.cpp b/src/libtomahawk/kdsingleapplicationguard/kdsharedmemorylocker.cpp new file mode 100644 index 000000000..0c99b8fff --- /dev/null +++ b/src/libtomahawk/kdsingleapplicationguard/kdsharedmemorylocker.cpp @@ -0,0 +1,40 @@ +#include "kdsharedmemorylocker.h" + +#if QT_VERSION >= 0x040400 || defined( DOXYGEN_RUN ) + +#include + +using namespace kdtools; + +/*! + \class KDSharedMemoryLocker + \ingroup raii core + \brief Exception-safe and convenient wrapper around QSharedMemory::lock() +*/ + +/** + * Constructor. Locks the shared memory segment \a mem. + * If another process has locking the segment, this constructor blocks + * until the lock is released. The memory segments needs to be properly created or attached. + */ +KDSharedMemoryLocker::KDSharedMemoryLocker( QSharedMemory* mem ) + : mem( mem ) +{ + mem->lock(); +} + +/** + * Destructor. Unlocks the shared memory segment associated with this + * KDSharedMemoryLocker. + */ +KDSharedMemoryLocker::~KDSharedMemoryLocker() +{ + mem->unlock(); +} + +#ifdef KDAB_EVAL +#include KDAB_EVAL +static const EvalDialogChecker evalChecker( "KD Tools", false ); +#endif + +#endif diff --git a/src/libtomahawk/kdsingleapplicationguard/kdsharedmemorylocker.h b/src/libtomahawk/kdsingleapplicationguard/kdsharedmemorylocker.h new file mode 100644 index 000000000..7ae83e771 --- /dev/null +++ b/src/libtomahawk/kdsingleapplicationguard/kdsharedmemorylocker.h @@ -0,0 +1,36 @@ +#ifndef __KDTOOLS__CORE__KDSHAREDMEMORYLOCKER_H +#define __KDTOOLS__CORE__KDSHAREDMEMORYLOCKER_H + +#include "kdtoolsglobal.h" + +#if QT_VERSION < 0x040400 && !defined( DOXYGEN_RUN ) +#ifdef Q_CC_GNU +#warning "Can't use KDTools KDSharedMemoryLocker with Qt versions prior to 4.4" +#endif +#else + +class QSharedMemory; + +#ifndef DOXYGEN_RUN +namespace kdtools +{ +#endif + +class KDTOOLSCORE_EXPORT KDSharedMemoryLocker +{ + Q_DISABLE_COPY( KDSharedMemoryLocker ) +public: + KDSharedMemoryLocker( QSharedMemory* mem ); + ~KDSharedMemoryLocker(); + +private: + QSharedMemory* const mem; +}; + +#ifndef DOXYGEN_RUN +} +#endif + +#endif + +#endif diff --git a/src/libtomahawk/kdsingleapplicationguard/kdsingleapplicationguard.cpp b/src/libtomahawk/kdsingleapplicationguard/kdsingleapplicationguard.cpp new file mode 100644 index 000000000..63893dbde --- /dev/null +++ b/src/libtomahawk/kdsingleapplicationguard/kdsingleapplicationguard.cpp @@ -0,0 +1,622 @@ +#include "kdsingleapplicationguard.h" + +#ifndef KDSINGLEAPPLICATIONGUARD_NUMBER_OF_PROCESSES +#define KDSINGLEAPPLICATIONGUARD_NUMBER_OF_PROCESSES 128 +#endif + +#ifndef KDSINGLEAPPLICATIONGUARD_MAX_COMMAND_LINE +#define KDSINGLEAPPLICATIONGUARD_MAX_COMMAND_LINE 1024 +#endif + + + +KDSingleApplicationGuard::Instance::Instance( const QStringList& args, qint64 p ) + : arguments( args ), + pid( p ) +{ +} + +#if QT_VERSION < 0x040400 + +class KDSingleApplicationGuard::Private +{ +}; + +KDSingleApplicationGuard::KDSingleApplicationGuard( QCoreApplication*, Policy ) +{ + qWarning( "KD Tools was compiled with a Qt version prior to 4.4. SingleApplicationGuard won't work." ); +} + +KDSingleApplicationGuard::~KDSingleApplicationGuard() +{ +} + +void KDSingleApplicationGuard::shutdownOtherInstances() +{ +} + +void KDSingleApplicationGuard::killOtherInstances() +{ +} + +void KDSingleApplicationGuard::timerEvent( QTimerEvent* ) +{ +} +#else + +#include +#include + +#include "kdsharedmemorylocker.h" +#include "kdlockedsharedmemorypointer.h" + +#include +#include +#include + +#ifndef Q_WS_WIN +#include +#endif + +using namespace kdtools; + +/*! + \class KDSingleApplicationGuard KDSingleApplicationGuard + \brief A guard to protect an application from having several instances. + + KDSingleApplicationGuard can be used to make sure only one instance of an + application is running at the same time. + + \note As KDSingleApplicationGuard uses QSharedMemory Qt 4.4 or later is required + */ + +/*! + \fn void KDSingleApplicationGuard::instanceStarted() + This signal is emitted by the primary instance when ever one other + instance was started. + */ + +/*! + \fn void KDSingleApplicationGuard::instanceExited() + This signal is emitted by the primary instance when ever one other + instance was exited. + */ + +/*! + \fn void KDSingleApplicationGuard::becamePrimaryInstance() + This signal is emitted, when the current running application gets the new + primary application. The old primary application has quit. + */ + +enum Command +{ + NoCommand = 0x00, + ExitedInstance = 0x01, + NewInstance = 0x02, + FreeInstance = 0x04, + ShutDownCommand = 0x08, + KillCommand = 0x10, + BecomePrimaryCommand = 0x20 +}; + +Q_DECLARE_FLAGS( Commands, Command ) +Q_DECLARE_OPERATORS_FOR_FLAGS( Commands ) + +struct ProcessInfo +{ + explicit ProcessInfo( Command c = FreeInstance, const QStringList& arguments = QStringList(), qint64 p = -1 ) + : command( c ), + pid( p ) + { + std::fill_n( commandline, KDSINGLEAPPLICATIONGUARD_MAX_COMMAND_LINE, '\0' ); + + int argpos = 0; + for( QStringList::const_iterator it = arguments.begin(); it != arguments.end(); ++it ) + { + const QByteArray arg = it->toLatin1(); + const int count = qMin( KDSINGLEAPPLICATIONGUARD_MAX_COMMAND_LINE - argpos, arg.count() ); + std::copy( arg.begin(), arg.begin() + count, commandline + argpos ); + argpos += arg.count() + 1; // makes sure there's a \0 between every parameter + } + } + + QStringList arguments() const + { + QStringList result; + + QByteArray arg; + for( int i = 0; i < KDSINGLEAPPLICATIONGUARD_MAX_COMMAND_LINE; ++i ) + { + if( commandline[ i ] == '\0' && !arg.isEmpty() ) + { + result.push_back( QString::fromLatin1( arg ) ); + arg.clear(); + } + else if( !commandline[ i ] == '\0' ) + { + arg.push_back( commandline[ i ] ); + } + } + + return result; + } + + Commands command; + char commandline[ KDSINGLEAPPLICATIONGUARD_MAX_COMMAND_LINE ]; + qint64 pid; +}; + +bool operator==( const ProcessInfo& lhs, const ProcessInfo& rhs ) +{ + return lhs.command == rhs.command && + ::memcmp( lhs.commandline, rhs.commandline, KDSINGLEAPPLICATIONGUARD_MAX_COMMAND_LINE ) == 0; +} + +bool operator!=( const ProcessInfo& lhs, const ProcessInfo& rhs ) +{ + return !operator==( lhs, rhs ); +} + +/*! + This struct contains information about the managed process system. + \internal + */ +struct InstanceRegister +{ + InstanceRegister( KDSingleApplicationGuard::Policy policy = KDSingleApplicationGuard::NoPolicy ) + : policy( policy ) + { + std::fill_n( info, KDSINGLEAPPLICATIONGUARD_NUMBER_OF_PROCESSES, ProcessInfo() ); + ::memcpy( magicCookie, "kdsingleapp", 12 ); + } + + /*! + Returns wheter this register was properly initialized by the first instance. + */ + bool isValid() const + { + return ::strcmp( magicCookie, "kdsingleapp" ) == 0; + } + + ProcessInfo info[ KDSINGLEAPPLICATIONGUARD_NUMBER_OF_PROCESSES ]; + KDSingleApplicationGuard::Policy policy; + char magicCookie[ 12 ]; +}; + +bool operator==( const InstanceRegister& lhs, const InstanceRegister& rhs ) +{ + if( lhs.policy != rhs.policy ) + return false; + + for( int i = 0; i < KDSINGLEAPPLICATIONGUARD_NUMBER_OF_PROCESSES; ++i ) + if( lhs.info[ i ] != rhs.info[ i ] ) + return false; + + return true; +} + +/*! + \internal + */ +class KDSingleApplicationGuard::Private +{ +public: + Private( KDSingleApplicationGuard* qq ) + : q( qq ), + id( -1 ) + { + if( primaryInstance == 0 ) + primaryInstance = q; + } + + ~Private() + { + if( primaryInstance == q ) + primaryInstance = 0; + } + + void shutdownInstance() + { + KDLockedSharedMemoryPointer< InstanceRegister > instances( &q->d->mem ); + instances->info[ q->d->id ].command = ExitedInstance; + + if( q->isPrimaryInstance() ) + { + // ohh... we need a new primary instance... + for( int i = 1; i < KDSINGLEAPPLICATIONGUARD_NUMBER_OF_PROCESSES; ++i ) + { + if( ( instances->info[ i ].command & ( FreeInstance | ExitedInstance | ShutDownCommand | KillCommand ) ) == 0 ) + { + instances->info[ i ].command |= BecomePrimaryCommand; + return; + } + } + // none found? then my species is dead :-( + } + } + + static KDSingleApplicationGuard* primaryInstance; + +private: + KDSingleApplicationGuard* const q; + +public: + Policy policy; + QSharedMemory mem; + int id; +}; + +KDSingleApplicationGuard* KDSingleApplicationGuard::Private::primaryInstance = 0; + +#ifndef Q_WS_WIN +void SIGINT_handler( int sig ) +{ + if( sig == SIGINT && KDSingleApplicationGuard::Private::primaryInstance != 0 ) + KDSingleApplicationGuard::Private::primaryInstance->d->shutdownInstance(); + ::exit( 1 ); +} +#endif + +/*! + Creates a new KDSingleApplicationGuard guarding \a parent from mulitply instances. + If \a policy is AutoKillOtherInstances (the default), all instances, which try to start, + are killed automatically and instanceStarted() is emitted. + If \a policy is NoPolicy, the other instance will run and instanceStarted() is emitted. + */ +KDSingleApplicationGuard::KDSingleApplicationGuard( QCoreApplication* parent, Policy policy ) + : QObject( parent ), + d( new Private( this ) ) +{ + const QString name = parent->applicationName(); + Q_ASSERT_X( !name.isEmpty(), "KDSingleApplicationGuard::KDSingleApplicationGuard", "applicationName must not be emty" ); + d->mem.setKey( name ); + + // if another instance crashed, the shared memory segment is still there on Unix + // the following lines trigger deletion in that case +#ifndef Q_WS_WIN + d->mem.attach(); + d->mem.detach(); +#endif + + d->policy = policy; + + const bool created = d->mem.create( sizeof( InstanceRegister ) ); + if( !created ) + { + if( !d->mem.attach() ) + { + qWarning( "KDSingleApplicationGuard: Could neither create nor attach to shared memory segment." ); + return; + } + + // lets wait till the other instance initialized the register + bool initialized = false; + while( !initialized ) + { + const KDLockedSharedMemoryPointer< InstanceRegister > instances( &d->mem ); + initialized = instances->isValid(); + } + } + + + KDLockedSharedMemoryPointer< InstanceRegister > instances( &d->mem ); + + if( !created ) + { + // we're _not_ the first instance + // but the + bool killOurSelf = false; + + // find a new slot... + d->id = std::find( instances->info, instances->info + KDSINGLEAPPLICATIONGUARD_NUMBER_OF_PROCESSES, ProcessInfo() ) - instances->info; + ProcessInfo& info = instances->info[ d->id ]; + info = ProcessInfo( NewInstance, parent->arguments(), QCoreApplication::applicationPid() ); + killOurSelf = instances->policy == AutoKillOtherInstances; + d->policy = instances->policy; + + // but the signal that we tried to start was sent to the primary application + if( killOurSelf ) + { + info.command |= ExitedInstance; + exit( 1 ); + } + } + else + { + // ok.... we are the first instance + InstanceRegister reg( policy ); // create a new list + d->id = 0; // our id = 0 + // and we've no command + reg.info[ 0 ] = ProcessInfo( NoCommand, parent->arguments(), QCoreApplication::applicationPid() ); + *instances = reg; // push this is the process list into shared memory + } + +#ifndef Q_WS_WIN + ::signal( SIGINT, SIGINT_handler ); +#endif + + // now listen for commands + startTimer( 250 ); +} + +/*! + Destroys this SingleApplicationGuard. + If this instance has been the primary instance and no other instance is existing anymore, + the application is shut down completely. Otherwise the destructor selects another instance to + be the primary instances. + */ +KDSingleApplicationGuard::~KDSingleApplicationGuard() +{ + if( d->id == -1 ) + return; + + d->shutdownInstance(); +} + +/*! + \property KDSingleApplicationGuard::primaryInstance + Determines wheter this instance is the primary instance. + The primary instance is the first instance which was started or an instance which + got selected by KDSingleApplicationGuard's destructor, when the primary instance was + shut down. + + Get this property's value using %isPrimaryInstance(), and monitor changes to it + using becamePrimaryInstance(). + */ +bool KDSingleApplicationGuard::isPrimaryInstance() const +{ + return d->id == 0; +} + +/*! + \property KDSingleApplicationGuard::Policy + Specifies the policy KDSingleApplicationGuard is using when new instances are started. + This can only be set in the primary instance. + + Get this property's value using %policy(), set it using %setPolicy(), and monitor changes + to it using policyChanged(). + */ +KDSingleApplicationGuard::Policy KDSingleApplicationGuard::policy() const +{ + return d->policy; +} + +void KDSingleApplicationGuard::setPolicy( Policy policy ) +{ + Q_ASSERT( isPrimaryInstance() ); + if( d->policy == policy ) + return; + + d->policy = policy; + emit policyChanged(); + KDLockedSharedMemoryPointer< InstanceRegister > instances( &d->mem ); + instances->policy = policy; +} + +/*! + Returns a list of all currently running instances. + */ +QList< KDSingleApplicationGuard::Instance > KDSingleApplicationGuard::instances() const +{ + QList< Instance > result; + const KDLockedSharedMemoryPointer< InstanceRegister > instances( const_cast< QSharedMemory* >( &d->mem ) ); + for( int i = 0; i < KDSINGLEAPPLICATIONGUARD_NUMBER_OF_PROCESSES; ++i ) + { + const ProcessInfo& info = instances->info[ i ]; + if( ( info.command & ( FreeInstance | ExitedInstance ) ) == 0 ) + result.push_back( Instance( info.arguments(), info.pid ) ); + } + return result; +} + +/*! + Shuts down all other instances. This can only be called from the + the primary instance. + Shut down is done gracefully via QCoreApplication::quit(). + */ +void KDSingleApplicationGuard::shutdownOtherInstances() +{ + Q_ASSERT( isPrimaryInstance() ); + KDLockedSharedMemoryPointer< InstanceRegister > instances( &d->mem ); + for( int i = 1; i < KDSINGLEAPPLICATIONGUARD_NUMBER_OF_PROCESSES; ++i ) + { + if( ( instances->info[ i ].command & ( FreeInstance | ExitedInstance ) ) == 0 ) + instances->info[ i ].command = ShutDownCommand; + } +} + +/*! + Kills all other instances. This can only be called from the + the primary instance. + Killing is done via exit(1) + */ +void KDSingleApplicationGuard::killOtherInstances() +{ + Q_ASSERT( isPrimaryInstance() ); + KDLockedSharedMemoryPointer< InstanceRegister > instances( &d->mem ); + for( int i = 1; i < KDSINGLEAPPLICATIONGUARD_NUMBER_OF_PROCESSES; ++i ) + { + if( ( instances->info[ i ].command & ( FreeInstance | ExitedInstance ) ) == 0 ) + instances->info[ i ].command = KillCommand; + } +} + +/*! + \reimp + */ +void KDSingleApplicationGuard::timerEvent( QTimerEvent* event ) +{ + Q_UNUSED( event ) + + if( isPrimaryInstance() ) + { + // only the primary instance will get notified about new instances + QList< Instance > exitedInstances; + QList< Instance > startedInstances; + + { + KDLockedSharedMemoryPointer< InstanceRegister > instances( &d->mem ); + + for( int i = 1; i < KDSINGLEAPPLICATIONGUARD_NUMBER_OF_PROCESSES; ++i ) + { + ProcessInfo& info = instances->info[ i ]; + if( info.command & NewInstance ) + { + startedInstances.push_back( Instance( info.arguments(), info.pid ) ); + info.command &= ~NewInstance; // clear NewInstance flag + } + else if( info.command & ExitedInstance ) + { + exitedInstances.push_back( Instance( info.arguments(), info.pid ) ); + info.command = FreeInstance; // set FreeInstance flag + } + } + } + + // one signal for every new instance - _after_ the memory segment was unlocked again + for( QList< Instance >::const_iterator it = startedInstances.begin(); it != startedInstances.end(); ++it ) + emit instanceStarted( *it ); + for( QList< Instance >::const_iterator it = exitedInstances.begin(); it != exitedInstances.end(); ++it ) + emit instanceExited( *it ); + } + else + { + // do we have a command? + bool killOurSelf = false; + bool shutDownOurSelf = false; + bool policyDidChange = false; + + { + KDLockedSharedMemoryPointer< InstanceRegister > instances( &d->mem ); + + policyDidChange = instances->policy != d->policy; + d->policy = instances->policy; + + if( instances->info[ d->id ].command & BecomePrimaryCommand ) + { + // we became primary! + instances->info[ 0 ] = instances->info[ d->id ]; + instances->info[ d->id ] = ProcessInfo(); // change our id to 0 and declare the old slot as free + d->id = 0; + emit becamePrimaryInstance(); + } + + killOurSelf = instances->info[ d->id ].command & KillCommand; // check for kill command + shutDownOurSelf = instances->info[ d->id ].command & ShutDownCommand; // check for shut down command + instances->info[ d->id ].command &= ~( KillCommand | ShutDownCommand | BecomePrimaryCommand ); // reset both flags + if( killOurSelf ) + { + instances->info[ d->id ].command |= ExitedInstance; // upon kill, we have to set the ExitedInstance flag + d->id = -1; // becauso our d'tor won't be called anymore + } + } + + if( killOurSelf ) // kill our self takes precedence + exit( 1 ); + else if( shutDownOurSelf ) + qApp->quit(); + else if( policyDidChange ) + emit policyChanged(); + } +} + +#ifdef KDTOOLSCORE_UNITTESTS + +#include + +#include + +#include +#include +#include + +Q_DECLARE_METATYPE( KDSingleApplicationGuard::Instance ); + +static void wait( int msec ) +{ + QTime t; + t.start(); + while( t.elapsed() < msec ) + { + qApp->processEvents( QEventLoop::WaitForMoreEvents, msec - t.elapsed() ); + } +} + +static std::ostream& operator<<( std::ostream& stream, const QStringList& list ) +{ + stream << "QStringList("; + for( QStringList::const_iterator it = list.begin(); it != list.end(); ++it ) + { + stream << " " << it->toLocal8Bit().data(); + if( it + 1 != list.end() ) + stream << ","; + } + stream << " )"; + return stream; +} + + +KDAB_UNITTEST_SIMPLE( KDSingleApplicationGuard, "kdcoretools" ) { + + // set it to an unique name + qApp->setApplicationName( QUuid::createUuid().toString() ); + + qRegisterMetaType< KDSingleApplicationGuard::Instance >(); + + KDSingleApplicationGuard* guard3 = 0; + QSignalSpy* spy3 = 0; + + { + KDSingleApplicationGuard guard1( qApp ); + assertEqual( guard1.policy(), KDSingleApplicationGuard::AutoKillOtherInstances ); + assertEqual( guard1.instances().count(), 1 ); + assertTrue( guard1.isPrimaryInstance() ); + + guard1.setPolicy( KDSingleApplicationGuard::NoPolicy ); + assertEqual( guard1.policy(), KDSingleApplicationGuard::NoPolicy ); + + QSignalSpy spy1( &guard1, SIGNAL( instanceStarted( KDSingleApplicationGuard::Instance ) ) ); + + KDSingleApplicationGuard guard2( qApp ); + assertEqual( guard1.instances().count(), 2 ); + assertEqual( guard2.instances().count(), 2 ); + assertEqual( guard2.policy(), KDSingleApplicationGuard::NoPolicy ); + assertFalse( guard2.isPrimaryInstance() ); + + wait( 1000 ); + + assertEqual( spy1.count(), 1 ); + guard3 = new KDSingleApplicationGuard( qApp ); + spy3 = new QSignalSpy( guard3, SIGNAL( becamePrimaryInstance() ) ); + assertFalse( guard3->isPrimaryInstance() ); + } + + wait( 1000 ); + assertEqual( spy3->count(), 1 ); + assertEqual( guard3->instances().count(), 1 ); + assertTrue( guard3->isPrimaryInstance() ); + + assertEqual( guard3->instances().first().arguments, qApp->arguments() ); + + QSignalSpy spyStarted( guard3, SIGNAL( instanceStarted( KDSingleApplicationGuard::Instance ) ) ); + QSignalSpy spyExited( guard3, SIGNAL( instanceExited( KDSingleApplicationGuard::Instance ) ) ); + + { + KDSingleApplicationGuard guard1( qApp ); + KDSingleApplicationGuard guard2( qApp ); + + wait( 1000 ); + + assertEqual( spyStarted.count(), 2 ); + } + + wait( 1000 ); + assertEqual( spyExited.count(), 2 ); + + delete spy3; + delete guard3; + } + +#endif // KDTOOLSCORE_UNITTESTS + +#endif // QT_VERSION < 0x040400 diff --git a/src/libtomahawk/kdsingleapplicationguard/kdsingleapplicationguard.h b/src/libtomahawk/kdsingleapplicationguard/kdsingleapplicationguard.h new file mode 100644 index 000000000..f649ef836 --- /dev/null +++ b/src/libtomahawk/kdsingleapplicationguard/kdsingleapplicationguard.h @@ -0,0 +1,74 @@ +#ifndef __KDTOOLSCORE_KDSINGLEAPPLICATIONGUARD_H__ +#define __KDTOOLSCORE_KDSINGLEAPPLICATIONGUARD_H__ + +#include +#include + +#include "pimpl_ptr.h" + +class QCoreApplication; + +#ifndef Q_WS_WIN +void SIGINT_handler( int sig ); +#endif + +class KDTOOLSCORE_EXPORT KDSingleApplicationGuard : public QObject +{ + Q_OBJECT +#ifndef Q_WS_WIN + friend void ::SIGINT_handler( int ); +#endif + +public: + enum Policy + { + NoPolicy = 0, + AutoKillOtherInstances = 1 + }; + + Q_PROPERTY( bool primaryInstance READ isPrimaryInstance NOTIFY becamePrimaryInstance ) + Q_PROPERTY( Policy policy READ policy WRITE setPolicy NOTIFY policyChanged ) + + explicit KDSingleApplicationGuard( QCoreApplication* parent, Policy policy = AutoKillOtherInstances ); + ~KDSingleApplicationGuard(); + + bool isPrimaryInstance() const; + + Policy policy() const; + void setPolicy( Policy policy ); + + struct Instance + { + Instance( const QStringList& arguments = QStringList(), qint64 pid = -1 ); + + QStringList arguments; + qint64 pid; + }; + + QList< Instance > instances() const; + +Q_SIGNALS: + void instanceStarted( KDSingleApplicationGuard::Instance instance ); + void instanceExited( KDSingleApplicationGuard::Instance instance ); + void becamePrimaryInstance(); + void policyChanged(); + +public Q_SLOTS: + void shutdownOtherInstances(); + void killOtherInstances(); + +protected: + void timerEvent( QTimerEvent* event ); + +private: + class Private; + kdtools::pimpl_ptr< Private > d; +}; + +#if QT_VERSION < 0x040400 +#ifdef Q_CC_GNU +#warning "Can't use KDSingleApplicationGuard with Qt versions prior to 4.4" +#endif +#endif + +#endif diff --git a/src/libtomahawk/kdsingleapplicationguard/kdtoolsglobal.cpp b/src/libtomahawk/kdsingleapplicationguard/kdtoolsglobal.cpp new file mode 100644 index 000000000..5997fe64c --- /dev/null +++ b/src/libtomahawk/kdsingleapplicationguard/kdtoolsglobal.cpp @@ -0,0 +1,32 @@ +#include "kdtoolsglobal.h" + +#include + +#include + +namespace { + struct Version { + unsigned char v[3]; + }; + + static inline bool operator<( const Version & lhs, const Version & rhs ) { + return std::lexicographical_compare( lhs.v, lhs.v + 3, rhs.v, rhs.v + 3 ); + } + static inline bool operator==( const Version & lhs, const Version & rhs ) { + return std::equal( lhs.v, lhs.v + 3, rhs.v ); + } + KDTOOLS_MAKE_RELATION_OPERATORS( Version, static inline ) +} + +static Version kdParseQtVersion( const char * const version ) { + if ( !version || qstrlen( version ) < 5 || version[1] != '.' || version[3] != '.' || version[5] != 0 && version[5] != '.' && version[5] != '-' ) + return Version(); // parse error + const Version result = { { version[0] - '0', version[2] - '0', version[4] - '0' } }; + return result; +} + +bool _kdCheckQtVersion_impl( int major, int minor, int patchlevel ) { + static const Version actual = kdParseQtVersion( qVersion() ); // do this only once each run... + const Version requested = { { major, minor, patchlevel } }; + return actual >= requested; +} diff --git a/src/libtomahawk/kdsingleapplicationguard/kdtoolsglobal.h b/src/libtomahawk/kdsingleapplicationguard/kdtoolsglobal.h new file mode 100644 index 000000000..4e8d0d673 --- /dev/null +++ b/src/libtomahawk/kdsingleapplicationguard/kdtoolsglobal.h @@ -0,0 +1,113 @@ +#ifndef __KDTOOLS_KDTOOLSGLOBAL_H__ +#define __KDTOOLS_KDTOOLSGLOBAL_H__ + +#include + +#define KDAB_DISABLE_COPY( x ) private: x( const x & ); x & operator=( const x & ) + +#ifdef KDTOOLS_SHARED +# ifdef BUILD_SHARED_KDTOOLSCORE +# define KDTOOLSCORE_EXPORT Q_DECL_EXPORT +# else +# define KDTOOLSCORE_EXPORT Q_DECL_IMPORT +# endif +# ifdef BUILD_SHARED_KDTOOLSGUI +# define KDTOOLSGUI_EXPORT Q_DECL_EXPORT +# else +# define KDTOOLSGUI_EXPORT Q_DECL_IMPORT +# endif +# ifdef BUILD_SHARED_KDTOOLSXML +# define KDTOOLSXML_EXPORT Q_DECL_EXPORT +# else +# define KDTOOLSXML_EXPORT Q_DECL_IMPORT +# endif +# ifdef BUILD_SHARED_KDUPDATER +# define KDTOOLS_UPDATER_EXPORT Q_DECL_EXPORT +# else +# define KDTOOLS_UPDATER_EXPORT Q_DECL_IMPORT +# endif +#else // KDTOOLS_SHARED +# define KDTOOLSCORE_EXPORT +# define KDTOOLSGUI_EXPORT +# define KDTOOLSXML_EXPORT +# define KDTOOLS_UPDATER_EXPORT +#endif // KDTOOLS_SHARED + +#define MAKEINCLUDES_EXPORT + +#define DOXYGEN_PROPERTY( x ) +#ifdef DOXYGEN_RUN +# define KDAB_IMPLEMENT_SAFE_BOOL_OPERATOR( func ) operator unspecified_bool_type() const { return func; } +# define KDAB_USING_SAFE_BOOL_OPERATOR( Class ) operator unspecified_bool_type() const; +#else +# define KDAB_IMPLEMENT_SAFE_BOOL_OPERATOR( func ) \ + private: struct __safe_bool_dummy__ { void nonnull() {} }; \ + typedef void ( __safe_bool_dummy__::*unspecified_bool_type )(); \ + public: \ + operator unspecified_bool_type() const { \ + return ( func ) ? &__safe_bool_dummy__::nonnull : 0 ; \ + } +#define KDAB_USING_SAFE_BOOL_OPERATOR( Class ) \ + using Class::operator Class::unspecified_bool_type; +#endif + +#define KDTOOLS_MAKE_RELATION_OPERATORS( Class, linkage ) \ + linkage bool operator>( const Class & lhs, const Class & rhs ) { \ + return operator<( rhs, lhs ); \ + } \ + linkage bool operator!=( const Class & lhs, const Class & rhs ) { \ + return !operator==( lhs, rhs ); \ + } \ + linkage bool operator<=( const Class & lhs, const Class & rhs ) { \ + return !operator>( lhs, rhs ); \ + } \ + linkage bool operator>=( const Class & lhs, const Class & rhs ) { \ + return !operator<( lhs, rhs ); \ + } + +template +inline T & __kdtools__dereference_for_methodcall( T & o ) { + return o; +} + +template +inline T & __kdtools__dereference_for_methodcall( T * o ) { + return *o; +} + +#define KDAB_SET_OBJECT_NAME( x ) __kdtools__dereference_for_methodcall( x ).setObjectName( QLatin1String( #x ) ) + +KDTOOLSCORE_EXPORT bool _kdCheckQtVersion_impl( int major, int minor=0, int patchlevel=0 ); +static inline bool kdCheckQtVersion( unsigned int major, unsigned int minor=0, unsigned int patchlevel=0 ) { + return (major<<16|minor<<8|patchlevel) <= static_cast(QT_VERSION) + || _kdCheckQtVersion_impl( major, minor, patchlevel ); +} + +#define KDTOOLS_DECLARE_PRIVATE_BASE( Class ) \ +protected: \ + class Private; \ + Private * d_func() { return _d; } \ + const Private * d_func() const { return _d; } \ + Class( Private * _d_, bool b ) : _d( _d_ ) { init(b); } \ +private: \ + void init(bool); \ +private: \ + Private * _d + +#define KDTOOLS_DECLARE_PRIVATE_DERIVED( Class, Base ) \ +protected: \ + class Private; \ + Private * d_func() { \ + return reinterpret_cast( Base::d_func() ); \ + } \ + const Private * d_func() const { \ + return reinterpret_cast( Base::d_func() ); \ + } \ + Class( Private * _d_, bool b ) \ + : Base( reinterpret_cast(_d_), b ) { init(b); } \ +private: \ + void init(bool) + + +#endif /* __KDTOOLS_KDTOOLSGLOBAL_H__ */ + diff --git a/src/libtomahawk/kdsingleapplicationguard/license-gpl b/src/libtomahawk/kdsingleapplicationguard/license-gpl new file mode 100644 index 000000000..332ed973c --- /dev/null +++ b/src/libtomahawk/kdsingleapplicationguard/license-gpl @@ -0,0 +1,349 @@ + + The KD Tools Library is Copyright (C) 2001-2003 Klarälvdalens Datakonsult AB. + + You may use, distribute and copy the KD Tools Library under the terms of + GNU General Public License version 2, which is displayed below. + +------------------------------------------------------------------------- + + GNU GENERAL PUBLIC LICENSE + Version 2, June 1991 + + Copyright (C) 1989, 1991 Free Software Foundation, Inc. + 675 Mass Ave, Cambridge, MA 02139, USA + Everyone is permitted to copy and distribute verbatim copies + of this license document, but changing it is not allowed. + + Preamble + + The licenses for most software are designed to take away your +freedom to share and change it. By contrast, the GNU General Public +License is intended to guarantee your freedom to share and change free +software--to make sure the software is free for all its users. This +General Public License applies to most of the Free Software +Foundation's software and to any other program whose authors commit to +using it. (Some other Free Software Foundation software is covered by +the GNU Library General Public License instead.) You can apply it to +your programs, too. + + When we speak of free software, we are referring to freedom, not +price. Our General Public Licenses are designed to make sure that you +have the freedom to distribute copies of free software (and charge for +this service if you wish), that you receive source code or can get it +if you want it, that you can change the software or use pieces of it +in new free programs; and that you know you can do these things. + + To protect your rights, we need to make restrictions that forbid +anyone to deny you these rights or to ask you to surrender the rights. +These restrictions translate to certain responsibilities for you if you +distribute copies of the software, or if you modify it. + + For example, if you distribute copies of such a program, whether +gratis or for a fee, you must give the recipients all the rights that +you have. You must make sure that they, too, receive or can get the +source code. And you must show them these terms so they know their +rights. + + We protect your rights with two steps: (1) copyright the software, and +(2) offer you this license which gives you legal permission to copy, +distribute and/or modify the software. + + Also, for each author's protection and ours, we want to make certain +that everyone understands that there is no warranty for this free +software. If the software is modified by someone else and passed on, we +want its recipients to know that what they have is not the original, so +that any problems introduced by others will not reflect on the original +authors' reputations. + + Finally, any free program is threatened constantly by software +patents. We wish to avoid the danger that redistributors of a free +program will individually obtain patent licenses, in effect making the +program proprietary. To prevent this, we have made it clear that any +patent must be licensed for everyone's free use or not licensed at all. + + The precise terms and conditions for copying, distribution and +modification follow. + + GNU GENERAL PUBLIC LICENSE + TERMS AND CONDITIONS FOR COPYING, DISTRIBUTION AND MODIFICATION + + 0. This License applies to any program or other work which contains +a notice placed by the copyright holder saying it may be distributed +under the terms of this General Public License. The "Program", below, +refers to any such program or work, and a "work based on the Program" +means either the Program or any derivative work under copyright law: +that is to say, a work containing the Program or a portion of it, +either verbatim or with modifications and/or translated into another +language. (Hereinafter, translation is included without limitation in +the term "modification".) Each licensee is addressed as "you". + +Activities other than copying, distribution and modification are not +covered by this License; they are outside its scope. The act of +running the Program is not restricted, and the output from the Program +is covered only if its contents constitute a work based on the +Program (independent of having been made by running the Program). +Whether that is true depends on what the Program does. + + 1. You may copy and distribute verbatim copies of the Program's +source code as you receive it, in any medium, provided that you +conspicuously and appropriately publish on each copy an appropriate +copyright notice and disclaimer of warranty; keep intact all the +notices that refer to this License and to the absence of any warranty; +and give any other recipients of the Program a copy of this License +along with the Program. + +You may charge a fee for the physical act of transferring a copy, and +you may at your option offer warranty protection in exchange for a fee. + + 2. You may modify your copy or copies of the Program or any portion +of it, thus forming a work based on the Program, and copy and +distribute such modifications or work under the terms of Section 1 +above, provided that you also meet all of these conditions: + + a) You must cause the modified files to carry prominent notices + stating that you changed the files and the date of any change. + + b) You must cause any work that you distribute or publish, that in + whole or in part contains or is derived from the Program or any + part thereof, to be licensed as a whole at no charge to all third + parties under the terms of this License. + + c) If the modified program normally reads commands interactively + when run, you must cause it, when started running for such + interactive use in the most ordinary way, to print or display an + announcement including an appropriate copyright notice and a + notice that there is no warranty (or else, saying that you provide + a warranty) and that users may redistribute the program under + these conditions, and telling the user how to view a copy of this + License. (Exception: if the Program itself is interactive but + does not normally print such an announcement, your work based on + the Program is not required to print an announcement.) + +These requirements apply to the modified work as a whole. If +identifiable sections of that work are not derived from the Program, +and can be reasonably considered independent and separate works in +themselves, then this License, and its terms, do not apply to those +sections when you distribute them as separate works. But when you +distribute the same sections as part of a whole which is a work based +on the Program, the distribution of the whole must be on the terms of +this License, whose permissions for other licensees extend to the +entire whole, and thus to each and every part regardless of who wrote it. + +Thus, it is not the intent of this section to claim rights or contest +your rights to work written entirely by you; rather, the intent is to +exercise the right to control the distribution of derivative or +collective works based on the Program. + +In addition, mere aggregation of another work not based on the Program +with the Program (or with a work based on the Program) on a volume of +a storage or distribution medium does not bring the other work under +the scope of this License. + + 3. You may copy and distribute the Program (or a work based on it, +under Section 2) in object code or executable form under the terms of +Sections 1 and 2 above provided that you also do one of the following: + + a) Accompany it with the complete corresponding machine-readable + source code, which must be distributed under the terms of Sections + 1 and 2 above on a medium customarily used for software interchange; or, + + b) Accompany it with a written offer, valid for at least three + years, to give any third party, for a charge no more than your + cost of physically performing source distribution, a complete + machine-readable copy of the corresponding source code, to be + distributed under the terms of Sections 1 and 2 above on a medium + customarily used for software interchange; or, + + c) Accompany it with the information you received as to the offer + to distribute corresponding source code. (This alternative is + allowed only for noncommercial distribution and only if you + received the program in object code or executable form with such + an offer, in accord with Subsection b above.) + +The source code for a work means the preferred form of the work for +making modifications to it. For an executable work, complete source +code means all the source code for all modules it contains, plus any +associated interface definition files, plus the scripts used to +control compilation and installation of the executable. However, as a +special exception, the source code distributed need not include +anything that is normally distributed (in either source or binary +form) with the major components (compiler, kernel, and so on) of the +operating system on which the executable runs, unless that component +itself accompanies the executable. + +If distribution of executable or object code is made by offering +access to copy from a designated place, then offering equivalent +access to copy the source code from the same place counts as +distribution of the source code, even though third parties are not +compelled to copy the source along with the object code. + + 4. You may not copy, modify, sublicense, or distribute the Program +except as expressly provided under this License. Any attempt +otherwise to copy, modify, sublicense or distribute the Program is +void, and will automatically terminate your rights under this License. +However, parties who have received copies, or rights, from you under +this License will not have their licenses terminated so long as such +parties remain in full compliance. + + 5. You are not required to accept this License, since you have not +signed it. However, nothing else grants you permission to modify or +distribute the Program or its derivative works. These actions are +prohibited by law if you do not accept this License. Therefore, by +modifying or distributing the Program (or any work based on the +Program), you indicate your acceptance of this License to do so, and +all its terms and conditions for copying, distributing or modifying +the Program or works based on it. + + 6. Each time you redistribute the Program (or any work based on the +Program), the recipient automatically receives a license from the +original licensor to copy, distribute or modify the Program subject to +these terms and conditions. You may not impose any further +restrictions on the recipients' exercise of the rights granted herein. +You are not responsible for enforcing compliance by third parties to +this License. + + 7. If, as a consequence of a court judgment or allegation of patent +infringement or for any other reason (not limited to patent issues), +conditions are imposed on you (whether by court order, agreement or +otherwise) that contradict the conditions of this License, they do not +excuse you from the conditions of this License. If you cannot +distribute so as to satisfy simultaneously your obligations under this +License and any other pertinent obligations, then as a consequence you +may not distribute the Program at all. For example, if a patent +license would not permit royalty-free redistribution of the Program by +all those who receive copies directly or indirectly through you, then +the only way you could satisfy both it and this License would be to +refrain entirely from distribution of the Program. + +If any portion of this section is held invalid or unenforceable under +any particular circumstance, the balance of the section is intended to +apply and the section as a whole is intended to apply in other +circumstances. + +It is not the purpose of this section to induce you to infringe any +patents or other property right claims or to contest validity of any +such claims; this section has the sole purpose of protecting the +integrity of the free software distribution system, which is +implemented by public license practices. Many people have made +generous contributions to the wide range of software distributed +through that system in reliance on consistent application of that +system; it is up to the author/donor to decide if he or she is willing +to distribute software through any other system and a licensee cannot +impose that choice. + +This section is intended to make thoroughly clear what is believed to +be a consequence of the rest of this License. + + 8. If the distribution and/or use of the Program is restricted in +certain countries either by patents or by copyrighted interfaces, the +original copyright holder who places the Program under this License +may add an explicit geographical distribution limitation excluding +those countries, so that distribution is permitted only in or among +countries not thus excluded. In such case, this License incorporates +the limitation as if written in the body of this License. + + 9. The Free Software Foundation may publish revised and/or new versions +of the General Public License from time to time. Such new versions will +be similar in spirit to the present version, but may differ in detail to +address new problems or concerns. + +Each version is given a distinguishing version number. If the Program +specifies a version number of this License which applies to it and "any +later version", you have the option of following the terms and conditions +either of that version or of any later version published by the Free +Software Foundation. If the Program does not specify a version number of +this License, you may choose any version ever published by the Free Software +Foundation. + + 10. If you wish to incorporate parts of the Program into other free +programs whose distribution conditions are different, write to the author +to ask for permission. For software which is copyrighted by the Free +Software Foundation, write to the Free Software Foundation; we sometimes +make exceptions for this. Our decision will be guided by the two goals +of preserving the free status of all derivatives of our free software and +of promoting the sharing and reuse of software generally. + + NO WARRANTY + + 11. BECAUSE THE PROGRAM IS LICENSED FREE OF CHARGE, THERE IS NO WARRANTY +FOR THE PROGRAM, TO THE EXTENT PERMITTED BY APPLICABLE LAW. EXCEPT WHEN +OTHERWISE STATED IN WRITING THE COPYRIGHT HOLDERS AND/OR OTHER PARTIES +PROVIDE THE PROGRAM "AS IS" WITHOUT WARRANTY OF ANY KIND, EITHER EXPRESSED +OR IMPLIED, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF +MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE. THE ENTIRE RISK AS +TO THE QUALITY AND PERFORMANCE OF THE PROGRAM IS WITH YOU. SHOULD THE +PROGRAM PROVE DEFECTIVE, YOU ASSUME THE COST OF ALL NECESSARY SERVICING, +REPAIR OR CORRECTION. + + 12. IN NO EVENT UNLESS REQUIRED BY APPLICABLE LAW OR AGREED TO IN WRITING +WILL ANY COPYRIGHT HOLDER, OR ANY OTHER PARTY WHO MAY MODIFY AND/OR +REDISTRIBUTE THE PROGRAM AS PERMITTED ABOVE, BE LIABLE TO YOU FOR DAMAGES, +INCLUDING ANY GENERAL, SPECIAL, INCIDENTAL OR CONSEQUENTIAL DAMAGES ARISING +OUT OF THE USE OR INABILITY TO USE THE PROGRAM (INCLUDING BUT NOT LIMITED +TO LOSS OF DATA OR DATA BEING RENDERED INACCURATE OR LOSSES SUSTAINED BY +YOU OR THIRD PARTIES OR A FAILURE OF THE PROGRAM TO OPERATE WITH ANY OTHER +PROGRAMS), EVEN IF SUCH HOLDER OR OTHER PARTY HAS BEEN ADVISED OF THE +POSSIBILITY OF SUCH DAMAGES. + + END OF TERMS AND CONDITIONS + + Appendix: How to Apply These Terms to Your New Programs + + If you develop a new program, and you want it to be of the greatest +possible use to the public, the best way to achieve this is to make it +free software which everyone can redistribute and change under these terms. + + To do so, attach the following notices to the program. It is safest +to attach them to the start of each source file to most effectively +convey the exclusion of warranty; and each file should have at least +the "copyright" line and a pointer to where the full notice is found. + + + Copyright (C) 19yy + + 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., 675 Mass Ave, Cambridge, MA 02139, USA. + +Also add information on how to contact you by electronic and paper mail. + +If the program is interactive, make it output a short notice like this +when it starts in an interactive mode: + + Gnomovision version 69, Copyright (C) 19yy name of author + Gnomovision comes with ABSOLUTELY NO WARRANTY; for details type `show w'. + This is free software, and you are welcome to redistribute it + under certain conditions; type `show c' for details. + +The hypothetical commands `show w' and `show c' should show the appropriate +parts of the General Public License. Of course, the commands you use may +be called something other than `show w' and `show c'; they could even be +mouse-clicks or menu items--whatever suits your program. + +You should also get your employer (if you work as a programmer) or your +school, if any, to sign a "copyright disclaimer" for the program, if +necessary. Here is a sample; alter the names: + + Yoyodyne, Inc., hereby disclaims all copyright interest in the program + `Gnomovision' (which makes passes at compilers) written by James Hacker. + + , 1 April 1989 + Ty Coon, President of Vice + +This General Public License does not permit incorporating your program into +proprietary programs. If your program is a subroutine library, you may +consider it more useful to permit linking proprietary applications with the +library. If this is what you want to do, use the GNU Library General +Public License instead of this License. + +------------------------------------------------------------------------- diff --git a/src/libtomahawk/kdsingleapplicationguard/pimpl_ptr.cpp b/src/libtomahawk/kdsingleapplicationguard/pimpl_ptr.cpp new file mode 100644 index 000000000..3045ebc2a --- /dev/null +++ b/src/libtomahawk/kdsingleapplicationguard/pimpl_ptr.cpp @@ -0,0 +1,203 @@ +#include "pimpl_ptr.h" + +/*! + \class pimpl_ptr: + \ingroup core smartptr + \brief Owning pointer for private implementations + \since_c 2.1 + + (The exception safety of this class has not been evaluated yet.) + + pimpl_ptr is a smart immutable pointer, which owns the contained object. Unlike other smart pointers, + it creates a standard constructed object when instanciated via the + \link pimpl_ptr() standard constructor\endlink. + Additionally, pimpl_ptr respects constness of the pointer object and returns \c const \c T* for + a const pimpl_ptr object. + + The content of a pimpl_ptr cannot be changed during it's lifetime. + + \section general-use General Use + + The general use case of pimpl_ptr is the "Pimpl Idiom", i.e. hiding the private implementation of a class + from the user's compiler which see \c MyClass as + + \code + class MyClass + { + public: + MyClass(); + ~MyClass(); + + // public class API + int value() const; + + private: + class Private; // defined later + kdtools::pimpl_ptr< Private > d; + }; + \endcode + + but not the private parts of it. These can only be seen (and accessed) by the code knowing \c MyClass::Private: + + \code + class MyClass::Private + { + public: + int value; + }; + + MyClass::MyClass() + { + // d was automatically filled with new Private + d->value = 42; + } + + MyClass::~MyClass() + { + // the content of d gets deleted automatically + } + + int MyClass::value() const + { + // access the private part: + // since MyClass::value() is const, the returned pointee is const, too + return d->value; + } + \endcode + +*/ + +/*! + \fn pimpl_ptr::pimpl_ptr() + + Default constructor. Constructs a pimpl_tr that contains (owns) a standard constructed + instance of \c T. + + \post \c *this owns a new object. +*/ + +/*! + \fn pimpl_ptr::pimpl_ptr( T * t ) + + Constructor. Constructs a pimpl_ptr that contains (owns) \a t. + + \post get() == obj +*/ + +/*! + \fn pimpl_ptr::~pimpl_ptr() + + Destructor. + + \post The object previously owned by \c *this has been deleted. +*/ + +/*! + \fn const T * pimpl_ptr::get() const + + \returns a const pointer to the contained (owned) object. + \overload +*/ + +/*! + \fn T * pimpl_ptr::get() + + \returns a pointer to the contained (owned) object. +*/ + +/*! + \fn const T & pimpl_ptr::operator*() const + + Dereference operator. Returns \link get() *get()\endlink. + \overload +*/ + +/*! + \fn T & pimpl_ptr::operator*() + + Dereference operator. Returns \link get() *get()\endlink. +*/ + +/*! + \fn const T * pimpl_ptr::operator->() const + + Member-by-pointer operator. Returns get(). + \overload +*/ + +/*! + \fn T * pimpl_ptr::operator->() + + Member-by-pointer operator. Returns get(). +*/ + +#ifdef KDTOOLSCORE_UNITTESTS + +#include + +#include +#include + +namespace +{ + struct ConstTester + { + bool isConst() + { + return false; + } + + bool isConst() const + { + return true; + } + }; +} + +KDAB_UNITTEST_SIMPLE( pimpl_ptr, "kdcoretools" ) { + + { + kdtools::pimpl_ptr< QObject > p; + assertNotNull( p.get() ); + assertNull( p->parent() ); + } + + + { + QPointer< QObject > o; + { + kdtools::pimpl_ptr< QObject > qobject( new QObject ); + o = qobject.get(); + assertEqual( o, qobject.operator->() ); + assertEqual( o, &(qobject.operator*()) ); + } + assertNull( o ); + } + + { + const kdtools::pimpl_ptr< QObject > qobject( new QObject ); + const QObject* o = qobject.get(); + assertEqual( o, qobject.operator->() ); + assertEqual( o, &(qobject.operator*()) ); + } + + { + kdtools::pimpl_ptr< QObject > o1; + assertTrue( o1 ); + kdtools::pimpl_ptr< QObject > o2( 0 ); + assertFalse( o2 ); + } + + { + const kdtools::pimpl_ptr< ConstTester > o1; + kdtools::pimpl_ptr< ConstTester > o2; + assertTrue( o1->isConst() ); + assertFalse( o2->isConst() ); + assertTrue( (*o1).isConst() ); + assertFalse( (*o2).isConst() ); + assertTrue( o1.get()->isConst() ); + assertFalse( o2.get()->isConst() ); + } +} + +#endif // KDTOOLSCORE_UNITTESTS diff --git a/src/libtomahawk/kdsingleapplicationguard/pimpl_ptr.h b/src/libtomahawk/kdsingleapplicationguard/pimpl_ptr.h new file mode 100644 index 000000000..7b7f36839 --- /dev/null +++ b/src/libtomahawk/kdsingleapplicationguard/pimpl_ptr.h @@ -0,0 +1,44 @@ +#ifndef __KDTOOLSCORE__PIMPL_PTR_H__ +#define __KDTOOLSCORE__PIMPL_PTR_H__ + +#include "kdtoolsglobal.h" + +#ifndef DOXYGEN_RUN +namespace kdtools { +#endif + + template + class pimpl_ptr { + KDAB_DISABLE_COPY( pimpl_ptr ); + T * d; + public: + pimpl_ptr() : d( new T ) {} + explicit pimpl_ptr( T * t ) : d( t ) {} + ~pimpl_ptr() { delete d; d = 0; } + + T * get() { return d; } + const T * get() const { return d; } + + T * operator->() { return get(); } + const T * operator->() const { return get(); } + + T & operator*() { return *get(); } + const T & operator*() const { return *get(); } + + KDAB_IMPLEMENT_SAFE_BOOL_OPERATOR( get() ) + }; + + // these are not implemented, so's we can catch their use at + // link-time. Leaving them undeclared would open up a comparison + // via operator unspecified-bool-type(). + template + void operator==( const pimpl_ptr &, const pimpl_ptr & ); + template + void operator!=( const pimpl_ptr &, const pimpl_ptr & ); + +#ifndef DOXYGEN_RUN +} // namespace kdtools +#endif + +#endif /* __KDTOOLSCORE__PIMPL_PTR_H__ */ + diff --git a/src/libtomahawk/qtsingleapp/qtlocalpeer.cpp b/src/libtomahawk/qtsingleapp/qtlocalpeer.cpp deleted file mode 100644 index 382d182dc..000000000 --- a/src/libtomahawk/qtsingleapp/qtlocalpeer.cpp +++ /dev/null @@ -1,199 +0,0 @@ -/**************************************************************************** -** -** Copyright (C) 2010 Nokia Corporation and/or its subsidiary(-ies). -** All rights reserved. -** -** Contact: Nokia Corporation (qt-info@nokia.com) -** -** This file is part of a Qt Solutions component. -** -** You may use this file under the terms of the BSD license as follows: -** -** "Redistribution and use in source and binary forms, with or without -** modification, are permitted provided that the following conditions are -** met: -** * Redistributions of source code must retain the above copyright -** notice, this list of conditions and the following disclaimer. -** * Redistributions in binary form must reproduce the above copyright -** notice, this list of conditions and the following disclaimer in -** the documentation and/or other materials provided with the -** distribution. -** * Neither the name of Nokia Corporation and its Subsidiary(-ies) nor -** the names of its contributors may be used to endorse or promote -** products derived from this software without specific prior written -** permission. -** -** THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS -** "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT -** LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR -** A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT -** OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, -** SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT -** LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, -** DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY -** THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT -** (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE -** OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE." -** -****************************************************************************/ - - -#include "qtlocalpeer.h" -#include -#include - -#if defined(Q_OS_WIN) -#include -#include -typedef BOOL(WINAPI*PProcessIdToSessionId)(DWORD,DWORD*); -static PProcessIdToSessionId pProcessIdToSessionId = 0; -#endif -#if defined(Q_OS_UNIX) -#include -#endif - -namespace QtLP_Private { -#include "qtlockedfile.cpp" -#if defined(Q_OS_WIN) -#include "qtlockedfile_win.cpp" -#else -#include "qtlockedfile_unix.cpp" -#endif -} - -const char* QtLocalPeer::ack = "ack"; - -QtLocalPeer::QtLocalPeer(QObject* parent, const QString &appId) - : QObject(parent), id(appId) -{ - QString prefix = id; - if (id.isEmpty()) { - id = QCoreApplication::applicationFilePath(); -#if defined(Q_OS_WIN) - id = id.toLower(); -#endif - prefix = id.section(QLatin1Char('/'), -1); - } - prefix.remove(QRegExp("[^a-zA-Z]")); - prefix.truncate(6); - - QByteArray idc = id.toUtf8(); - quint16 idNum = qChecksum(idc.constData(), idc.size()); - socketName = QLatin1String("qtsingleapp-") + prefix - + QLatin1Char('-') + QString::number(idNum, 16); - -#if defined(Q_OS_WIN) - if (!pProcessIdToSessionId) { - QLibrary lib("kernel32"); - pProcessIdToSessionId = (PProcessIdToSessionId)lib.resolve("ProcessIdToSessionId"); - } - if (pProcessIdToSessionId) { - DWORD sessionId = 0; - pProcessIdToSessionId(GetCurrentProcessId(), &sessionId); - socketName += QLatin1Char('-') + QString::number(sessionId, 16); - } -#else - socketName += QLatin1Char('-') + QString::number(::getuid(), 16); -#endif - - server = new QLocalServer(this); - QString lockName = QDir(QDir::tempPath()).absolutePath() - + QLatin1Char('/') + socketName - + QLatin1String("-lockfile"); - lockFile.setFileName(lockName); - lockFile.open(QIODevice::ReadWrite); -} - - - -bool QtLocalPeer::isClient() -{ - if (lockFile.isLocked()) - return false; - - if (!lockFile.lock(QtLP_Private::QtLockedFile::WriteLock, false)) - return true; - - bool res = server->listen(socketName); -#if defined(Q_OS_UNIX) && (QT_VERSION >= QT_VERSION_CHECK(4,5,0)) - // ### Workaround - if (!res && server->serverError() == QAbstractSocket::AddressInUseError) { - QFile::remove(QDir::cleanPath(QDir::tempPath())+QLatin1Char('/')+socketName); - res = server->listen(socketName); - } -#endif - if (!res) - qWarning("QtSingleCoreApplication: listen on local socket failed, %s", qPrintable(server->errorString())); - QObject::connect(server, SIGNAL(newConnection()), SLOT(receiveConnection())); - return false; -} - - -bool QtLocalPeer::sendMessage(const QString &message, int timeout) -{ - if (!isClient()) - return false; - - QLocalSocket socket; - bool connOk = false; - for(int i = 0; i < 2; i++) { - // Try twice, in case the other instance is just starting up - socket.connectToServer(socketName); - connOk = socket.waitForConnected(timeout/2); - if (connOk || i) - break; - int ms = 250; -#if defined(Q_OS_WIN) - Sleep(DWORD(ms)); -#else - struct timespec ts = { ms / 1000, (ms % 1000) * 1000 * 1000 }; - nanosleep(&ts, NULL); -#endif - } - if (!connOk) - return false; - - QByteArray uMsg(message.toUtf8()); - QDataStream ds(&socket); - ds.writeBytes(uMsg.constData(), uMsg.size()); - bool res = socket.waitForBytesWritten(timeout); - if (res) { - res &= socket.waitForReadyRead(timeout); // wait for ack - if (res) - res &= (socket.read(qstrlen(ack)) == ack); - } - return res; -} - - -void QtLocalPeer::receiveConnection() -{ - QLocalSocket* socket = server->nextPendingConnection(); - if (!socket) - return; - - while (socket->bytesAvailable() < (int)sizeof(quint32)) - socket->waitForReadyRead(); - QDataStream ds(socket); - QByteArray uMsg; - quint32 remaining; - ds >> remaining; - uMsg.resize(remaining); - int got = 0; - char* uMsgBuf = uMsg.data(); - do { - got = ds.readRawData(uMsgBuf, remaining); - remaining -= got; - uMsgBuf += got; - } while (remaining && got >= 0 && socket->waitForReadyRead(2000)); - if (got < 0) { - qWarning("QtLocalPeer: Message reception failed %s", socket->errorString().toLatin1().constData()); - delete socket; - return; - } - QString message(QString::fromUtf8(uMsg)); - socket->write(ack, qstrlen(ack)); - socket->waitForBytesWritten(1000); - delete socket; - emit messageReceived(message); //### (might take a long time to return) -} diff --git a/src/libtomahawk/qtsingleapp/qtlocalpeer.h b/src/libtomahawk/qtsingleapp/qtlocalpeer.h deleted file mode 100644 index 869af2ac2..000000000 --- a/src/libtomahawk/qtsingleapp/qtlocalpeer.h +++ /dev/null @@ -1,72 +0,0 @@ -/**************************************************************************** -** -** Copyright (C) 2010 Nokia Corporation and/or its subsidiary(-ies). -** All rights reserved. -** -** Contact: Nokia Corporation (qt-info@nokia.com) -** -** This file is part of a Qt Solutions component. -** -** You may use this file under the terms of the BSD license as follows: -** -** "Redistribution and use in source and binary forms, with or without -** modification, are permitted provided that the following conditions are -** met: -** * Redistributions of source code must retain the above copyright -** notice, this list of conditions and the following disclaimer. -** * Redistributions in binary form must reproduce the above copyright -** notice, this list of conditions and the following disclaimer in -** the documentation and/or other materials provided with the -** distribution. -** * Neither the name of Nokia Corporation and its Subsidiary(-ies) nor -** the names of its contributors may be used to endorse or promote -** products derived from this software without specific prior written -** permission. -** -** THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS -** "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT -** LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR -** A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT -** OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, -** SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT -** LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, -** DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY -** THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT -** (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE -** OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE." -** -****************************************************************************/ - - -#include -#include -#include - -#include "qtlockedfile.h" - -class QtLocalPeer : public QObject -{ - Q_OBJECT - -public: - QtLocalPeer(QObject *parent = 0, const QString &appId = QString()); - bool isClient(); - bool sendMessage(const QString &message, int timeout); - QString applicationId() const - { return id; } - -Q_SIGNALS: - void messageReceived(const QString &message); - -protected Q_SLOTS: - void receiveConnection(); - -protected: - QString id; - QString socketName; - QLocalServer* server; - QtLP_Private::QtLockedFile lockFile; - -private: - static const char* ack; -}; diff --git a/src/libtomahawk/qtsingleapp/qtlockedfile.cpp b/src/libtomahawk/qtsingleapp/qtlockedfile.cpp deleted file mode 100644 index 3e73ba652..000000000 --- a/src/libtomahawk/qtsingleapp/qtlockedfile.cpp +++ /dev/null @@ -1,192 +0,0 @@ -/**************************************************************************** -** -** Copyright (C) 2010 Nokia Corporation and/or its subsidiary(-ies). -** All rights reserved. -** -** Contact: Nokia Corporation (qt-info@nokia.com) -** -** This file is part of a Qt Solutions component. -** -** You may use this file under the terms of the BSD license as follows: -** -** "Redistribution and use in source and binary forms, with or without -** modification, are permitted provided that the following conditions are -** met: -** * Redistributions of source code must retain the above copyright -** notice, this list of conditions and the following disclaimer. -** * Redistributions in binary form must reproduce the above copyright -** notice, this list of conditions and the following disclaimer in -** the documentation and/or other materials provided with the -** distribution. -** * Neither the name of Nokia Corporation and its Subsidiary(-ies) nor -** the names of its contributors may be used to endorse or promote -** products derived from this software without specific prior written -** permission. -** -** THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS -** "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT -** LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR -** A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT -** OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, -** SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT -** LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, -** DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY -** THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT -** (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE -** OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE." -** -****************************************************************************/ - -#include "qtlockedfile.h" - -/*! - \class QtLockedFile - - \brief The QtLockedFile class extends QFile with advisory locking - functions. - - A file may be locked in read or write mode. Multiple instances of - \e QtLockedFile, created in multiple processes running on the same - machine, may have a file locked in read mode. Exactly one instance - may have it locked in write mode. A read and a write lock cannot - exist simultaneously on the same file. - - The file locks are advisory. This means that nothing prevents - another process from manipulating a locked file using QFile or - file system functions offered by the OS. Serialization is only - guaranteed if all processes that access the file use - QLockedFile. Also, while holding a lock on a file, a process - must not open the same file again (through any API), or locks - can be unexpectedly lost. - - The lock provided by an instance of \e QtLockedFile is released - whenever the program terminates. This is true even when the - program crashes and no destructors are called. -*/ - -/*! \enum QtLockedFile::LockMode - - This enum describes the available lock modes. - - \value ReadLock A read lock. - \value WriteLock A write lock. - \value NoLock Neither a read lock nor a write lock. -*/ - -/*! - Constructs an unlocked \e QtLockedFile object. This constructor - behaves in the same way as \e QFile::QFile(). - - \sa QFile::QFile() -*/ -QtLockedFile::QtLockedFile() - : QFile() -{ -#ifdef Q_OS_WIN - wmutex = 0; - rmutex = 0; -#endif - m_lock_mode = NoLock; -} - -/*! - Constructs an unlocked QtLockedFile object with file \a name. This - constructor behaves in the same way as \e QFile::QFile(const - QString&). - - \sa QFile::QFile() -*/ -QtLockedFile::QtLockedFile(const QString &name) - : QFile(name) -{ -#ifdef Q_OS_WIN - wmutex = 0; - rmutex = 0; -#endif - m_lock_mode = NoLock; -} - -/*! - Opens the file in OpenMode \a mode. - - This is identical to QFile::open(), with the one exception that the - Truncate mode flag is disallowed. Truncation would conflict with the - advisory file locking, since the file would be modified before the - write lock is obtained. If truncation is required, use resize(0) - after obtaining the write lock. - - Returns true if successful; otherwise false. - - \sa QFile::open(), QFile::resize() -*/ -bool QtLockedFile::open(OpenMode mode) -{ - if (mode & QIODevice::Truncate) { - qWarning("QtLockedFile::open(): Truncate mode not allowed."); - return false; - } - return QFile::open(mode); -} - -/*! - Returns \e true if this object has a in read or write lock; - otherwise returns \e false. - - \sa lockMode() -*/ -bool QtLockedFile::isLocked() const -{ - return m_lock_mode != NoLock; -} - -/*! - Returns the type of lock currently held by this object, or \e - QtLockedFile::NoLock. - - \sa isLocked() -*/ -QtLockedFile::LockMode QtLockedFile::lockMode() const -{ - return m_lock_mode; -} - -/*! - \fn bool QtLockedFile::lock(LockMode mode, bool block = true) - - Obtains a lock of type \a mode. The file must be opened before it - can be locked. - - If \a block is true, this function will block until the lock is - aquired. If \a block is false, this function returns \e false - immediately if the lock cannot be aquired. - - If this object already has a lock of type \a mode, this function - returns \e true immediately. If this object has a lock of a - different type than \a mode, the lock is first released and then a - new lock is obtained. - - This function returns \e true if, after it executes, the file is - locked by this object, and \e false otherwise. - - \sa unlock(), isLocked(), lockMode() -*/ - -/*! - \fn bool QtLockedFile::unlock() - - Releases a lock. - - If the object has no lock, this function returns immediately. - - This function returns \e true if, after it executes, the file is - not locked by this object, and \e false otherwise. - - \sa lock(), isLocked(), lockMode() -*/ - -/*! - \fn QtLockedFile::~QtLockedFile() - - Destroys the \e QtLockedFile object. If any locks were held, they - are released. -*/ diff --git a/src/libtomahawk/qtsingleapp/qtlockedfile.h b/src/libtomahawk/qtsingleapp/qtlockedfile.h deleted file mode 100644 index 07a42bffb..000000000 --- a/src/libtomahawk/qtsingleapp/qtlockedfile.h +++ /dev/null @@ -1,96 +0,0 @@ -/**************************************************************************** -** -** Copyright (C) 2010 Nokia Corporation and/or its subsidiary(-ies). -** All rights reserved. -** -** Contact: Nokia Corporation (qt-info@nokia.com) -** -** This file is part of a Qt Solutions component. -** -** You may use this file under the terms of the BSD license as follows: -** -** "Redistribution and use in source and binary forms, with or without -** modification, are permitted provided that the following conditions are -** met: -** * Redistributions of source code must retain the above copyright -** notice, this list of conditions and the following disclaimer. -** * Redistributions in binary form must reproduce the above copyright -** notice, this list of conditions and the following disclaimer in -** the documentation and/or other materials provided with the -** distribution. -** * Neither the name of Nokia Corporation and its Subsidiary(-ies) nor -** the names of its contributors may be used to endorse or promote -** products derived from this software without specific prior written -** permission. -** -** THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS -** "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT -** LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR -** A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT -** OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, -** SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT -** LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, -** DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY -** THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT -** (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE -** OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE." -** -****************************************************************************/ - -#ifndef QTLOCKEDFILE_H -#define QTLOCKEDFILE_H - -#include -#ifdef Q_OS_WIN -#include -#endif - -#if defined(Q_WS_WIN) -# if !defined(QT_QTLOCKEDFILE_EXPORT) && !defined(QT_QTLOCKEDFILE_IMPORT) -# define QT_QTLOCKEDFILE_EXPORT -# elif defined(QT_QTLOCKEDFILE_IMPORT) -# if defined(QT_QTLOCKEDFILE_EXPORT) -# undef QT_QTLOCKEDFILE_EXPORT -# endif -# define QT_QTLOCKEDFILE_EXPORT __declspec(dllimport) -# elif defined(QT_QTLOCKEDFILE_EXPORT) -# undef QT_QTLOCKEDFILE_EXPORT -# define QT_QTLOCKEDFILE_EXPORT __declspec(dllexport) -# endif -#else -# define QT_QTLOCKEDFILE_EXPORT -#endif - -namespace QtLP_Private { - -class QT_QTLOCKEDFILE_EXPORT QtLockedFile : public QFile -{ -public: - enum LockMode { NoLock = 0, ReadLock, WriteLock }; - - QtLockedFile(); - QtLockedFile(const QString &name); - ~QtLockedFile(); - - bool open(OpenMode mode); - - bool lock(LockMode mode, bool block = true); - bool unlock(); - bool isLocked() const; - LockMode lockMode() const; - -private: -#ifdef Q_OS_WIN - Qt::HANDLE wmutex; - Qt::HANDLE rmutex; - QVector rmutexes; - QString mutexname; - - Qt::HANDLE getMutexHandle(int idx, bool doCreate); - bool waitMutex(Qt::HANDLE mutex, bool doBlock); - -#endif - LockMode m_lock_mode; -}; -} -#endif diff --git a/src/libtomahawk/qtsingleapp/qtlockedfile_unix.cpp b/src/libtomahawk/qtsingleapp/qtlockedfile_unix.cpp deleted file mode 100644 index 715c7d9b1..000000000 --- a/src/libtomahawk/qtsingleapp/qtlockedfile_unix.cpp +++ /dev/null @@ -1,114 +0,0 @@ -/**************************************************************************** -** -** Copyright (C) 2010 Nokia Corporation and/or its subsidiary(-ies). -** All rights reserved. -** -** Contact: Nokia Corporation (qt-info@nokia.com) -** -** This file is part of a Qt Solutions component. -** -** You may use this file under the terms of the BSD license as follows: -** -** "Redistribution and use in source and binary forms, with or without -** modification, are permitted provided that the following conditions are -** met: -** * Redistributions of source code must retain the above copyright -** notice, this list of conditions and the following disclaimer. -** * Redistributions in binary form must reproduce the above copyright -** notice, this list of conditions and the following disclaimer in -** the documentation and/or other materials provided with the -** distribution. -** * Neither the name of Nokia Corporation and its Subsidiary(-ies) nor -** the names of its contributors may be used to endorse or promote -** products derived from this software without specific prior written -** permission. -** -** THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS -** "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT -** LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR -** A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT -** OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, -** SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT -** LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, -** DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY -** THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT -** (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE -** OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE." -** -****************************************************************************/ - -#include -#include -#include -#include - -#include "qtlockedfile.h" - -bool QtLockedFile::lock(LockMode mode, bool block) -{ - if (!isOpen()) { - qWarning("QtLockedFile::lock(): file is not opened"); - return false; - } - - if (mode == NoLock) - return unlock(); - - if (mode == m_lock_mode) - return true; - - if (m_lock_mode != NoLock) - unlock(); - - struct flock fl; - fl.l_whence = SEEK_SET; - fl.l_start = 0; - fl.l_len = 0; - fl.l_type = (mode == ReadLock) ? F_RDLCK : F_WRLCK; - int cmd = block ? F_SETLKW : F_SETLK; - int ret = fcntl(handle(), cmd, &fl); - - if (ret == -1) { - if (errno != EINTR && errno != EAGAIN) - qWarning("QtLockedFile::lock(): fcntl: %s", strerror(errno)); - return false; - } - - - m_lock_mode = mode; - return true; -} - - -bool QtLockedFile::unlock() -{ - if (!isOpen()) { - qWarning("QtLockedFile::unlock(): file is not opened"); - return false; - } - - if (!isLocked()) - return true; - - struct flock fl; - fl.l_whence = SEEK_SET; - fl.l_start = 0; - fl.l_len = 0; - fl.l_type = F_UNLCK; - int ret = fcntl(handle(), F_SETLKW, &fl); - - if (ret == -1) { - qWarning("QtLockedFile::lock(): fcntl: %s", strerror(errno)); - return false; - } - - m_lock_mode = NoLock; - return true; -} - -QtLockedFile::~QtLockedFile() -{ - if (isOpen()) - unlock(); -} - diff --git a/src/libtomahawk/qtsingleapp/qtlockedfile_win.cpp b/src/libtomahawk/qtsingleapp/qtlockedfile_win.cpp deleted file mode 100644 index 8090470cd..000000000 --- a/src/libtomahawk/qtsingleapp/qtlockedfile_win.cpp +++ /dev/null @@ -1,208 +0,0 @@ -/**************************************************************************** -** -** Copyright (C) 2010 Nokia Corporation and/or its subsidiary(-ies). -** All rights reserved. -** -** Contact: Nokia Corporation (qt-info@nokia.com) -** -** This file is part of a Qt Solutions component. -** -** You may use this file under the terms of the BSD license as follows: -** -** "Redistribution and use in source and binary forms, with or without -** modification, are permitted provided that the following conditions are -** met: -** * Redistributions of source code must retain the above copyright -** notice, this list of conditions and the following disclaimer. -** * Redistributions in binary form must reproduce the above copyright -** notice, this list of conditions and the following disclaimer in -** the documentation and/or other materials provided with the -** distribution. -** * Neither the name of Nokia Corporation and its Subsidiary(-ies) nor -** the names of its contributors may be used to endorse or promote -** products derived from this software without specific prior written -** permission. -** -** THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS -** "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT -** LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR -** A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT -** OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, -** SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT -** LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, -** DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY -** THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT -** (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE -** OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE." -** -****************************************************************************/ - -#include "qtlockedfile.h" -#include -#include - -#define MUTEX_PREFIX "QtLockedFile mutex " -// Maximum number of concurrent read locks. Must not be greater than MAXIMUM_WAIT_OBJECTS -#define MAX_READERS MAXIMUM_WAIT_OBJECTS - -#define TCHAR WCHAR - -Qt::HANDLE QtLockedFile::getMutexHandle(int idx, bool doCreate) -{ - if (mutexname.isEmpty()) { - QFileInfo fi(*this); - mutexname = QString::fromLatin1(MUTEX_PREFIX) - + fi.absoluteFilePath().toLower(); - } - QString mname(mutexname); - if (idx >= 0) - mname += QString::number(idx); - - Qt::HANDLE mutex; - if (doCreate) { - QT_WA( { mutex = CreateMutexW(NULL, FALSE, (TCHAR*)mname.utf16()); }, - { mutex = CreateMutexA(NULL, FALSE, mname.toLocal8Bit().constData()); } ); - if (!mutex) { - qErrnoWarning("QtLockedFile::lock(): CreateMutex failed"); - return 0; - } - } - else { - QT_WA( { mutex = OpenMutexW(SYNCHRONIZE | MUTEX_MODIFY_STATE, FALSE, (TCHAR*)mname.utf16()); }, - { mutex = OpenMutexA(SYNCHRONIZE | MUTEX_MODIFY_STATE, FALSE, mname.toLocal8Bit().constData()); } ); - if (!mutex) { - if (GetLastError() != ERROR_FILE_NOT_FOUND) - qErrnoWarning("QtLockedFile::lock(): OpenMutex failed"); - return 0; - } - } - return mutex; -} - -bool QtLockedFile::waitMutex(Qt::HANDLE mutex, bool doBlock) -{ - Q_ASSERT(mutex); - DWORD res = WaitForSingleObject(mutex, doBlock ? INFINITE : 0); - switch (res) { - case WAIT_OBJECT_0: - case WAIT_ABANDONED: - return true; - break; - case WAIT_TIMEOUT: - break; - default: - qErrnoWarning("QtLockedFile::lock(): WaitForSingleObject failed"); - } - return false; -} - - - -bool QtLockedFile::lock(LockMode mode, bool block) -{ - if (!isOpen()) { - qWarning("QtLockedFile::lock(): file is not opened"); - return false; - } - - if (mode == NoLock) - return unlock(); - - if (mode == m_lock_mode) - return true; - - if (m_lock_mode != NoLock) - unlock(); - - if (!wmutex && !(wmutex = getMutexHandle(-1, true))) - return false; - - if (!waitMutex(wmutex, block)) - return false; - - if (mode == ReadLock) { - int idx = 0; - for (; idx < MAX_READERS; idx++) { - rmutex = getMutexHandle(idx, false); - if (!rmutex || waitMutex(rmutex, false)) - break; - CloseHandle(rmutex); - } - bool ok = true; - if (idx >= MAX_READERS) { - qWarning("QtLockedFile::lock(): too many readers"); - rmutex = 0; - ok = false; - } - else if (!rmutex) { - rmutex = getMutexHandle(idx, true); - if (!rmutex || !waitMutex(rmutex, false)) - ok = false; - } - if (!ok && rmutex) { - CloseHandle(rmutex); - rmutex = 0; - } - ReleaseMutex(wmutex); - if (!ok) - return false; - } - else { - Q_ASSERT(rmutexes.isEmpty()); - for (int i = 0; i < MAX_READERS; i++) { - Qt::HANDLE mutex = getMutexHandle(i, false); - if (mutex) - rmutexes.append(mutex); - } - if (rmutexes.size()) { - DWORD res = WaitForMultipleObjects(rmutexes.size(), rmutexes.constData(), - TRUE, block ? INFINITE : 0); - if (res != WAIT_OBJECT_0 && res != WAIT_ABANDONED) { - if (res != WAIT_TIMEOUT) - qErrnoWarning("QtLockedFile::lock(): WaitForMultipleObjects failed"); - m_lock_mode = WriteLock; // trick unlock() to clean up - semiyucky - unlock(); - return false; - } - } - } - - m_lock_mode = mode; - return true; -} - -bool QtLockedFile::unlock() -{ - if (!isOpen()) { - qWarning("QtLockedFile::unlock(): file is not opened"); - return false; - } - - if (!isLocked()) - return true; - - if (m_lock_mode == ReadLock) { - ReleaseMutex(rmutex); - CloseHandle(rmutex); - rmutex = 0; - } - else { - foreach(Qt::HANDLE mutex, rmutexes) { - ReleaseMutex(mutex); - CloseHandle(mutex); - } - rmutexes.clear(); - ReleaseMutex(wmutex); - } - - m_lock_mode = QtLockedFile::NoLock; - return true; -} - -QtLockedFile::~QtLockedFile() -{ - if (isOpen()) - unlock(); - if (wmutex) - CloseHandle(wmutex); -} diff --git a/src/libtomahawk/qtsingleapp/qtsingleapplication.cpp b/src/libtomahawk/qtsingleapp/qtsingleapplication.cpp deleted file mode 100644 index 5a8f1b035..000000000 --- a/src/libtomahawk/qtsingleapp/qtsingleapplication.cpp +++ /dev/null @@ -1,344 +0,0 @@ -/**************************************************************************** -** -** Copyright (C) 2010 Nokia Corporation and/or its subsidiary(-ies). -** All rights reserved. -** -** Contact: Nokia Corporation (qt-info@nokia.com) -** -** This file is part of a Qt Solutions component. -** -** You may use this file under the terms of the BSD license as follows: -** -** "Redistribution and use in source and binary forms, with or without -** modification, are permitted provided that the following conditions are -** met: -** * Redistributions of source code must retain the above copyright -** notice, this list of conditions and the following disclaimer. -** * Redistributions in binary form must reproduce the above copyright -** notice, this list of conditions and the following disclaimer in -** the documentation and/or other materials provided with the -** distribution. -** * Neither the name of Nokia Corporation and its Subsidiary(-ies) nor -** the names of its contributors may be used to endorse or promote -** products derived from this software without specific prior written -** permission. -** -** THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS -** "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT -** LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR -** A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT -** OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, -** SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT -** LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, -** DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY -** THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT -** (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE -** OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE." -** -****************************************************************************/ - - -#include "qtsingleapplication.h" -#include "qtlocalpeer.h" -#include - - -/*! - \class QtSingleApplication qtsingleapplication.h - \brief The QtSingleApplication class provides an API to detect and - communicate with running instances of an application. - - This class allows you to create applications where only one - instance should be running at a time. I.e., if the user tries to - launch another instance, the already running instance will be - activated instead. Another usecase is a client-server system, - where the first started instance will assume the role of server, - and the later instances will act as clients of that server. - - By default, the full path of the executable file is used to - determine whether two processes are instances of the same - application. You can also provide an explicit identifier string - that will be compared instead. - - The application should create the QtSingleApplication object early - in the startup phase, and call isRunning() to find out if another - instance of this application is already running. If isRunning() - returns false, it means that no other instance is running, and - this instance has assumed the role as the running instance. In - this case, the application should continue with the initialization - of the application user interface before entering the event loop - with exec(), as normal. - - The messageReceived() signal will be emitted when the running - application receives messages from another instance of the same - application. When a message is received it might be helpful to the - user to raise the application so that it becomes visible. To - facilitate this, QtSingleApplication provides the - setActivationWindow() function and the activateWindow() slot. - - If isRunning() returns true, another instance is already - running. It may be alerted to the fact that another instance has - started by using the sendMessage() function. Also data such as - startup parameters (e.g. the name of the file the user wanted this - new instance to open) can be passed to the running instance with - this function. Then, the application should terminate (or enter - client mode). - - If isRunning() returns true, but sendMessage() fails, that is an - indication that the running instance is frozen. - - Here's an example that shows how to convert an existing - application to use QtSingleApplication. It is very simple and does - not make use of all QtSingleApplication's functionality (see the - examples for that). - - \code - // Original - int main(int argc, char **argv) - { - QApplication app(argc, argv); - - MyMainWidget mmw; - mmw.show(); - return app.exec(); - } - - // Single instance - int main(int argc, char **argv) - { - QtSingleApplication app(argc, argv); - - if (app.isRunning()) - return !app.sendMessage(someDataString); - - MyMainWidget mmw; - app.setActivationWindow(&mmw); - mmw.show(); - return app.exec(); - } - \endcode - - Once this QtSingleApplication instance is destroyed (normally when - the process exits or crashes), when the user next attempts to run the - application this instance will not, of course, be encountered. The - next instance to call isRunning() or sendMessage() will assume the - role as the new running instance. - - For console (non-GUI) applications, QtSingleCoreApplication may be - used instead of this class, to avoid the dependency on the QtGui - library. - - \sa QtSingleCoreApplication -*/ - - -void QtSingleApplication::sysInit(const QString &appId) -{ - actWin = 0; - peer = new QtLocalPeer(this, appId); - connect(peer, SIGNAL(messageReceived(const QString&)), SIGNAL(messageReceived(const QString&))); -} - - -/*! - Creates a QtSingleApplication object. The application identifier - will be QCoreApplication::applicationFilePath(). \a argc, \a - argv, and \a GUIenabled are passed on to the QAppliation constructor. - - If you are creating a console application (i.e. setting \a - GUIenabled to false), you may consider using - QtSingleCoreApplication instead. -*/ - -QtSingleApplication::QtSingleApplication(int &argc, char **argv, bool GUIenabled) - : QApplication(argc, argv, GUIenabled) -{ - sysInit(); -} - - -/*! - Creates a QtSingleApplication object with the application - identifier \a appId. \a argc and \a argv are passed on to the - QAppliation constructor. -*/ - -QtSingleApplication::QtSingleApplication(const QString &appId, int &argc, char **argv) - : QApplication(argc, argv) -{ - sysInit(appId); -} - - -/*! - Creates a QtSingleApplication object. The application identifier - will be QCoreApplication::applicationFilePath(). \a argc, \a - argv, and \a type are passed on to the QAppliation constructor. -*/ -QtSingleApplication::QtSingleApplication(int &argc, char **argv, Type type) - : QApplication(argc, argv, type) -{ - sysInit(); -} - - -#if defined(Q_WS_X11) -/*! - Special constructor for X11, ref. the documentation of - QApplication's corresponding constructor. The application identifier - will be QCoreApplication::applicationFilePath(). \a dpy, \a visual, - and \a cmap are passed on to the QApplication constructor. -*/ -QtSingleApplication::QtSingleApplication(Display* dpy, Qt::HANDLE visual, Qt::HANDLE cmap) - : QApplication(dpy, visual, cmap) -{ - sysInit(); -} - -/*! - Special constructor for X11, ref. the documentation of - QApplication's corresponding constructor. The application identifier - will be QCoreApplication::applicationFilePath(). \a dpy, \a argc, \a - argv, \a visual, and \a cmap are passed on to the QApplication - constructor. -*/ -QtSingleApplication::QtSingleApplication(Display *dpy, int &argc, char **argv, Qt::HANDLE visual, Qt::HANDLE cmap) - : QApplication(dpy, argc, argv, visual, cmap) -{ - sysInit(); -} - -/*! - Special constructor for X11, ref. the documentation of - QApplication's corresponding constructor. The application identifier - will be \a appId. \a dpy, \a argc, \a - argv, \a visual, and \a cmap are passed on to the QApplication - constructor. -*/ -QtSingleApplication::QtSingleApplication(Display* dpy, const QString &appId, int argc, char **argv, Qt::HANDLE visual, Qt::HANDLE cmap) - : QApplication(dpy, argc, argv, visual, cmap) -{ - sysInit(appId); -} -#endif - - -/*! - Returns true if another instance of this application is running; - otherwise false. - - This function does not find instances of this application that are - being run by a different user (on Windows: that are running in - another session). - - \sa sendMessage() -*/ - -bool QtSingleApplication::isRunning() -{ - return peer->isClient(); -} - - -/*! - Tries to send the text \a message to the currently running - instance. The QtSingleApplication object in the running instance - will emit the messageReceived() signal when it receives the - message. - - This function returns true if the message has been sent to, and - processed by, the current instance. If there is no instance - currently running, or if the running instance fails to process the - message within \a timeout milliseconds, this function return false. - - \sa isRunning(), messageReceived() -*/ -bool QtSingleApplication::sendMessage(const QString &message, int timeout) -{ - return peer->sendMessage(message, timeout); -} - - -/*! - Returns the application identifier. Two processes with the same - identifier will be regarded as instances of the same application. -*/ -QString QtSingleApplication::id() const -{ - return peer->applicationId(); -} - - -/*! - Sets the activation window of this application to \a aw. The - activation window is the widget that will be activated by - activateWindow(). This is typically the application's main window. - - If \a activateOnMessage is true (the default), the window will be - activated automatically every time a message is received, just prior - to the messageReceived() signal being emitted. - - \sa activateWindow(), messageReceived() -*/ - -void QtSingleApplication::setActivationWindow(QWidget* aw, bool activateOnMessage) -{ - actWin = aw; - if (activateOnMessage) - connect(peer, SIGNAL(messageReceived(const QString&)), this, SLOT(activateWindow())); - else - disconnect(peer, SIGNAL(messageReceived(const QString&)), this, SLOT(activateWindow())); -} - - -/*! - Returns the applications activation window if one has been set by - calling setActivationWindow(), otherwise returns 0. - - \sa setActivationWindow() -*/ -QWidget* QtSingleApplication::activationWindow() const -{ - return actWin; -} - - -/*! - De-minimizes, raises, and activates this application's activation window. - This function does nothing if no activation window has been set. - - This is a convenience function to show the user that this - application instance has been activated when he has tried to start - another instance. - - This function should typically be called in response to the - messageReceived() signal. By default, that will happen - automatically, if an activation window has been set. - - \sa setActivationWindow(), messageReceived(), initialize() -*/ -void QtSingleApplication::activateWindow() -{ - if (actWin) { - actWin->setWindowState(actWin->windowState() & ~Qt::WindowMinimized); - actWin->raise(); - actWin->activateWindow(); - } -} - - -/*! - \fn void QtSingleApplication::messageReceived(const QString& message) - - This signal is emitted when the current instance receives a \a - message from another instance of this application. - - \sa sendMessage(), setActivationWindow(), activateWindow() -*/ - - -/*! - \fn void QtSingleApplication::initialize(bool dummy = true) - - \obsolete -*/ diff --git a/src/libtomahawk/qtsingleapp/qtsingleapplication.h b/src/libtomahawk/qtsingleapp/qtsingleapplication.h deleted file mode 100644 index c696d60ce..000000000 --- a/src/libtomahawk/qtsingleapp/qtsingleapplication.h +++ /dev/null @@ -1,84 +0,0 @@ -/**************************************************************************** -** -** Copyright (C) 2010 Nokia Corporation and/or its subsidiary(-ies). -** All rights reserved. -** -** Contact: Nokia Corporation (qt-info@nokia.com) -** -** This file is part of a Qt Solutions component. -** -** You may use this file under the terms of the BSD license as follows: -** -** "Redistribution and use in source and binary forms, with or without -** modification, are permitted provided that the following conditions are -** met: -** * Redistributions of source code must retain the above copyright -** notice, this list of conditions and the following disclaimer. -** * Redistributions in binary form must reproduce the above copyright -** notice, this list of conditions and the following disclaimer in -** the documentation and/or other materials provided with the -** distribution. -** * Neither the name of Nokia Corporation and its Subsidiary(-ies) nor -** the names of its contributors may be used to endorse or promote -** products derived from this software without specific prior written -** permission. -** -** THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS -** "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT -** LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR -** A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT -** OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, -** SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT -** LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, -** DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY -** THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT -** (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE -** OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE." -** -****************************************************************************/ - - -#include - -#include "dllmacro.h" - -class QtLocalPeer; - -class DLLEXPORT QtSingleApplication : public QApplication -{ - Q_OBJECT - -public: - QtSingleApplication(int &argc, char **argv, bool GUIenabled = true); - QtSingleApplication(const QString &id, int &argc, char **argv); - QtSingleApplication(int &argc, char **argv, Type type); -#if defined(Q_WS_X11) - QtSingleApplication(Display* dpy, Qt::HANDLE visual = 0, Qt::HANDLE colormap = 0); - QtSingleApplication(Display *dpy, int &argc, char **argv, Qt::HANDLE visual = 0, Qt::HANDLE cmap= 0); - QtSingleApplication(Display* dpy, const QString &appId, int argc, char **argv, Qt::HANDLE visual = 0, Qt::HANDLE colormap = 0); -#endif - - bool isRunning(); - QString id() const; - - void setActivationWindow(QWidget* aw, bool activateOnMessage = true); - QWidget* activationWindow() const; - - // Obsolete: - void initialize(bool dummy = true) - { isRunning(); Q_UNUSED(dummy) } - -public Q_SLOTS: - bool sendMessage(const QString &message, int timeout = 5000); - void activateWindow(); - - -Q_SIGNALS: - void messageReceived(const QString &message); - - -private: - void sysInit(const QString &appId = QString()); - QtLocalPeer *peer; - QWidget *actWin; -}; diff --git a/src/libtomahawk/qtsingleapp/qtsinglecoreapplication.cpp b/src/libtomahawk/qtsingleapp/qtsinglecoreapplication.cpp deleted file mode 100644 index cf607710e..000000000 --- a/src/libtomahawk/qtsingleapp/qtsinglecoreapplication.cpp +++ /dev/null @@ -1,148 +0,0 @@ -/**************************************************************************** -** -** Copyright (C) 2010 Nokia Corporation and/or its subsidiary(-ies). -** All rights reserved. -** -** Contact: Nokia Corporation (qt-info@nokia.com) -** -** This file is part of a Qt Solutions component. -** -** You may use this file under the terms of the BSD license as follows: -** -** "Redistribution and use in source and binary forms, with or without -** modification, are permitted provided that the following conditions are -** met: -** * Redistributions of source code must retain the above copyright -** notice, this list of conditions and the following disclaimer. -** * Redistributions in binary form must reproduce the above copyright -** notice, this list of conditions and the following disclaimer in -** the documentation and/or other materials provided with the -** distribution. -** * Neither the name of Nokia Corporation and its Subsidiary(-ies) nor -** the names of its contributors may be used to endorse or promote -** products derived from this software without specific prior written -** permission. -** -** THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS -** "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT -** LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR -** A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT -** OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, -** SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT -** LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, -** DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY -** THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT -** (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE -** OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE." -** -****************************************************************************/ - - -#include "qtsinglecoreapplication.h" -#include "qtlocalpeer.h" - -/*! - \class QtSingleCoreApplication qtsinglecoreapplication.h - \brief A variant of the QtSingleApplication class for non-GUI applications. - - This class is a variant of QtSingleApplication suited for use in - console (non-GUI) applications. It is an extension of - QCoreApplication (instead of QApplication). It does not require - the QtGui library. - - The API and usage is identical to QtSingleApplication, except that - functions relating to the "activation window" are not present, for - obvious reasons. Please refer to the QtSingleApplication - documentation for explanation of the usage. - - A QtSingleCoreApplication instance can communicate to a - QtSingleApplication instance if they share the same application - id. Hence, this class can be used to create a light-weight - command-line tool that sends commands to a GUI application. - - \sa QtSingleApplication -*/ - -/*! - Creates a QtSingleCoreApplication object. The application identifier - will be QCoreApplication::applicationFilePath(). \a argc and \a - argv are passed on to the QCoreAppliation constructor. -*/ - -QtSingleCoreApplication::QtSingleCoreApplication(int &argc, char **argv) - : QCoreApplication(argc, argv) -{ - peer = new QtLocalPeer(this); - connect(peer, SIGNAL(messageReceived(const QString&)), SIGNAL(messageReceived(const QString&))); -} - - -/*! - Creates a QtSingleCoreApplication object with the application - identifier \a appId. \a argc and \a argv are passed on to the - QCoreAppliation constructor. -*/ -QtSingleCoreApplication::QtSingleCoreApplication(const QString &appId, int &argc, char **argv) - : QCoreApplication(argc, argv) -{ - peer = new QtLocalPeer(this, appId); - connect(peer, SIGNAL(messageReceived(const QString&)), SIGNAL(messageReceived(const QString&))); -} - - -/*! - Returns true if another instance of this application is running; - otherwise false. - - This function does not find instances of this application that are - being run by a different user (on Windows: that are running in - another session). - - \sa sendMessage() -*/ - -bool QtSingleCoreApplication::isRunning() -{ - return peer->isClient(); -} - - -/*! - Tries to send the text \a message to the currently running - instance. The QtSingleCoreApplication object in the running instance - will emit the messageReceived() signal when it receives the - message. - - This function returns true if the message has been sent to, and - processed by, the current instance. If there is no instance - currently running, or if the running instance fails to process the - message within \a timeout milliseconds, this function return false. - - \sa isRunning(), messageReceived() -*/ - -bool QtSingleCoreApplication::sendMessage(const QString &message, int timeout) -{ - return peer->sendMessage(message, timeout); -} - - -/*! - Returns the application identifier. Two processes with the same - identifier will be regarded as instances of the same application. -*/ - -QString QtSingleCoreApplication::id() const -{ - return peer->applicationId(); -} - - -/*! - \fn void QtSingleCoreApplication::messageReceived(const QString& message) - - This signal is emitted when the current instance receives a \a - message from another instance of this application. - - \sa sendMessage() -*/ diff --git a/src/libtomahawk/qtsingleapp/qtsinglecoreapplication.h b/src/libtomahawk/qtsingleapp/qtsinglecoreapplication.h deleted file mode 100644 index ef529a8f6..000000000 --- a/src/libtomahawk/qtsingleapp/qtsinglecoreapplication.h +++ /dev/null @@ -1,66 +0,0 @@ -/**************************************************************************** -** -** Copyright (C) 2010 Nokia Corporation and/or its subsidiary(-ies). -** All rights reserved. -** -** Contact: Nokia Corporation (qt-info@nokia.com) -** -** This file is part of a Qt Solutions component. -** -** You may use this file under the terms of the BSD license as follows: -** -** "Redistribution and use in source and binary forms, with or without -** modification, are permitted provided that the following conditions are -** met: -** * Redistributions of source code must retain the above copyright -** notice, this list of conditions and the following disclaimer. -** * Redistributions in binary form must reproduce the above copyright -** notice, this list of conditions and the following disclaimer in -** the documentation and/or other materials provided with the -** distribution. -** * Neither the name of Nokia Corporation and its Subsidiary(-ies) nor -** the names of its contributors may be used to endorse or promote -** products derived from this software without specific prior written -** permission. -** -** THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS -** "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT -** LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR -** A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT -** OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, -** SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT -** LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, -** DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY -** THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT -** (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE -** OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE." -** -****************************************************************************/ - - -#include - -class QtLocalPeer; - -class QtSingleCoreApplication : public QCoreApplication -{ - Q_OBJECT - -public: - QtSingleCoreApplication(int &argc, char **argv); - QtSingleCoreApplication(const QString &id, int &argc, char **argv); - - bool isRunning(); - QString id() const; - -public Q_SLOTS: - bool sendMessage(const QString &message, int timeout = 5000); - - -Q_SIGNALS: - void messageReceived(const QString &message); - - -private: - QtLocalPeer* peer; -}; diff --git a/src/main.cpp b/src/main.cpp index b5c84dae6..a3277a55f 100644 --- a/src/main.cpp +++ b/src/main.cpp @@ -26,6 +26,8 @@ #endif #include + +#include "kdsingleapplicationguard/kdsingleapplicationguard.h" int main( int argc, char *argv[] ) { @@ -40,21 +42,17 @@ main( int argc, char *argv[] ) #endif - try - { - TomahawkApp a( argc, argv ); + TomahawkApp a( argc, argv ); + KDSingleApplicationGuard guard( &a, KDSingleApplicationGuard::AutoKillOtherInstances ); + QObject::connect( &guard, SIGNAL( instanceStarted( KDSingleApplicationGuard::Instance ) ), &a, SLOT( instanceStarted( KDSingleApplicationGuard::Instance ) ) ); + + QString locale = QLocale::system().name(); - QString locale = QLocale::system().name(); - - QTranslator translator; - translator.load( QString( ":/lang/tomahawk_" ) + locale ); - a.installTranslator( &translator ); - return a.exec(); - } - catch( const std::runtime_error& e ) - { - return 0; - } + QTranslator translator; + translator.load( QString( ":/lang/tomahawk_" ) + locale ); + a.installTranslator( &translator ); + return a.exec(); + } #ifdef Q_WS_MAC diff --git a/src/tomahawkapp.cpp b/src/tomahawkapp.cpp index a148d9a02..1efa811d3 100644 --- a/src/tomahawkapp.cpp +++ b/src/tomahawkapp.cpp @@ -152,14 +152,6 @@ TomahawkApp::TomahawkApp( int& argc, char *argv[] ) { qsrand( QTime( 0, 0, 0 ).secsTo( QTime::currentTime() ) ); - // send the first arg to an already running instance, but don't open twice no matter what - if( ( argc > 1 && sendMessage( argv[ 1 ] ) ) || sendMessage( "" ) ) { - qDebug() << "Sent message, already exists"; - throw runtime_error( "Already Running" ); - } - - connect( this, SIGNAL( messageReceived( QString ) ), this, SLOT( messageReceived( QString ) ) ); - #ifdef TOMAHAWK_HEADLESS m_headless = true; #else @@ -535,13 +527,15 @@ TomahawkApp::loadUrl( const QString& url ) void -TomahawkApp::messageReceived( const QString& msg ) +TomahawkApp::instanceStarted( KDSingleApplicationGuard::Instance instance ) { - qDebug() << "MESSAGE RECEIVED" << msg; - if( msg.isEmpty() ) { + qDebug() << "INSTANCE STARTED!" << instance.pid << instance.arguments; + + if( instance.arguments.size() < 2 ) + { return; } - loadUrl( msg ); + loadUrl( instance.arguments.at( 1 ) ); } From a848561e60e7af4a175ea5b47b5a9b78963cf74b Mon Sep 17 00:00:00 2001 From: Jeff Mitchell Date: Sat, 2 Apr 2011 19:29:53 -0400 Subject: [PATCH 235/329] Add some debugging for Chris and fix duplicate paths being queued --- src/musicscanner.cpp | 2 ++ src/scanmanager.cpp | 3 ++- 2 files changed, 4 insertions(+), 1 deletion(-) diff --git a/src/musicscanner.cpp b/src/musicscanner.cpp index 6ccbf14ae..d9404a18a 100644 --- a/src/musicscanner.cpp +++ b/src/musicscanner.cpp @@ -67,6 +67,8 @@ DirLister::scanDir( QDir dir, int depth, DirLister::Mode mode ) foreach( const QFileInfo& di, dirs ) { + qDebug() << "Considering dir " << di.absoluteFilePath(); + qDebug() << "m_dirtimes contains it? " << (m_dirmtimes.contains( di.absoluteFilePath() ) ? "true" : "false"); if( mode == DirLister::Recursive || !m_dirmtimes.contains( di.absoluteFilePath() ) ) scanDir( di.absoluteFilePath(), depth + 1, DirLister::Recursive ); else //should be the non-recursive case since the second test above should only happen with a new dir diff --git a/src/scanmanager.cpp b/src/scanmanager.cpp index 945d42987..0073c73b9 100644 --- a/src/scanmanager.cpp +++ b/src/scanmanager.cpp @@ -219,7 +219,8 @@ ScanManager::handleChangedDir( const QString& path ) { qDebug() << Q_FUNC_INFO; qDebug() << "Dir changed: " << path; - m_queuedChangedDirs << path; + if( !m_queuedChangedDirs.contains( path ) ) + m_queuedChangedDirs << path; if( TomahawkSettings::instance()->watchForChanges() ) m_queuedScanTimer->start( 10000 ); } From 743575b91a1640412e7584b76a64f6c2f069f1d1 Mon Sep 17 00:00:00 2001 From: Leo Franchi Date: Sat, 2 Apr 2011 19:34:24 -0400 Subject: [PATCH 236/329] use our export macro that works on windows :) --- .../kdsingleapplicationguard/kdsingleapplicationguard.h | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/src/libtomahawk/kdsingleapplicationguard/kdsingleapplicationguard.h b/src/libtomahawk/kdsingleapplicationguard/kdsingleapplicationguard.h index f649ef836..14706e4d0 100644 --- a/src/libtomahawk/kdsingleapplicationguard/kdsingleapplicationguard.h +++ b/src/libtomahawk/kdsingleapplicationguard/kdsingleapplicationguard.h @@ -5,6 +5,7 @@ #include #include "pimpl_ptr.h" +#include "dllmacro.h" class QCoreApplication; @@ -12,7 +13,7 @@ class QCoreApplication; void SIGINT_handler( int sig ); #endif -class KDTOOLSCORE_EXPORT KDSingleApplicationGuard : public QObject +class DLLEXPORT KDSingleApplicationGuard : public QObject { Q_OBJECT #ifndef Q_WS_WIN From c3df88d0f82d677886dc060372c2c4b885c3af52 Mon Sep 17 00:00:00 2001 From: Jeff Mitchell Date: Sat, 2 Apr 2011 20:48:39 -0400 Subject: [PATCH 237/329] More debug, and fixed watch behavior :-) --- src/musicscanner.cpp | 35 ++++++++++++++++++++++++++++++----- 1 file changed, 30 insertions(+), 5 deletions(-) diff --git a/src/musicscanner.cpp b/src/musicscanner.cpp index d9404a18a..dc0647c73 100644 --- a/src/musicscanner.cpp +++ b/src/musicscanner.cpp @@ -19,6 +19,7 @@ #include "musicscanner.h" #include "tomahawk/tomahawkapp.h" +#include "tomahawksettings.h" #include "sourcelist.h" #include "database/database.h" #include "database/databasecommand_dirmtimes.h" @@ -31,6 +32,31 @@ using namespace Tomahawk; void DirLister::go() { + qDebug() << Q_FUNC_INFO; + qDebug() << "Current mtimes: " << m_dirmtimes; + qDebug() << "Recursive? : " << (m_recursive ? "true" : "false"); + if( !m_recursive ) + { + foreach( QString dir, m_dirs ) + { + if( m_dirmtimes.contains( dir ) ) + { + qDebug() << "Removing " << dir << " from m_dirmtimes because it's specifically requested"; + m_dirmtimes.remove( dir ); + } + QStringList filtered = QStringList( m_dirmtimes.keys() ).filter( dir ); + foreach( QString filteredDir, filtered ) + { + if( !QDir( filteredDir ).exists() ) + { + qDebug() << "Removing " << filteredDir << " from m_dirmtimes because it does not exist"; + m_dirmtimes.remove( filteredDir ); + } + } + } + m_newdirmtimes = m_dirmtimes; + } + foreach( QString dir, m_dirs ) scanDir( QDir( dir, 0 ), 0, ( m_recursive ? DirLister::Recursive : DirLister::NonRecursive ) ); emit finished( m_newdirmtimes ); @@ -68,11 +94,11 @@ DirLister::scanDir( QDir dir, int depth, DirLister::Mode mode ) foreach( const QFileInfo& di, dirs ) { qDebug() << "Considering dir " << di.absoluteFilePath(); - qDebug() << "m_dirtimes contains it? " << (m_dirmtimes.contains( di.absoluteFilePath() ) ? "true" : "false"); + qDebug() << "m_dirmtimes contains it? " << (m_dirmtimes.contains( di.absoluteFilePath() ) ? "true" : "false"); if( mode == DirLister::Recursive || !m_dirmtimes.contains( di.absoluteFilePath() ) ) scanDir( di.absoluteFilePath(), depth + 1, DirLister::Recursive ); - else //should be the non-recursive case since the second test above should only happen with a new dir - scanDir( di.absoluteFilePath(), depth + 1, DirLister::MTimeOnly ); + //else //should be the non-recursive case since the second test above should only happen with a new dir + // scanDir( di.absoluteFilePath(), depth + 1, DirLister::MTimeOnly ); } } @@ -130,8 +156,7 @@ MusicScanner::startScan() m_skippedFiles.clear(); // trigger the scan once we've loaded old mtimes for dirs below our path - //FIXME: MULTIPLECOLLECTIONDIRS - DatabaseCommand_DirMtimes* cmd = new DatabaseCommand_DirMtimes( m_dirs.first() ); + DatabaseCommand_DirMtimes* cmd = new DatabaseCommand_DirMtimes( TomahawkSettings::instance()->scannerPaths() ); connect( cmd, SIGNAL( done( QMap ) ), SLOT( setMtimes( QMap ) ) ); connect( cmd, SIGNAL( done( QMap ) ), From daa822cd147594645832526f36d21526c41b45f9 Mon Sep 17 00:00:00 2001 From: Jeff Mitchell Date: Sat, 2 Apr 2011 21:44:48 -0400 Subject: [PATCH 238/329] Some minor work on infosystem --- include/tomahawk/infosystem.h | 13 +++++---- src/infosystem/infosystem.cpp | 45 ++++++++++++++++++++++---------- src/infosystem/infosystemcache.h | 7 +++++ 3 files changed, 46 insertions(+), 19 deletions(-) diff --git a/include/tomahawk/infosystem.h b/include/tomahawk/infosystem.h index 30c43f5d2..75e365362 100644 --- a/include/tomahawk/infosystem.h +++ b/include/tomahawk/infosystem.h @@ -107,15 +107,18 @@ public: qDebug() << Q_FUNC_INFO; } - virtual void getInfo( const QString &caller, const InfoType type, const QVariant &data, Tomahawk::InfoSystem::InfoCustomDataHash customData ) = 0; + virtual void getInfo( const QString &caller, const InfoType type, const QVariant &data, InfoCustomDataHash customData ) = 0; signals: void info( QString caller, Tomahawk::InfoSystem::InfoType type, QVariant input, QVariant output, Tomahawk::InfoSystem::InfoCustomDataHash customData ); void getCachedInfo( QHash< QString, QString > criteria, QString caller, Tomahawk::InfoSystem::InfoType type, QVariant input, QVariant output, Tomahawk::InfoSystem::InfoCustomDataHash customData ); void finished( QString, Tomahawk::InfoSystem::InfoType ); -//public slots: - //void notInCacheSlot( QString caller, Tomahawk::InfoSystem::InfoType type, QVariant input, QVariant output, Tomahawk::InfoSystem::InfoCustomDataHash customData ) = 0; +public slots: + //FIXME: Make pure virtual when everything supports it + void notInCacheSlot( QHash criteria, QString caller, Tomahawk::InfoSystem::InfoType type, QVariant input, QVariant output, Tomahawk::InfoSystem::InfoCustomDataHash customData ) + { + } protected: InfoType m_type; @@ -145,7 +148,7 @@ signals: public slots: void infoSlot( QString target, Tomahawk::InfoSystem::InfoType type, QVariant input, QVariant output, Tomahawk::InfoSystem::InfoCustomDataHash customData ); - void finishedSlot( QString target,Tomahawk::InfoSystem::InfoType type); + void finishedSlot( QString target, Tomahawk::InfoSystem::InfoType type); private: QLinkedList< InfoPluginPtr > determineOrderedMatches( const InfoType type ) const; @@ -155,7 +158,7 @@ private: // For now, statically instantiate plugins; this is just somewhere to keep them QLinkedList< InfoPluginPtr > m_plugins; - QHash< QString, QHash< Tomahawk::InfoSystem::InfoType, int > > m_dataTracker; + QHash< QString, QHash< InfoType, int > > m_dataTracker; InfoSystemCache* m_cache; QThread* m_infoSystemCacheThreadController; diff --git a/src/infosystem/infosystem.cpp b/src/infosystem/infosystem.cpp index e4a6a6c2a..deba612f0 100644 --- a/src/infosystem/infosystem.cpp +++ b/src/infosystem/infosystem.cpp @@ -25,7 +25,11 @@ #include "infoplugins/musixmatchplugin.h" #include "infoplugins/lastfmplugin.h" -using namespace Tomahawk::InfoSystem; +namespace Tomahawk +{ + +namespace InfoSystem +{ InfoPlugin::InfoPlugin(QObject *parent) :QObject( parent ) @@ -33,10 +37,11 @@ InfoPlugin::InfoPlugin(QObject *parent) qDebug() << Q_FUNC_INFO; InfoSystem *system = qobject_cast< InfoSystem* >( parent ); if( system ) - QObject::connect( system->getCache(), - SIGNAL( notInCache( QString, Tomahawk::InfoSystem::InfoType, QVariant, QVariant, Tomahawk::InfoSystem::InfoCustomDataHash ) ), + QObject::connect( + system->getCache(), + SIGNAL( notInCache( QHash< QString, QString >, QString, Tomahawk::InfoSystem::InfoType, QVariant, QVariant, Tomahawk::InfoSystem::InfoCustomDataHash ) ), this, - SLOT( notInCacheSlot( QString, Tomahawk::InfoSystem::InfoType, QVariant, QVariant, Tomahawk::InfoSystem::InfoCustomDataHash ) ) + SLOT( notInCacheSlot( QHash< QString, QString >, QString, Tomahawk::InfoSystem::InfoType, QVariant, QVariant, Tomahawk::InfoSystem::InfoCustomDataHash ) ) ); } @@ -49,7 +54,7 @@ InfoSystem::InfoSystem(QObject *parent) qRegisterMetaType >("Tomahawk::InfoSystem::InfoCustomDataHash"); m_infoSystemCacheThreadController = new QThread( this ); - m_cache = new Tomahawk::InfoSystem::InfoSystemCache(); + m_cache = new InfoSystemCache(); m_cache->moveToThread( m_infoSystemCacheThreadController ); m_infoSystemCacheThreadController->start( QThread::IdlePriority ); @@ -59,6 +64,18 @@ InfoSystem::InfoSystem(QObject *parent) m_plugins.append(mmptr); InfoPluginPtr lfmptr(new LastFmPlugin(this)); m_plugins.append(lfmptr); + + Q_FOREACH( InfoPluginPtr plugin, m_plugins ) + { + connect(plugin.data(), SIGNAL(info(QString, Tomahawk::InfoSystem::InfoType, QVariant, QVariant, Tomahawk::InfoSystem::InfoCustomDataHash)), + this, SLOT(infoSlot(QString, Tomahawk::InfoSystem::InfoType, QVariant, QVariant, Tomahawk::InfoSystem::InfoCustomDataHash)), Qt::UniqueConnection); + connect(plugin.data(), SIGNAL(finished(QString, Tomahawk::InfoSystem::InfoType)), + this, SLOT(finishedSlot(QString, Tomahawk::InfoSystem::InfoType)), Qt::UniqueConnection); + } + connect(m_cache, SIGNAL(info(QString, Tomahawk::InfoSystem::InfoType, QVariant, QVariant, Tomahawk::InfoSystem::InfoCustomDataHash)), + this, SLOT(infoSlot(QString, Tomahawk::InfoSystem::InfoType, QVariant, QVariant, Tomahawk::InfoSystem::InfoCustomDataHash)), Qt::UniqueConnection); + connect(m_cache, SIGNAL(finished(QString, Tomahawk::InfoSystem::InfoType)), + this, SLOT(finishedSlot(QString, Tomahawk::InfoSystem::InfoType)), Qt::UniqueConnection); } InfoSystem::~InfoSystem() @@ -114,7 +131,7 @@ void InfoSystem::getInfo(const QString &caller, const InfoType type, const QVari QLinkedList< InfoPluginPtr > providers = determineOrderedMatches(type); if (providers.isEmpty()) { - emit info(QString(), Tomahawk::InfoSystem::InfoNoInfo, QVariant(), QVariant(), customData); + emit info(QString(), InfoNoInfo, QVariant(), QVariant(), customData); emit finished(caller); return; } @@ -122,17 +139,13 @@ void InfoSystem::getInfo(const QString &caller, const InfoType type, const QVari InfoPluginPtr ptr = providers.first(); if (!ptr) { - emit info(QString(), Tomahawk::InfoSystem::InfoNoInfo, QVariant(), QVariant(), customData); + emit info(QString(), InfoNoInfo, QVariant(), QVariant(), customData); emit finished(caller); return; } m_dataTracker[caller][type] = m_dataTracker[caller][type] + 1; qDebug() << "current count in dataTracker for type" << type << "is" << m_dataTracker[caller][type]; - connect(ptr.data(), SIGNAL(info(QString, Tomahawk::InfoSystem::InfoType, QVariant, QVariant, Tomahawk::InfoSystem::InfoCustomDataHash)), - this, SLOT(infoSlot(QString, Tomahawk::InfoSystem::InfoType, QVariant, QVariant, Tomahawk::InfoSystem::InfoCustomDataHash)), Qt::UniqueConnection); - connect(ptr.data(), SIGNAL(finished(QString, Tomahawk::InfoSystem::InfoType)), - this, SLOT(finishedSlot(QString, Tomahawk::InfoSystem::InfoType)), Qt::UniqueConnection); ptr.data()->getInfo(caller, type, data, customData); } @@ -142,7 +155,7 @@ void InfoSystem::getInfo(const QString &caller, const InfoMap &input, InfoCustom getInfo(caller, type, input[type], customData); } -void InfoSystem::infoSlot(QString target, Tomahawk::InfoSystem::InfoType type, QVariant input, QVariant output, Tomahawk::InfoSystem::InfoCustomDataHash customData) +void InfoSystem::infoSlot(QString target, InfoType type, QVariant input, QVariant output, InfoCustomDataHash customData) { qDebug() << Q_FUNC_INFO; qDebug() << "current count in dataTracker is " << m_dataTracker[target][type]; @@ -154,12 +167,12 @@ void InfoSystem::infoSlot(QString target, Tomahawk::InfoSystem::InfoType type, Q emit info(target, type, input, output, customData); } -void InfoSystem::finishedSlot(QString target, Tomahawk::InfoSystem::InfoType type) +void InfoSystem::finishedSlot(QString target, InfoType type) { qDebug() << Q_FUNC_INFO; m_dataTracker[target][type] = m_dataTracker[target][type] - 1; qDebug() << "current count in dataTracker is " << m_dataTracker[target][type]; - Q_FOREACH(Tomahawk::InfoSystem::InfoType testtype, m_dataTracker[target].keys()) + Q_FOREACH(InfoType testtype, m_dataTracker[target].keys()) { if (m_dataTracker[target][testtype] != 0) { @@ -170,3 +183,7 @@ void InfoSystem::finishedSlot(QString target, Tomahawk::InfoSystem::InfoType typ qDebug() << "emitting finished with target" << target; emit finished(target); } + +} //namespace InfoSystem + +} //namespace Tomahawk \ No newline at end of file diff --git a/src/infosystem/infosystemcache.h b/src/infosystem/infosystemcache.h index 97990b7a1..89aa9df6e 100644 --- a/src/infosystem/infosystemcache.h +++ b/src/infosystem/infosystemcache.h @@ -22,6 +22,8 @@ #include #include +#include "tomahawk/infosystem.h" + namespace Tomahawk { @@ -44,6 +46,11 @@ public: qDebug() << Q_FUNC_INFO; } +signals: + void notInCache( QHash< QString, QString > criteria, QString caller, Tomahawk::InfoSystem::InfoType type, QVariant input, QVariant output, Tomahawk::InfoSystem::InfoCustomDataHash customData ); + void info( QString caller, Tomahawk::InfoSystem::InfoType type, QVariant input, QVariant output, Tomahawk::InfoSystem::InfoCustomDataHash customData ); + void finished( QString, Tomahawk::InfoSystem::InfoType ); + }; } //namespace InfoSystem From 1da0a34d8921dfad2a518532b0a8bd3004db7927 Mon Sep 17 00:00:00 2001 From: Leo Franchi Date: Tue, 22 Mar 2011 21:33:27 -0400 Subject: [PATCH 239/329] tomahawk:// handler stuff on windows --- admin/win/nsi/tomahawk.nsi | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/admin/win/nsi/tomahawk.nsi b/admin/win/nsi/tomahawk.nsi index c2d2a9bd1..222defc2b 100644 --- a/admin/win/nsi/tomahawk.nsi +++ b/admin/win/nsi/tomahawk.nsi @@ -457,6 +457,12 @@ Section -post WriteRegDWORD HKLM "Software\Microsoft\Windows\CurrentVersion\Uninstall\Tomahawk" "NoModify" "1" WriteRegDWORD HKLM "Software\Microsoft\Windows\CurrentVersion\Uninstall\Tomahawk" "NoRepair" "1" + ; Register tomahawk:// protocol handler + WriteRegStr HKCR "tomahawk" "" "URL: Tomahawk Protocol" + WriteRegStr HKCR "tomahawk\DefaultIcon" "" $INSTDIR\tomahawk.exe,1 + WriteRegStr HKCR "tomahawk\shell" "" "open" + WriteRegStr HKCR "tomahawk\shell\open\command" "" '"$INSTDIR\tomahawk.exe" "%1"' + SetDetailsPrint textonly DetailPrint "Finsihed." SectionEnd @@ -516,6 +522,8 @@ Section Uninstall DeleteRegValue HKLM "Software\Tomahawk" "" DeleteRegKey HKLM "Software\Tomahawk" + DeleteRegKey HKCR "tomahawk" + ;Start menu shortcuts. !ifdef OPTION_SECTION_SC_START_MENU SetShellVarContext all From 2b85beb704c7917f7e97466e504f1168331ad998 Mon Sep 17 00:00:00 2001 From: Jeff Mitchell Date: Sat, 2 Apr 2011 22:49:11 -0400 Subject: [PATCH 240/329] Requests for cover art from lastfm now go through the infosystem caching mechanism. It's not actually storing a cache yet but the information flow works (at least, for cache misses :-) ) --- include/tomahawk/infosystem.h | 23 +++--- src/audiocontrols.cpp | 14 ++-- src/audiocontrols.h | 2 +- src/infosystem/infoplugins/echonestplugin.cpp | 18 ++--- src/infosystem/infoplugins/echonestplugin.h | 20 ++--- src/infosystem/infoplugins/lastfmplugin.cpp | 70 +++++++++++------ src/infosystem/infoplugins/lastfmplugin.h | 11 +-- .../infoplugins/musixmatchplugin.cpp | 24 +++--- src/infosystem/infoplugins/musixmatchplugin.h | 4 +- src/infosystem/infosystem.cpp | 76 +++++++++++++------ src/infosystem/infosystemcache.cpp | 34 +++++++++ src/infosystem/infosystemcache.h | 8 +- src/scrobbler.cpp | 12 +-- src/scrobbler.h | 2 +- src/xmppbot/xmppbot.cpp | 16 ++-- src/xmppbot/xmppbot.h | 2 +- 16 files changed, 215 insertions(+), 121 deletions(-) diff --git a/include/tomahawk/infosystem.h b/include/tomahawk/infosystem.h index 75e365362..2ab5d2da0 100644 --- a/include/tomahawk/infosystem.h +++ b/include/tomahawk/infosystem.h @@ -93,7 +93,8 @@ enum InfoType { typedef QMap< InfoType, QVariant > InfoMap; typedef QMap< QString, QMap< QString, QString > > InfoGenericMap; -typedef QHash< QString, QVariant > InfoCustomDataHash; +typedef QHash< QString, QVariant > InfoCustomData; +typedef QHash< QString, QString > InfoCacheCriteria; class InfoPlugin : public QObject { @@ -107,16 +108,17 @@ public: qDebug() << Q_FUNC_INFO; } - virtual void getInfo( const QString &caller, const InfoType type, const QVariant &data, InfoCustomDataHash customData ) = 0; + virtual void getInfo( const QString &caller, const InfoType type, const QVariant &data, InfoCustomData customData ) = 0; signals: - void info( QString caller, Tomahawk::InfoSystem::InfoType type, QVariant input, QVariant output, Tomahawk::InfoSystem::InfoCustomDataHash customData ); - void getCachedInfo( QHash< QString, QString > criteria, QString caller, Tomahawk::InfoSystem::InfoType type, QVariant input, QVariant output, Tomahawk::InfoSystem::InfoCustomDataHash customData ); + void getCachedInfo( Tomahawk::InfoSystem::InfoCacheCriteria criteria, QString caller, Tomahawk::InfoSystem::InfoType type, QVariant input, Tomahawk::InfoSystem::InfoCustomData customData ); + void updateCache( Tomahawk::InfoSystem::InfoCacheCriteria criteria, Tomahawk::InfoSystem::InfoType type, QVariant output ); + void info( QString caller, Tomahawk::InfoSystem::InfoType type, QVariant input, QVariant output, Tomahawk::InfoSystem::InfoCustomData customData ); void finished( QString, Tomahawk::InfoSystem::InfoType ); public slots: //FIXME: Make pure virtual when everything supports it - void notInCacheSlot( QHash criteria, QString caller, Tomahawk::InfoSystem::InfoType type, QVariant input, QVariant output, Tomahawk::InfoSystem::InfoCustomDataHash customData ) + virtual void notInCacheSlot( Tomahawk::InfoSystem::InfoCacheCriteria criteria, QString caller, Tomahawk::InfoSystem::InfoType type, QVariant input, Tomahawk::InfoSystem::InfoCustomData customData ) { } @@ -137,17 +139,17 @@ public: void registerInfoTypes( const InfoPluginPtr &plugin, const QSet< InfoType > &types ); - void getInfo( const QString &caller, const InfoType type, const QVariant &data, InfoCustomDataHash customData ); - void getInfo( const QString &caller, const InfoMap &input, InfoCustomDataHash customData ); + void getInfo( const QString &caller, const InfoType type, const QVariant &data, InfoCustomData customData ); + void getInfo( const QString &caller, const InfoMap &input, InfoCustomData customData ); InfoSystemCache* getCache() { return m_cache; } signals: - void info( QString caller, Tomahawk::InfoSystem::InfoType, QVariant input, QVariant output, Tomahawk::InfoSystem::InfoCustomDataHash customData ); + void info( QString caller, Tomahawk::InfoSystem::InfoType, QVariant input, QVariant output, Tomahawk::InfoSystem::InfoCustomData customData ); void finished( QString target ); public slots: - void infoSlot( QString target, Tomahawk::InfoSystem::InfoType type, QVariant input, QVariant output, Tomahawk::InfoSystem::InfoCustomDataHash customData ); + void infoSlot( QString target, Tomahawk::InfoSystem::InfoType type, QVariant input, QVariant output, Tomahawk::InfoSystem::InfoCustomData customData ); void finishedSlot( QString target, Tomahawk::InfoSystem::InfoType type); private: @@ -169,6 +171,7 @@ private: } Q_DECLARE_METATYPE( Tomahawk::InfoSystem::InfoGenericMap ); -Q_DECLARE_METATYPE( Tomahawk::InfoSystem::InfoCustomDataHash ); +Q_DECLARE_METATYPE( Tomahawk::InfoSystem::InfoCustomData ); +Q_DECLARE_METATYPE( Tomahawk::InfoSystem::InfoCacheCriteria ); #endif // TOMAHAWK_INFOSYSTEM_H diff --git a/src/audiocontrols.cpp b/src/audiocontrols.cpp index cd6f7682e..8f4214ed8 100644 --- a/src/audiocontrols.cpp +++ b/src/audiocontrols.cpp @@ -168,8 +168,8 @@ AudioControls::AudioControls( QWidget* parent ) .scaled( ui->coverImage->size(), Qt::IgnoreAspectRatio, Qt::SmoothTransformation ); connect( TomahawkApp::instance()->infoSystem(), - SIGNAL( info( QString, Tomahawk::InfoSystem::InfoType, QVariant, QVariant, Tomahawk::InfoSystem::InfoCustomDataHash ) ), - SLOT( infoSystemInfo( QString, Tomahawk::InfoSystem::InfoType, QVariant, QVariant, Tomahawk::InfoSystem::InfoCustomDataHash ) ) ); + SIGNAL( info( QString, Tomahawk::InfoSystem::InfoType, QVariant, QVariant, Tomahawk::InfoSystem::InfoCustomData ) ), + SLOT( infoSystemInfo( QString, Tomahawk::InfoSystem::InfoType, QVariant, QVariant, Tomahawk::InfoSystem::InfoCustomData ) ) ); connect( TomahawkApp::instance()->infoSystem(), SIGNAL( finished( QString ) ), SLOT( infoSystemFinished( QString ) ) ); @@ -252,17 +252,17 @@ AudioControls::onPlaybackStarted( const Tomahawk::result_ptr& result ) QString artistName = result->artist()->name(); QString albumName = result->album()->name(); - Tomahawk::InfoSystem::InfoCustomDataHash trackInfo; + Tomahawk::InfoSystem::InfoCustomData trackInfo; trackInfo["artist"] = QVariant::fromValue< QString >( result->artist()->name() ); trackInfo["album"] = QVariant::fromValue< QString >( result->album()->name() ); TomahawkApp::instance()->infoSystem()->getInfo( s_infoIdentifier, Tomahawk::InfoSystem::InfoAlbumCoverArt, - QVariant::fromValue< Tomahawk::InfoSystem::InfoCustomDataHash >( trackInfo ), Tomahawk::InfoSystem::InfoCustomDataHash() ); + QVariant::fromValue< Tomahawk::InfoSystem::InfoCustomData >( trackInfo ), Tomahawk::InfoSystem::InfoCustomData() ); } void -AudioControls::infoSystemInfo( QString caller, Tomahawk::InfoSystem::InfoType type, QVariant input, QVariant output, Tomahawk::InfoSystem::InfoCustomDataHash customData ) +AudioControls::infoSystemInfo( QString caller, Tomahawk::InfoSystem::InfoType type, QVariant input, QVariant output, Tomahawk::InfoSystem::InfoCustomData customData ) { qDebug() << Q_FUNC_INFO; if ( caller != s_infoIdentifier || type != Tomahawk::InfoSystem::InfoAlbumCoverArt ) @@ -277,13 +277,13 @@ AudioControls::infoSystemInfo( QString caller, Tomahawk::InfoSystem::InfoType ty return; } - if ( !output.canConvert< Tomahawk::InfoSystem::InfoCustomDataHash >() ) + if ( !output.canConvert< Tomahawk::InfoSystem::InfoCustomData >() ) { qDebug() << "Cannot convert fetched art from a QByteArray"; return; } - Tomahawk::InfoSystem::InfoCustomDataHash returnedData = output.value< Tomahawk::InfoSystem::InfoCustomDataHash >(); + Tomahawk::InfoSystem::InfoCustomData returnedData = output.value< Tomahawk::InfoSystem::InfoCustomData >(); const QByteArray ba = returnedData["imgbytes"].toByteArray(); if ( ba.length() ) { diff --git a/src/audiocontrols.h b/src/audiocontrols.h index 0491745c3..3b90005d6 100644 --- a/src/audiocontrols.h +++ b/src/audiocontrols.h @@ -45,7 +45,7 @@ signals: public slots: void onRepeatModeChanged( PlaylistInterface::RepeatMode mode ); void onShuffleModeChanged( bool enabled ); - void infoSystemInfo( QString caller, Tomahawk::InfoSystem::InfoType type, QVariant input, QVariant output, Tomahawk::InfoSystem::InfoCustomDataHash customData ); + void infoSystemInfo( QString caller, Tomahawk::InfoSystem::InfoType type, QVariant input, QVariant output, Tomahawk::InfoSystem::InfoCustomData customData ); void infoSystemFinished( QString target ); protected: diff --git a/src/infosystem/infoplugins/echonestplugin.cpp b/src/infosystem/infoplugins/echonestplugin.cpp index fab7c9bc4..ec852f787 100644 --- a/src/infosystem/infoplugins/echonestplugin.cpp +++ b/src/infosystem/infoplugins/echonestplugin.cpp @@ -41,7 +41,7 @@ EchoNestPlugin::~EchoNestPlugin() qDebug() << Q_FUNC_INFO; } -void EchoNestPlugin::getInfo(const QString &caller, const InfoType type, const QVariant& data, InfoCustomDataHash customData) +void EchoNestPlugin::getInfo(const QString &caller, const InfoType type, const QVariant& data, InfoCustomData customData) { switch (type) { @@ -65,7 +65,7 @@ void EchoNestPlugin::getInfo(const QString &caller, const InfoType type, const Q } } -void EchoNestPlugin::getSongProfile(const QString &caller, const QVariant& data, InfoCustomDataHash &customData, const QString &item) +void EchoNestPlugin::getSongProfile(const QString &caller, const QVariant& data, InfoCustomData &customData, const QString &item) { //WARNING: Totally not implemented yet @@ -80,7 +80,7 @@ void EchoNestPlugin::getSongProfile(const QString &caller, const QVariant& data, // connect(reply, SIGNAL(finished()), SLOT(getArtistBiographySlot())); } -void EchoNestPlugin::getArtistBiography(const QString &caller, const QVariant& data, InfoCustomDataHash &customData) +void EchoNestPlugin::getArtistBiography(const QString &caller, const QVariant& data, InfoCustomData &customData) { if( !isValidArtistData( caller, data, customData ) ) return; @@ -94,7 +94,7 @@ void EchoNestPlugin::getArtistBiography(const QString &caller, const QVariant& d connect(reply, SIGNAL(finished()), SLOT(getArtistBiographySlot())); } -void EchoNestPlugin::getArtistFamiliarity(const QString &caller, const QVariant& data, InfoCustomDataHash &customData) +void EchoNestPlugin::getArtistFamiliarity(const QString &caller, const QVariant& data, InfoCustomData &customData) { if( !isValidArtistData( caller, data, customData ) ) return; @@ -109,7 +109,7 @@ void EchoNestPlugin::getArtistFamiliarity(const QString &caller, const QVariant& connect(reply, SIGNAL(finished()), SLOT(getArtistFamiliaritySlot())); } -void EchoNestPlugin::getArtistHotttnesss(const QString &caller, const QVariant& data, InfoCustomDataHash &customData) +void EchoNestPlugin::getArtistHotttnesss(const QString &caller, const QVariant& data, InfoCustomData &customData) { if( !isValidArtistData( caller, data, customData ) ) return; @@ -123,7 +123,7 @@ void EchoNestPlugin::getArtistHotttnesss(const QString &caller, const QVariant& connect(reply, SIGNAL(finished()), SLOT(getArtistHotttnesssSlot())); } -void EchoNestPlugin::getArtistTerms(const QString &caller, const QVariant& data, InfoCustomDataHash &customData) +void EchoNestPlugin::getArtistTerms(const QString &caller, const QVariant& data, InfoCustomData &customData) { if( !isValidArtistData( caller, data, customData ) ) return; @@ -137,7 +137,7 @@ void EchoNestPlugin::getArtistTerms(const QString &caller, const QVariant& data, connect(reply, SIGNAL(finished()), SLOT(getArtistTermsSlot())); } -void EchoNestPlugin::getMiscTopTerms(const QString &caller, const QVariant& data, InfoCustomDataHash& customData) +void EchoNestPlugin::getMiscTopTerms(const QString &caller, const QVariant& data, InfoCustomData& customData) { QNetworkReply* reply = Echonest::Artist::topTerms( 20 ); m_replyMap[reply] = customData; @@ -230,7 +230,7 @@ void EchoNestPlugin::getMiscTopSlot() reply->deleteLater(); } -bool EchoNestPlugin::isValidArtistData(const QString &caller, const QVariant& data, InfoCustomDataHash &customData) +bool EchoNestPlugin::isValidArtistData(const QString &caller, const QVariant& data, InfoCustomData &customData) { if (data.isNull() || !data.isValid() || !data.canConvert()) { @@ -246,7 +246,7 @@ bool EchoNestPlugin::isValidArtistData(const QString &caller, const QVariant& da return true; } -bool EchoNestPlugin::isValidTrackData(const QString &caller, const QVariant& data, InfoCustomDataHash &customData) +bool EchoNestPlugin::isValidTrackData(const QString &caller, const QVariant& data, InfoCustomData &customData) { if (data.isNull() || !data.isValid() || !data.canConvert()) { diff --git a/src/infosystem/infoplugins/echonestplugin.h b/src/infosystem/infoplugins/echonestplugin.h index 734ceecd2..d4a4db367 100644 --- a/src/infosystem/infoplugins/echonestplugin.h +++ b/src/infosystem/infoplugins/echonestplugin.h @@ -42,18 +42,18 @@ public: EchoNestPlugin(QObject *parent); virtual ~EchoNestPlugin(); - void getInfo( const QString &caller, const InfoType type, const QVariant &data, InfoCustomDataHash customData ); + void getInfo( const QString &caller, const InfoType type, const QVariant &data, InfoCustomData customData ); private: - void getSongProfile( const QString &caller, const QVariant &data, InfoCustomDataHash &customData, const QString &item = QString() ); - void getArtistBiography ( const QString &caller, const QVariant &data, InfoCustomDataHash &customData ); - void getArtistFamiliarity( const QString &caller, const QVariant &data, InfoCustomDataHash &customData ); - void getArtistHotttnesss( const QString &caller, const QVariant &data, InfoCustomDataHash &customData ); - void getArtistTerms( const QString &caller, const QVariant &data, InfoCustomDataHash &customData ); - void getMiscTopTerms( const QString &caller, const QVariant &data, InfoCustomDataHash &customData ); + void getSongProfile( const QString &caller, const QVariant &data, InfoCustomData &customData, const QString &item = QString() ); + void getArtistBiography ( const QString &caller, const QVariant &data, InfoCustomData &customData ); + void getArtistFamiliarity( const QString &caller, const QVariant &data, InfoCustomData &customData ); + void getArtistHotttnesss( const QString &caller, const QVariant &data, InfoCustomData &customData ); + void getArtistTerms( const QString &caller, const QVariant &data, InfoCustomData &customData ); + void getMiscTopTerms( const QString &caller, const QVariant &data, InfoCustomData &customData ); - bool isValidArtistData( const QString &caller, const QVariant& data, InfoCustomDataHash& customData ); - bool isValidTrackData( const QString &caller, const QVariant& data, InfoCustomDataHash& customData ); + bool isValidArtistData( const QString &caller, const QVariant& data, InfoCustomData& customData ); + bool isValidTrackData( const QString &caller, const QVariant& data, InfoCustomData& customData ); Echonest::Artist artistFromReply( QNetworkReply* ); private slots: @@ -64,7 +64,7 @@ private slots: void getMiscTopSlot(); private: - QHash< QNetworkReply*, InfoCustomDataHash > m_replyMap; + QHash< QNetworkReply*, InfoCustomData > m_replyMap; QHash< QNetworkReply*, QString > m_callerMap; }; diff --git a/src/infosystem/infoplugins/lastfmplugin.cpp b/src/infosystem/infoplugins/lastfmplugin.cpp index 81272b86f..5848f6f68 100644 --- a/src/infosystem/infoplugins/lastfmplugin.cpp +++ b/src/infosystem/infoplugins/lastfmplugin.cpp @@ -92,7 +92,7 @@ LastFmPlugin::~LastFmPlugin() } void -LastFmPlugin::dataError( const QString &caller, const InfoType type, const QVariant& data, Tomahawk::InfoSystem::InfoCustomDataHash &customData ) +LastFmPlugin::dataError( const QString &caller, const InfoType type, const QVariant& data, Tomahawk::InfoSystem::InfoCustomData &customData ) { emit info( caller, type, data, QVariant(), customData ); emit finished( caller, type ); @@ -100,7 +100,7 @@ LastFmPlugin::dataError( const QString &caller, const InfoType type, const QVari } void -LastFmPlugin::getInfo( const QString &caller, const InfoType type, const QVariant& data, Tomahawk::InfoSystem::InfoCustomDataHash customData ) +LastFmPlugin::getInfo( const QString &caller, const InfoType type, const QVariant& data, Tomahawk::InfoSystem::InfoCustomData customData ) { qDebug() << Q_FUNC_INFO; if ( type == InfoMiscSubmitNowPlaying ) @@ -114,14 +114,14 @@ LastFmPlugin::getInfo( const QString &caller, const InfoType type, const QVarian } void -LastFmPlugin::nowPlaying( const QString &caller, const InfoType type, const QVariant& data, Tomahawk::InfoSystem::InfoCustomDataHash &customData ) +LastFmPlugin::nowPlaying( const QString &caller, const InfoType type, const QVariant& data, Tomahawk::InfoSystem::InfoCustomData &customData ) { - if ( !data.canConvert< Tomahawk::InfoSystem::InfoCustomDataHash >() || !m_scrobbler ) + if ( !data.canConvert< Tomahawk::InfoSystem::InfoCustomData >() || !m_scrobbler ) { dataError( caller, type, data, customData ); return; } - InfoCustomDataHash hash = data.value< Tomahawk::InfoSystem::InfoCustomDataHash >(); + InfoCustomData hash = data.value< Tomahawk::InfoSystem::InfoCustomData >(); if ( !hash.contains( "title" ) || !hash.contains( "artist" ) || !hash.contains( "album" ) || !hash.contains( "duration" ) ) { dataError( caller, type, data, customData ); @@ -143,7 +143,7 @@ LastFmPlugin::nowPlaying( const QString &caller, const InfoType type, const QVar } void -LastFmPlugin::scrobble( const QString &caller, const InfoType type, const QVariant& data, Tomahawk::InfoSystem::InfoCustomDataHash &customData ) +LastFmPlugin::scrobble( const QString &caller, const InfoType type, const QVariant& data, Tomahawk::InfoSystem::InfoCustomData &customData ) { Q_ASSERT( QThread::currentThread() == thread() ); @@ -162,33 +162,50 @@ LastFmPlugin::scrobble( const QString &caller, const InfoType type, const QVaria } void -LastFmPlugin::fetchCoverArt( const QString &caller, const InfoType type, const QVariant& data, Tomahawk::InfoSystem::InfoCustomDataHash &customData ) +LastFmPlugin::fetchCoverArt( const QString &caller, const InfoType type, const QVariant& data, Tomahawk::InfoSystem::InfoCustomData &customData ) { qDebug() << Q_FUNC_INFO; - if ( !data.canConvert< Tomahawk::InfoSystem::InfoCustomDataHash >() ) + if ( !data.canConvert< Tomahawk::InfoSystem::InfoCustomData >() ) { dataError( caller, type, data, customData ); return; } - InfoCustomDataHash hash = data.value< Tomahawk::InfoSystem::InfoCustomDataHash >(); + InfoCustomData hash = data.value< Tomahawk::InfoSystem::InfoCustomData >(); if ( !hash.contains( "artist" ) || !hash.contains( "album" ) ) { dataError( caller, type, data, customData ); return; } - QString artistName = hash["artist"].toString(); - QString albumName = hash["album"].toString(); + Tomahawk::InfoSystem::InfoCacheCriteria criteria; + criteria["artist"] = hash["artist"].toString(); + criteria["album"] = hash["album"].toString(); - QString imgurl = "http://ws.audioscrobbler.com/2.0/?method=album.imageredirect&artist=%1&album=%2&size=medium&api_key=7a90f6672a04b809ee309af169f34b8b"; - QNetworkRequest req( imgurl.arg( artistName ).arg( albumName ) ); - QNetworkReply* reply = TomahawkUtils::nam()->get( req ); - reply->setProperty("customData", QVariant::fromValue(customData)); - reply->setProperty("origData", data); - reply->setProperty("caller", caller); - reply->setProperty("type", (uint)(type) ); + emit getCachedInfo( criteria, caller, type, data, customData ); +} - connect( reply, SIGNAL( finished() ), SLOT( coverArtReturned() ) ); +void +LastFmPlugin::notInCacheSlot( QHash criteria, QString caller, Tomahawk::InfoSystem::InfoType type, QVariant input, Tomahawk::InfoSystem::InfoCustomData customData ) +{ + qDebug() << Q_FUNC_INFO; + if ( type == InfoAlbumCoverArt ) + { + QString artistName = criteria["artist"]; + QString albumName = criteria["album"]; + + QString imgurl = "http://ws.audioscrobbler.com/2.0/?method=album.imageredirect&artist=%1&album=%2&size=medium&api_key=7a90f6672a04b809ee309af169f34b8b"; + QNetworkRequest req( imgurl.arg( artistName ).arg( albumName ) ); + QNetworkReply* reply = TomahawkUtils::nam()->get( req ); + reply->setProperty( "customData", QVariant::fromValue( customData ) ); + reply->setProperty( "origData", input ); + reply->setProperty( "caller", caller ); + reply->setProperty( "type", (uint)(type) ); + + connect( reply, SIGNAL( finished() ), SLOT( coverArtReturned() ) ); + return; + } + else + qDebug() << "Couldn't figure out what to do with this type of request after cache miss"; } void @@ -200,17 +217,26 @@ LastFmPlugin::coverArtReturned() if ( redir.isEmpty() ) { const QByteArray ba = reply->readAll(); - Tomahawk::InfoSystem::InfoCustomDataHash returnedData; + InfoCustomData returnedData; returnedData["imgbytes"] = ba; returnedData["url"] = reply->url().toString(); + + InfoCustomData customData = reply->property( "customData" ).value< Tomahawk::InfoSystem::InfoCustomData >(); + InfoType type = (Tomahawk::InfoSystem::InfoType)(reply->property( "type" ).toUInt()); emit info( reply->property( "caller" ).toString(), - (Tomahawk::InfoSystem::InfoType)(reply->property( "type" ).toUInt()), + type, reply->property( "origData" ), returnedData, - reply->property( "customData" ).value< Tomahawk::InfoSystem::InfoCustomDataHash >() + customData ); emit finished( reply->property( "caller" ).toString(), (Tomahawk::InfoSystem::InfoType)(reply->property( "type" ).toUInt()) ); + + InfoCustomData origData = reply->property( "origData" ).value< Tomahawk::InfoSystem::InfoCustomData >(); + Tomahawk::InfoSystem::InfoCacheCriteria criteria; + criteria["artist"] = origData["artist"].toString(); + criteria["album"] = origData["album"].toString(); + emit updateCache( criteria, type, returnedData ); } else { diff --git a/src/infosystem/infoplugins/lastfmplugin.h b/src/infosystem/infoplugins/lastfmplugin.h index 3f396edfb..589d40db1 100644 --- a/src/infosystem/infoplugins/lastfmplugin.h +++ b/src/infosystem/infoplugins/lastfmplugin.h @@ -43,19 +43,20 @@ public: LastFmPlugin( QObject *parent ); virtual ~LastFmPlugin(); - void getInfo( const QString &caller, const InfoType type, const QVariant &data, InfoCustomDataHash customData ); + void getInfo( const QString &caller, const InfoType type, const QVariant &data, InfoCustomData customData ); public slots: void settingsChanged(); void onAuthenticated(); void coverArtReturned(); + virtual void notInCacheSlot( Tomahawk::InfoSystem::InfoCacheCriteria criteria, QString caller, Tomahawk::InfoSystem::InfoType type, QVariant input, Tomahawk::InfoSystem::InfoCustomData customData ); private: - void fetchCoverArt( const QString &caller, const InfoType type, const QVariant& data, Tomahawk::InfoSystem::InfoCustomDataHash &customData ); - void scrobble( const QString &caller, const InfoType type, const QVariant& data, InfoCustomDataHash &customData ); + void fetchCoverArt( const QString &caller, const InfoType type, const QVariant& data, Tomahawk::InfoSystem::InfoCustomData &customData ); + void scrobble( const QString &caller, const InfoType type, const QVariant& data, InfoCustomData &customData ); void createScrobbler(); - void nowPlaying( const QString &caller, const InfoType type, const QVariant& data, InfoCustomDataHash &customData ); - void dataError( const QString &caller, const InfoType type, const QVariant& data, InfoCustomDataHash &customData ); + void nowPlaying( const QString &caller, const InfoType type, const QVariant& data, InfoCustomData &customData ); + void dataError( const QString &caller, const InfoType type, const QVariant& data, InfoCustomData &customData ); lastfm::MutableTrack m_track; lastfm::Audioscrobbler* m_scrobbler; diff --git a/src/infosystem/infoplugins/musixmatchplugin.cpp b/src/infosystem/infoplugins/musixmatchplugin.cpp index 0ee319f78..cc2cd576f 100644 --- a/src/infosystem/infoplugins/musixmatchplugin.cpp +++ b/src/infosystem/infoplugins/musixmatchplugin.cpp @@ -42,12 +42,12 @@ MusixMatchPlugin::~MusixMatchPlugin() qDebug() << Q_FUNC_INFO; } -void MusixMatchPlugin::getInfo(const QString &caller, const InfoType type, const QVariant& data, Tomahawk::InfoSystem::InfoCustomDataHash customData) +void MusixMatchPlugin::getInfo(const QString &caller, const InfoType type, const QVariant& data, Tomahawk::InfoSystem::InfoCustomData customData) { qDebug() << Q_FUNC_INFO; - if( !isValidTrackData(caller, data, customData) || !data.canConvert()) + if( !isValidTrackData(caller, data, customData) || !data.canConvert()) return; - Tomahawk::InfoSystem::InfoCustomDataHash hash = data.value(); + Tomahawk::InfoSystem::InfoCustomData hash = data.value(); QString artist = hash["artistName"].toString(); QString track = hash["trackName"].toString(); if( artist.isEmpty() || track.isEmpty() ) @@ -63,24 +63,24 @@ void MusixMatchPlugin::getInfo(const QString &caller, const InfoType type, const url.addQueryItem("q_artist", artist); url.addQueryItem("q_track", track); QNetworkReply* reply = TomahawkUtils::nam()->get(QNetworkRequest(url)); - reply->setProperty("customData", QVariant::fromValue(customData)); + reply->setProperty("customData", QVariant::fromValue(customData)); reply->setProperty("origData", data); reply->setProperty("caller", caller); connect(reply, SIGNAL(finished()), SLOT(trackSearchSlot())); } -bool MusixMatchPlugin::isValidTrackData(const QString &caller, const QVariant& data, Tomahawk::InfoSystem::InfoCustomDataHash &customData) +bool MusixMatchPlugin::isValidTrackData(const QString &caller, const QVariant& data, Tomahawk::InfoSystem::InfoCustomData &customData) { qDebug() << Q_FUNC_INFO; - if (data.isNull() || !data.isValid() || !data.canConvert()) + if (data.isNull() || !data.isValid() || !data.canConvert()) { emit info(caller, Tomahawk::InfoSystem::InfoTrackLyrics, data, QVariant(), customData); emit finished(caller, Tomahawk::InfoSystem::InfoTrackLyrics); qDebug() << "MusixMatchPlugin::isValidTrackData: Data null, invalid, or can't convert"; return false; } - InfoCustomDataHash hash = data.value(); + InfoCustomData hash = data.value(); if (hash["trackName"].toString().isEmpty() ) { emit info(caller, Tomahawk::InfoSystem::InfoTrackLyrics, data, QVariant(), customData); @@ -104,7 +104,7 @@ void MusixMatchPlugin::trackSearchSlot() QNetworkReply* oldReply = qobject_cast( sender() ); if (!oldReply) { - emit info(QString(), Tomahawk::InfoSystem::InfoTrackLyrics, QVariant(), QVariant(), Tomahawk::InfoSystem::InfoCustomDataHash()); + emit info(QString(), Tomahawk::InfoSystem::InfoTrackLyrics, QVariant(), QVariant(), Tomahawk::InfoSystem::InfoCustomData()); return; } QDomDocument doc; @@ -113,7 +113,7 @@ void MusixMatchPlugin::trackSearchSlot() QDomNodeList domNodeList = doc.elementsByTagName("track_id"); if (domNodeList.isEmpty()) { - emit info(oldReply->property("caller").toString(), Tomahawk::InfoSystem::InfoTrackLyrics, oldReply->property("origData"), QVariant(), oldReply->property("customData").value()); + emit info(oldReply->property("caller").toString(), Tomahawk::InfoSystem::InfoTrackLyrics, oldReply->property("origData"), QVariant(), oldReply->property("customData").value()); emit finished(oldReply->property("caller").toString(), Tomahawk::InfoSystem::InfoTrackLyrics); return; } @@ -135,7 +135,7 @@ void MusixMatchPlugin::trackLyricsSlot() QNetworkReply* reply = qobject_cast( sender() ); if (!reply) { - emit info(QString(), Tomahawk::InfoSystem::InfoTrackLyrics, QVariant(), QVariant(), Tomahawk::InfoSystem::InfoCustomDataHash()); + emit info(QString(), Tomahawk::InfoSystem::InfoTrackLyrics, QVariant(), QVariant(), Tomahawk::InfoSystem::InfoCustomData()); return; } QDomDocument doc; @@ -143,12 +143,12 @@ void MusixMatchPlugin::trackLyricsSlot() QDomNodeList domNodeList = doc.elementsByTagName("lyrics_body"); if (domNodeList.isEmpty()) { - emit info(reply->property("caller").toString(), Tomahawk::InfoSystem::InfoTrackLyrics, reply->property("origData"), QVariant(), reply->property("customData").value()); + emit info(reply->property("caller").toString(), Tomahawk::InfoSystem::InfoTrackLyrics, reply->property("origData"), QVariant(), reply->property("customData").value()); emit finished(reply->property("caller").toString(), Tomahawk::InfoSystem::InfoTrackLyrics); return; } QString lyrics = domNodeList.at(0).toElement().text(); qDebug() << "Emitting lyrics: " << lyrics; - emit info(reply->property("caller").toString(), Tomahawk::InfoSystem::InfoTrackLyrics, reply->property("origData"), QVariant(lyrics), reply->property("customData").value()); + emit info(reply->property("caller").toString(), Tomahawk::InfoSystem::InfoTrackLyrics, reply->property("origData"), QVariant(lyrics), reply->property("customData").value()); emit finished(reply->property("caller").toString(), Tomahawk::InfoSystem::InfoTrackLyrics); } diff --git a/src/infosystem/infoplugins/musixmatchplugin.h b/src/infosystem/infoplugins/musixmatchplugin.h index 284c81515..255ebd53d 100644 --- a/src/infosystem/infoplugins/musixmatchplugin.h +++ b/src/infosystem/infoplugins/musixmatchplugin.h @@ -36,10 +36,10 @@ public: MusixMatchPlugin(QObject *parent); virtual ~MusixMatchPlugin(); - void getInfo(const QString &caller, const InfoType type, const QVariant &data, InfoCustomDataHash customData); + void getInfo(const QString &caller, const InfoType type, const QVariant &data, InfoCustomData customData); private: - bool isValidTrackData( const QString &caller, const QVariant& data, InfoCustomDataHash &customData ); + bool isValidTrackData( const QString &caller, const QVariant& data, InfoCustomData &customData ); public slots: void trackSearchSlot(); diff --git a/src/infosystem/infosystem.cpp b/src/infosystem/infosystem.cpp index deba612f0..d03b53f10 100644 --- a/src/infosystem/infosystem.cpp +++ b/src/infosystem/infosystem.cpp @@ -37,12 +37,26 @@ InfoPlugin::InfoPlugin(QObject *parent) qDebug() << Q_FUNC_INFO; InfoSystem *system = qobject_cast< InfoSystem* >( parent ); if( system ) - QObject::connect( - system->getCache(), - SIGNAL( notInCache( QHash< QString, QString >, QString, Tomahawk::InfoSystem::InfoType, QVariant, QVariant, Tomahawk::InfoSystem::InfoCustomDataHash ) ), - this, - SLOT( notInCacheSlot( QHash< QString, QString >, QString, Tomahawk::InfoSystem::InfoType, QVariant, QVariant, Tomahawk::InfoSystem::InfoCustomDataHash ) ) - ); + { + QObject::connect( + this, + SIGNAL( getCachedInfo( Tomahawk::InfoSystem::InfoCacheCriteria, QString, Tomahawk::InfoSystem::InfoType, QVariant, Tomahawk::InfoSystem::InfoCustomData ) ), + system->getCache(), + SLOT( getCachedInfoSlot( Tomahawk::InfoSystem::InfoCacheCriteria, QString, Tomahawk::InfoSystem::InfoType, QVariant, Tomahawk::InfoSystem::InfoCustomData ) ) + ); + QObject::connect( + system->getCache(), + SIGNAL( notInCache( Tomahawk::InfoSystem::InfoCacheCriteria, QString, Tomahawk::InfoSystem::InfoType, QVariant, Tomahawk::InfoSystem::InfoCustomData ) ), + this, + SLOT( notInCacheSlot( Tomahawk::InfoSystem::InfoCacheCriteria, QString, Tomahawk::InfoSystem::InfoType, QVariant, Tomahawk::InfoSystem::InfoCustomData ) ) + ); + QObject::connect( + this, + SIGNAL( updateCache( Tomahawk::InfoSystem::InfoCacheCriteria, Tomahawk::InfoSystem::InfoType, QVariant ) ), + system->getCache(), + SLOT( updateCacheSlot( Tomahawk::InfoSystem::InfoCacheCriteria, Tomahawk::InfoSystem::InfoType, QVariant ) ) + ); + } } @@ -50,32 +64,44 @@ InfoSystem::InfoSystem(QObject *parent) : QObject(parent) { qDebug() << Q_FUNC_INFO; - qRegisterMetaType > >("Tomahawk::InfoSystem::InfoGenericMap"); - qRegisterMetaType >("Tomahawk::InfoSystem::InfoCustomDataHash"); + qRegisterMetaType< QMap< QString, QMap< QString, QString > > >( "Tomahawk::InfoSystem::InfoGenericMap" ); + qRegisterMetaType< QHash< QString, QVariant > >( "Tomahawk::InfoSystem::InfoCustomData" ); + qRegisterMetaType< QHash< QString, QString > >( "Tomahawk::InfoSystem::InfoCacheCriteria" ); + qRegisterMetaType< Tomahawk::InfoSystem::InfoType >( "Tomahawk::InfoSystem::InfoType" ); m_infoSystemCacheThreadController = new QThread( this ); m_cache = new InfoSystemCache(); m_cache->moveToThread( m_infoSystemCacheThreadController ); m_infoSystemCacheThreadController->start( QThread::IdlePriority ); - InfoPluginPtr enptr(new EchoNestPlugin(this)); - m_plugins.append(enptr); - InfoPluginPtr mmptr(new MusixMatchPlugin(this)); - m_plugins.append(mmptr); - InfoPluginPtr lfmptr(new LastFmPlugin(this)); - m_plugins.append(lfmptr); + InfoPluginPtr enptr( new EchoNestPlugin( this ) ); + m_plugins.append( enptr ); + InfoPluginPtr mmptr( new MusixMatchPlugin( this ) ); + m_plugins.append( mmptr ); + InfoPluginPtr lfmptr( new LastFmPlugin( this ) ); + m_plugins.append( lfmptr ); Q_FOREACH( InfoPluginPtr plugin, m_plugins ) { - connect(plugin.data(), SIGNAL(info(QString, Tomahawk::InfoSystem::InfoType, QVariant, QVariant, Tomahawk::InfoSystem::InfoCustomDataHash)), - this, SLOT(infoSlot(QString, Tomahawk::InfoSystem::InfoType, QVariant, QVariant, Tomahawk::InfoSystem::InfoCustomDataHash)), Qt::UniqueConnection); - connect(plugin.data(), SIGNAL(finished(QString, Tomahawk::InfoSystem::InfoType)), - this, SLOT(finishedSlot(QString, Tomahawk::InfoSystem::InfoType)), Qt::UniqueConnection); + connect( + plugin.data(), + SIGNAL( info( QString, Tomahawk::InfoSystem::InfoType, QVariant, QVariant, Tomahawk::InfoSystem::InfoCustomData ) ), + this, + SLOT( infoSlot( QString, Tomahawk::InfoSystem::InfoType, QVariant, QVariant, Tomahawk::InfoSystem::InfoCustomData ) ), + Qt::UniqueConnection + ); + + connect( + plugin.data(), + SIGNAL( finished( QString, Tomahawk::InfoSystem::InfoType ) ), + this, + SLOT( finishedSlot( QString, Tomahawk::InfoSystem::InfoType ) ), Qt::UniqueConnection + ); } - connect(m_cache, SIGNAL(info(QString, Tomahawk::InfoSystem::InfoType, QVariant, QVariant, Tomahawk::InfoSystem::InfoCustomDataHash)), - this, SLOT(infoSlot(QString, Tomahawk::InfoSystem::InfoType, QVariant, QVariant, Tomahawk::InfoSystem::InfoCustomDataHash)), Qt::UniqueConnection); - connect(m_cache, SIGNAL(finished(QString, Tomahawk::InfoSystem::InfoType)), - this, SLOT(finishedSlot(QString, Tomahawk::InfoSystem::InfoType)), Qt::UniqueConnection); + connect( m_cache, SIGNAL( info( QString, Tomahawk::InfoSystem::InfoType, QVariant, QVariant, Tomahawk::InfoSystem::InfoCustomData ) ), + this, SLOT( infoSlot( QString, Tomahawk::InfoSystem::InfoType, QVariant, QVariant, Tomahawk::InfoSystem::InfoCustomData ) ), Qt::UniqueConnection ); + connect( m_cache, SIGNAL( finished( QString, Tomahawk::InfoSystem::InfoType ) ), + this, SLOT( finishedSlot( QString, Tomahawk::InfoSystem::InfoType ) ), Qt::UniqueConnection ); } InfoSystem::~InfoSystem() @@ -125,7 +151,7 @@ QLinkedList< InfoPluginPtr > InfoSystem::determineOrderedMatches(const InfoType return providers; } -void InfoSystem::getInfo(const QString &caller, const InfoType type, const QVariant& data, InfoCustomDataHash customData) +void InfoSystem::getInfo(const QString &caller, const InfoType type, const QVariant& data, InfoCustomData customData) { qDebug() << Q_FUNC_INFO; QLinkedList< InfoPluginPtr > providers = determineOrderedMatches(type); @@ -149,13 +175,13 @@ void InfoSystem::getInfo(const QString &caller, const InfoType type, const QVari ptr.data()->getInfo(caller, type, data, customData); } -void InfoSystem::getInfo(const QString &caller, const InfoMap &input, InfoCustomDataHash customData) +void InfoSystem::getInfo(const QString &caller, const InfoMap &input, InfoCustomData customData) { Q_FOREACH( InfoType type, input.keys() ) getInfo(caller, type, input[type], customData); } -void InfoSystem::infoSlot(QString target, InfoType type, QVariant input, QVariant output, InfoCustomDataHash customData) +void InfoSystem::infoSlot(QString target, InfoType type, QVariant input, QVariant output, InfoCustomData customData) { qDebug() << Q_FUNC_INFO; qDebug() << "current count in dataTracker is " << m_dataTracker[target][type]; diff --git a/src/infosystem/infosystemcache.cpp b/src/infosystem/infosystemcache.cpp index e69de29bb..c25fb10bb 100644 --- a/src/infosystem/infosystemcache.cpp +++ b/src/infosystem/infosystemcache.cpp @@ -0,0 +1,34 @@ +/* === This file is part of Tomahawk Player - === + * + * Copyright 2010-2011, Christian Muehlhaeuser + * + * Tomahawk is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * Tomahawk is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with Tomahawk. If not, see . + */ + +#include + +#include "infosystemcache.h" + +void +Tomahawk::InfoSystem::InfoSystemCache::getCachedInfoSlot( Tomahawk::InfoSystem::InfoCacheCriteria criteria, QString caller, Tomahawk::InfoSystem::InfoType type, QVariant input, Tomahawk::InfoSystem::InfoCustomData customData ) +{ + qDebug() << Q_FUNC_INFO; + emit notInCache( criteria, caller, type, input, customData ); +} + +void +Tomahawk::InfoSystem::InfoSystemCache::updateCacheSlot( Tomahawk::InfoSystem::InfoCacheCriteria criteria, Tomahawk::InfoSystem::InfoType type, QVariant output ) +{ + qDebug() << Q_FUNC_INFO; +} diff --git a/src/infosystem/infosystemcache.h b/src/infosystem/infosystemcache.h index 89aa9df6e..44476314a 100644 --- a/src/infosystem/infosystemcache.h +++ b/src/infosystem/infosystemcache.h @@ -47,10 +47,14 @@ public: } signals: - void notInCache( QHash< QString, QString > criteria, QString caller, Tomahawk::InfoSystem::InfoType type, QVariant input, QVariant output, Tomahawk::InfoSystem::InfoCustomDataHash customData ); - void info( QString caller, Tomahawk::InfoSystem::InfoType type, QVariant input, QVariant output, Tomahawk::InfoSystem::InfoCustomDataHash customData ); + void notInCache( Tomahawk::InfoSystem::InfoCacheCriteria criteria, QString caller, Tomahawk::InfoSystem::InfoType type, QVariant input, Tomahawk::InfoSystem::InfoCustomData customData ); + void info( QString caller, Tomahawk::InfoSystem::InfoType type, QVariant input, QVariant output, Tomahawk::InfoSystem::InfoCustomData customData ); void finished( QString, Tomahawk::InfoSystem::InfoType ); +public slots: + void getCachedInfoSlot( Tomahawk::InfoSystem::InfoCacheCriteria criteria, QString caller, Tomahawk::InfoSystem::InfoType type, QVariant input, Tomahawk::InfoSystem::InfoCustomData customData ); + void updateCacheSlot( Tomahawk::InfoSystem::InfoCacheCriteria criteria, Tomahawk::InfoSystem::InfoType type, QVariant output ); + }; } //namespace InfoSystem diff --git a/src/scrobbler.cpp b/src/scrobbler.cpp index 06ebe33f5..ef599db76 100644 --- a/src/scrobbler.cpp +++ b/src/scrobbler.cpp @@ -39,8 +39,8 @@ Scrobbler::Scrobbler( QObject* parent ) SLOT( engineTick( unsigned int ) ), Qt::QueuedConnection ); connect( TomahawkApp::instance()->infoSystem(), - SIGNAL( info( QString, Tomahawk::InfoSystem::InfoType, QVariant, QVariant, Tomahawk::InfoSystem::InfoCustomDataHash ) ), - SLOT( infoSystemInfo( QString, Tomahawk::InfoSystem::InfoType, QVariant, QVariant, Tomahawk::InfoSystem::InfoCustomDataHash ) ) ); + SIGNAL( info( QString, Tomahawk::InfoSystem::InfoType, QVariant, QVariant, Tomahawk::InfoSystem::InfoCustomData ) ), + SLOT( infoSystemInfo( QString, Tomahawk::InfoSystem::InfoType, QVariant, QVariant, Tomahawk::InfoSystem::InfoCustomData ) ) ); connect( TomahawkApp::instance()->infoSystem(), SIGNAL( finished( QString ) ), SLOT( infoSystemFinished( QString ) ) ); } @@ -63,7 +63,7 @@ Scrobbler::trackStarted( const Tomahawk::result_ptr& track ) scrobble(); } - Tomahawk::InfoSystem::InfoCustomDataHash trackInfo; + Tomahawk::InfoSystem::InfoCustomData trackInfo; trackInfo["title"] = QVariant::fromValue< QString >( track->track() ); trackInfo["artist"] = QVariant::fromValue< QString >( track->artist()->name() ); @@ -71,7 +71,7 @@ Scrobbler::trackStarted( const Tomahawk::result_ptr& track ) trackInfo["duration"] = QVariant::fromValue< uint >( track->duration() ); TomahawkApp::instance()->infoSystem()->getInfo( s_infoIdentifier, Tomahawk::InfoSystem::InfoMiscSubmitNowPlaying, - QVariant::fromValue< Tomahawk::InfoSystem::InfoCustomDataHash >( trackInfo ), Tomahawk::InfoSystem::InfoCustomDataHash() ); + QVariant::fromValue< Tomahawk::InfoSystem::InfoCustomData >( trackInfo ), Tomahawk::InfoSystem::InfoCustomData() ); m_scrobblePoint = ScrobblePoint( track->duration() / 2 ); } @@ -119,11 +119,11 @@ Scrobbler::scrobble() TomahawkApp::instance()->infoSystem()->getInfo( s_infoIdentifier, Tomahawk::InfoSystem::InfoMiscSubmitScrobble, - QVariant(), Tomahawk::InfoSystem::InfoCustomDataHash() ); + QVariant(), Tomahawk::InfoSystem::InfoCustomData() ); } void -Scrobbler::infoSystemInfo( QString caller, Tomahawk::InfoSystem::InfoType type, QVariant input, QVariant output, Tomahawk::InfoSystem::InfoCustomDataHash customData ) +Scrobbler::infoSystemInfo( QString caller, Tomahawk::InfoSystem::InfoType type, QVariant input, QVariant output, Tomahawk::InfoSystem::InfoCustomData customData ) { if ( caller == s_infoIdentifier ) { diff --git a/src/scrobbler.h b/src/scrobbler.h index 4ed020fba..11741beec 100644 --- a/src/scrobbler.h +++ b/src/scrobbler.h @@ -45,7 +45,7 @@ public slots: void trackStopped(); void engineTick( unsigned int secondsElapsed ); - void infoSystemInfo( QString caller, Tomahawk::InfoSystem::InfoType type, QVariant input, QVariant output, Tomahawk::InfoSystem::InfoCustomDataHash customData ); + void infoSystemInfo( QString caller, Tomahawk::InfoSystem::InfoType type, QVariant input, QVariant output, Tomahawk::InfoSystem::InfoCustomData customData ); void infoSystemFinished( QString target ); private: diff --git a/src/xmppbot/xmppbot.cpp b/src/xmppbot/xmppbot.cpp index d7d267f9d..464e09ede 100644 --- a/src/xmppbot/xmppbot.cpp +++ b/src/xmppbot/xmppbot.cpp @@ -67,8 +67,8 @@ XMPPBot::XMPPBot(QObject *parent) SLOT(newTrackSlot(const Tomahawk::result_ptr &))); connect(TomahawkApp::instance()->infoSystem(), - SIGNAL(info(QString, Tomahawk::InfoSystem::InfoType, QVariant, QVariant, Tomahawk::InfoSystem::InfoCustomDataHash)), - SLOT(infoReturnedSlot(QString, Tomahawk::InfoSystem::InfoType, QVariant, QVariant, Tomahawk::InfoSystem::InfoCustomDataHash))); + SIGNAL(info(QString, Tomahawk::InfoSystem::InfoType, QVariant, QVariant, Tomahawk::InfoSystem::InfoCustomData)), + SLOT(infoReturnedSlot(QString, Tomahawk::InfoSystem::InfoType, QVariant, QVariant, Tomahawk::InfoSystem::InfoCustomData))); connect(TomahawkApp::instance()->infoSystem(), SIGNAL(finished(QString)), SLOT(infoFinishedSlot(QString))); @@ -215,10 +215,10 @@ void XMPPBot::handleMessage(const Message& msg, MessageSession* session) infoMap[InfoArtistFamiliarity] = m_currTrack.data()->artist()->name(); if (token == "lyrics") { - InfoCustomDataHash myhash; + InfoCustomData myhash; myhash["trackName"] = QVariant::fromValue(m_currTrack.data()->track()); myhash["artistName"] = QVariant::fromValue(m_currTrack.data()->artist()->name()); - infoMap[InfoTrackLyrics] = QVariant::fromValue(myhash); + infoMap[InfoTrackLyrics] = QVariant::fromValue(myhash); } } @@ -235,12 +235,12 @@ void XMPPBot::handleMessage(const Message& msg, MessageSession* session) QString waitMsg("Please wait..."); Message retMsg(Message::Chat, JID(originatingJid.toStdString()), waitMsg.toStdString()); m_client.data()->send(retMsg); - Tomahawk::InfoSystem::InfoCustomDataHash hash; + Tomahawk::InfoSystem::InfoCustomData hash; hash["XMPPBotSendToJID"] = originatingJid; TomahawkApp::instance()->infoSystem()->getInfo(s_infoIdentifier, infoMap, hash); } -void XMPPBot::infoReturnedSlot(QString caller, Tomahawk::InfoSystem::InfoType type, QVariant input, QVariant output, Tomahawk::InfoSystem::InfoCustomDataHash customData) +void XMPPBot::infoReturnedSlot(QString caller, Tomahawk::InfoSystem::InfoType type, QVariant input, QVariant output, Tomahawk::InfoSystem::InfoCustomData customData) { qDebug() << Q_FUNC_INFO; @@ -364,13 +364,13 @@ void XMPPBot::infoReturnedSlot(QString caller, Tomahawk::InfoSystem::InfoType ty { qDebug() << "Lyrics requested"; if (!output.canConvert() || - !input.canConvert() + !input.canConvert() ) { qDebug() << "Variants failed to be valid"; break; } - InfoCustomDataHash inHash = input.value(); + InfoCustomData inHash = input.value(); QString artist = inHash["artistName"].toString(); QString track = inHash["trackName"].toString(); QString lyrics = output.toString(); diff --git a/src/xmppbot/xmppbot.h b/src/xmppbot/xmppbot.h index e88704a44..ee5a2b38a 100644 --- a/src/xmppbot/xmppbot.h +++ b/src/xmppbot/xmppbot.h @@ -66,7 +66,7 @@ public: public slots: virtual void newTrackSlot(const Tomahawk::result_ptr &track); - virtual void infoReturnedSlot(QString caller, Tomahawk::InfoSystem::InfoType type, QVariant input, QVariant output, Tomahawk::InfoSystem::InfoCustomDataHash customData); + virtual void infoReturnedSlot(QString caller, Tomahawk::InfoSystem::InfoType type, QVariant input, QVariant output, Tomahawk::InfoSystem::InfoCustomData customData); virtual void infoFinishedSlot(QString caller); protected: From 680b204d11d7ad3e229f3eb2960f77f399661dfb Mon Sep 17 00:00:00 2001 From: Jeff Mitchell Date: Sun, 3 Apr 2011 05:44:32 -0400 Subject: [PATCH 241/329] Make caching work. Doesn't save/load to disk yet but it's ready to be used as a memcache at least. --- include/tomahawk/infosystem.h | 20 ++++++++++++++++++- src/infosystem/infoplugins/echonestplugin.cpp | 5 ----- src/infosystem/infoplugins/lastfmplugin.cpp | 4 ---- .../infoplugins/musixmatchplugin.cpp | 7 ------- src/infosystem/infosystem.cpp | 15 +------------- src/infosystem/infosystemcache.cpp | 13 +++++++++++- src/infosystem/infosystemcache.h | 3 ++- 7 files changed, 34 insertions(+), 33 deletions(-) diff --git a/include/tomahawk/infosystem.h b/include/tomahawk/infosystem.h index 2ab5d2da0..e0efec86b 100644 --- a/include/tomahawk/infosystem.h +++ b/include/tomahawk/infosystem.h @@ -19,6 +19,7 @@ #ifndef TOMAHAWK_INFOSYSTEM_H #define TOMAHAWK_INFOSYSTEM_H +#include #include #include #include @@ -150,7 +151,6 @@ signals: public slots: void infoSlot( QString target, Tomahawk::InfoSystem::InfoType type, QVariant input, QVariant output, Tomahawk::InfoSystem::InfoCustomData customData ); - void finishedSlot( QString target, Tomahawk::InfoSystem::InfoType type); private: QLinkedList< InfoPluginPtr > determineOrderedMatches( const InfoType type ) const; @@ -170,6 +170,24 @@ private: } +inline uint qHash( Tomahawk::InfoSystem::InfoCacheCriteria hash ) +{ + QCryptographicHash md5( QCryptographicHash::Md5 ); + foreach( QString key, hash.keys() ) + md5.addData( key.toUtf8() ); + foreach( QString value, hash.values() ) + md5.addData( value.toUtf8() ); + + QString hexData = md5.result(); + + uint returnval = 0; + + foreach( uint val, hexData.toUcs4() ) + returnval += val; + + return returnval; +} + Q_DECLARE_METATYPE( Tomahawk::InfoSystem::InfoGenericMap ); Q_DECLARE_METATYPE( Tomahawk::InfoSystem::InfoCustomData ); Q_DECLARE_METATYPE( Tomahawk::InfoSystem::InfoCacheCriteria ); diff --git a/src/infosystem/infoplugins/echonestplugin.cpp b/src/infosystem/infoplugins/echonestplugin.cpp index ec852f787..38f64ed4c 100644 --- a/src/infosystem/infoplugins/echonestplugin.cpp +++ b/src/infosystem/infoplugins/echonestplugin.cpp @@ -163,7 +163,6 @@ void EchoNestPlugin::getArtistBiographySlot() } emit info( m_callerMap[reply], Tomahawk::InfoSystem::InfoArtistBiography, reply->property( "data" ), QVariant::fromValue(biographyMap), m_replyMap[reply] ); - emit finished( m_callerMap[reply], Tomahawk::InfoSystem::InfoArtistBiography); m_replyMap.remove(reply); m_callerMap.remove(reply); reply->deleteLater(); @@ -175,7 +174,6 @@ void EchoNestPlugin::getArtistFamiliaritySlot() Echonest::Artist artist = artistFromReply( reply ); qreal familiarity = artist.familiarity(); emit info( m_callerMap[reply], Tomahawk::InfoSystem::InfoArtistFamiliarity, reply->property( "data" ), familiarity, m_replyMap[reply] ); - emit finished( m_callerMap[reply], Tomahawk::InfoSystem::InfoArtistFamiliarity); m_replyMap.remove(reply); m_callerMap.remove(reply); reply->deleteLater(); @@ -187,7 +185,6 @@ void EchoNestPlugin::getArtistHotttnesssSlot() Echonest::Artist artist = artistFromReply( reply ); qreal hotttnesss = artist.hotttnesss(); emit info( m_callerMap[reply], Tomahawk::InfoSystem::InfoArtistHotttness, reply->property( "data" ), hotttnesss, m_replyMap[reply] ); - emit finished( m_callerMap[reply], Tomahawk::InfoSystem::InfoArtistHotttness); m_replyMap.remove(reply); m_callerMap.remove(reply); reply->deleteLater(); @@ -206,7 +203,6 @@ void EchoNestPlugin::getArtistTermsSlot() termsMap[ term.name() ] = termMap; } emit info( m_callerMap[reply], Tomahawk::InfoSystem::InfoArtistTerms, reply->property( "data" ), QVariant::fromValue(termsMap), m_replyMap[reply] ); - emit finished( m_callerMap[reply], Tomahawk::InfoSystem::InfoArtistTerms); m_replyMap.remove(reply); m_callerMap.remove(reply); reply->deleteLater(); @@ -224,7 +220,6 @@ void EchoNestPlugin::getMiscTopSlot() termsMap[ term.name().toLower() ] = termMap; } emit info( m_callerMap[reply], Tomahawk::InfoSystem::InfoMiscTopTerms, QVariant(), QVariant::fromValue(termsMap), m_replyMap[reply] ); - emit finished( m_callerMap[reply], Tomahawk::InfoSystem::InfoMiscTopTerms); m_replyMap.remove(reply); m_callerMap.remove(reply); reply->deleteLater(); diff --git a/src/infosystem/infoplugins/lastfmplugin.cpp b/src/infosystem/infoplugins/lastfmplugin.cpp index 5848f6f68..652d87414 100644 --- a/src/infosystem/infoplugins/lastfmplugin.cpp +++ b/src/infosystem/infoplugins/lastfmplugin.cpp @@ -95,7 +95,6 @@ void LastFmPlugin::dataError( const QString &caller, const InfoType type, const QVariant& data, Tomahawk::InfoSystem::InfoCustomData &customData ) { emit info( caller, type, data, QVariant(), customData ); - emit finished( caller, type ); return; } @@ -139,7 +138,6 @@ LastFmPlugin::nowPlaying( const QString &caller, const InfoType type, const QVar m_scrobbler->nowPlaying( m_track ); emit info( caller, type, data, QVariant(), customData ); - emit finished( caller, type ); } void @@ -158,7 +156,6 @@ LastFmPlugin::scrobble( const QString &caller, const InfoType type, const QVaria m_scrobbler->submit(); emit info( caller, type, data, QVariant(), customData ); - emit finished( caller, type ); } void @@ -230,7 +227,6 @@ LastFmPlugin::coverArtReturned() returnedData, customData ); - emit finished( reply->property( "caller" ).toString(), (Tomahawk::InfoSystem::InfoType)(reply->property( "type" ).toUInt()) ); InfoCustomData origData = reply->property( "origData" ).value< Tomahawk::InfoSystem::InfoCustomData >(); Tomahawk::InfoSystem::InfoCacheCriteria criteria; diff --git a/src/infosystem/infoplugins/musixmatchplugin.cpp b/src/infosystem/infoplugins/musixmatchplugin.cpp index cc2cd576f..128085ca6 100644 --- a/src/infosystem/infoplugins/musixmatchplugin.cpp +++ b/src/infosystem/infoplugins/musixmatchplugin.cpp @@ -53,7 +53,6 @@ void MusixMatchPlugin::getInfo(const QString &caller, const InfoType type, const if( artist.isEmpty() || track.isEmpty() ) { emit info(caller, Tomahawk::InfoSystem::InfoTrackLyrics, data, QVariant(), customData); - emit finished(caller, Tomahawk::InfoSystem::InfoTrackLyrics); return; } qDebug() << "artist is " << artist << ", track is " << track; @@ -76,7 +75,6 @@ bool MusixMatchPlugin::isValidTrackData(const QString &caller, const QVariant& d if (data.isNull() || !data.isValid() || !data.canConvert()) { emit info(caller, Tomahawk::InfoSystem::InfoTrackLyrics, data, QVariant(), customData); - emit finished(caller, Tomahawk::InfoSystem::InfoTrackLyrics); qDebug() << "MusixMatchPlugin::isValidTrackData: Data null, invalid, or can't convert"; return false; } @@ -84,14 +82,12 @@ bool MusixMatchPlugin::isValidTrackData(const QString &caller, const QVariant& d if (hash["trackName"].toString().isEmpty() ) { emit info(caller, Tomahawk::InfoSystem::InfoTrackLyrics, data, QVariant(), customData); - emit finished(caller, Tomahawk::InfoSystem::InfoTrackLyrics); qDebug() << "MusixMatchPlugin::isValidTrackData: Track name is empty"; return false; } if (hash["artistName"].toString().isEmpty() ) { emit info(caller, Tomahawk::InfoSystem::InfoTrackLyrics, data, QVariant(), customData); - emit finished(caller, Tomahawk::InfoSystem::InfoTrackLyrics); qDebug() << "MusixMatchPlugin::isValidTrackData: No artist name found"; return false; } @@ -114,7 +110,6 @@ void MusixMatchPlugin::trackSearchSlot() if (domNodeList.isEmpty()) { emit info(oldReply->property("caller").toString(), Tomahawk::InfoSystem::InfoTrackLyrics, oldReply->property("origData"), QVariant(), oldReply->property("customData").value()); - emit finished(oldReply->property("caller").toString(), Tomahawk::InfoSystem::InfoTrackLyrics); return; } QString track_id = domNodeList.at(0).toElement().text(); @@ -144,11 +139,9 @@ void MusixMatchPlugin::trackLyricsSlot() if (domNodeList.isEmpty()) { emit info(reply->property("caller").toString(), Tomahawk::InfoSystem::InfoTrackLyrics, reply->property("origData"), QVariant(), reply->property("customData").value()); - emit finished(reply->property("caller").toString(), Tomahawk::InfoSystem::InfoTrackLyrics); return; } QString lyrics = domNodeList.at(0).toElement().text(); qDebug() << "Emitting lyrics: " << lyrics; emit info(reply->property("caller").toString(), Tomahawk::InfoSystem::InfoTrackLyrics, reply->property("origData"), QVariant(lyrics), reply->property("customData").value()); - emit finished(reply->property("caller").toString(), Tomahawk::InfoSystem::InfoTrackLyrics); } diff --git a/src/infosystem/infosystem.cpp b/src/infosystem/infosystem.cpp index d03b53f10..7fd6158c6 100644 --- a/src/infosystem/infosystem.cpp +++ b/src/infosystem/infosystem.cpp @@ -90,18 +90,9 @@ InfoSystem::InfoSystem(QObject *parent) SLOT( infoSlot( QString, Tomahawk::InfoSystem::InfoType, QVariant, QVariant, Tomahawk::InfoSystem::InfoCustomData ) ), Qt::UniqueConnection ); - - connect( - plugin.data(), - SIGNAL( finished( QString, Tomahawk::InfoSystem::InfoType ) ), - this, - SLOT( finishedSlot( QString, Tomahawk::InfoSystem::InfoType ) ), Qt::UniqueConnection - ); } connect( m_cache, SIGNAL( info( QString, Tomahawk::InfoSystem::InfoType, QVariant, QVariant, Tomahawk::InfoSystem::InfoCustomData ) ), this, SLOT( infoSlot( QString, Tomahawk::InfoSystem::InfoType, QVariant, QVariant, Tomahawk::InfoSystem::InfoCustomData ) ), Qt::UniqueConnection ); - connect( m_cache, SIGNAL( finished( QString, Tomahawk::InfoSystem::InfoType ) ), - this, SLOT( finishedSlot( QString, Tomahawk::InfoSystem::InfoType ) ), Qt::UniqueConnection ); } InfoSystem::~InfoSystem() @@ -191,11 +182,7 @@ void InfoSystem::infoSlot(QString target, InfoType type, QVariant input, QVarian return; } emit info(target, type, input, output, customData); -} - -void InfoSystem::finishedSlot(QString target, InfoType type) -{ - qDebug() << Q_FUNC_INFO; + m_dataTracker[target][type] = m_dataTracker[target][type] - 1; qDebug() << "current count in dataTracker is " << m_dataTracker[target][type]; Q_FOREACH(InfoType testtype, m_dataTracker[target].keys()) diff --git a/src/infosystem/infosystemcache.cpp b/src/infosystem/infosystemcache.cpp index c25fb10bb..8e4801540 100644 --- a/src/infosystem/infosystemcache.cpp +++ b/src/infosystem/infosystemcache.cpp @@ -24,11 +24,22 @@ void Tomahawk::InfoSystem::InfoSystemCache::getCachedInfoSlot( Tomahawk::InfoSystem::InfoCacheCriteria criteria, QString caller, Tomahawk::InfoSystem::InfoType type, QVariant input, Tomahawk::InfoSystem::InfoCustomData customData ) { qDebug() << Q_FUNC_INFO; - emit notInCache( criteria, caller, type, input, customData ); + if( !m_memCache.contains( type ) || !m_memCache[type].contains( criteria ) ) + { + emit notInCache( criteria, caller, type, input, customData ); + return; + } + + emit info( caller, type, input, m_memCache[type][criteria], customData ); } void Tomahawk::InfoSystem::InfoSystemCache::updateCacheSlot( Tomahawk::InfoSystem::InfoCacheCriteria criteria, Tomahawk::InfoSystem::InfoType type, QVariant output ) { qDebug() << Q_FUNC_INFO; + QHash< InfoCacheCriteria, QVariant > typecache; + if( m_memCache.contains( type ) ) + typecache = m_memCache[type]; + typecache[criteria] = output; + m_memCache[type] = typecache; } diff --git a/src/infosystem/infosystemcache.h b/src/infosystem/infosystemcache.h index 44476314a..ef31710c2 100644 --- a/src/infosystem/infosystemcache.h +++ b/src/infosystem/infosystemcache.h @@ -49,12 +49,13 @@ public: signals: void notInCache( Tomahawk::InfoSystem::InfoCacheCriteria criteria, QString caller, Tomahawk::InfoSystem::InfoType type, QVariant input, Tomahawk::InfoSystem::InfoCustomData customData ); void info( QString caller, Tomahawk::InfoSystem::InfoType type, QVariant input, QVariant output, Tomahawk::InfoSystem::InfoCustomData customData ); - void finished( QString, Tomahawk::InfoSystem::InfoType ); public slots: void getCachedInfoSlot( Tomahawk::InfoSystem::InfoCacheCriteria criteria, QString caller, Tomahawk::InfoSystem::InfoType type, QVariant input, Tomahawk::InfoSystem::InfoCustomData customData ); void updateCacheSlot( Tomahawk::InfoSystem::InfoCacheCriteria criteria, Tomahawk::InfoSystem::InfoType type, QVariant output ); +private: + QHash< InfoType, QHash< InfoCacheCriteria, QVariant > > m_memCache; }; } //namespace InfoSystem From 8729c91c95b922ac31b3f4257f9d16e67d76319c Mon Sep 17 00:00:00 2001 From: Christian Muehlhaeuser Date: Sun, 3 Apr 2011 13:07:20 +0200 Subject: [PATCH 242/329] * Fixed crash on startup with empty config. --- src/settingsdialog.cpp | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/src/settingsdialog.cpp b/src/settingsdialog.cpp index b8808b621..72bc4edbe 100644 --- a/src/settingsdialog.cpp +++ b/src/settingsdialog.cpp @@ -87,7 +87,8 @@ SettingsDialog::SettingsDialog( QWidget *parent ) // MUSIC SCANNER //FIXME: MULTIPLECOLLECTIONDIRS - ui->lineEditMusicPath->setText( s->scannerPaths().first() ); + if ( s->scannerPaths().count() ) + ui->lineEditMusicPath->setText( s->scannerPaths().first() ); ui->checkBoxWatchForChanges->setChecked( s->watchForChanges() ); // LAST FM From f66a062796b958811c8dd067df196a2253902a89 Mon Sep 17 00:00:00 2001 From: Jeff Mitchell Date: Sun, 3 Apr 2011 13:30:13 -0400 Subject: [PATCH 243/329] Hopefully fix removing files actually being removed from db during watch scan --- src/musicscanner.cpp | 6 ++---- 1 file changed, 2 insertions(+), 4 deletions(-) diff --git a/src/musicscanner.cpp b/src/musicscanner.cpp index dc0647c73..71707012e 100644 --- a/src/musicscanner.cpp +++ b/src/musicscanner.cpp @@ -75,9 +75,9 @@ DirLister::scanDir( QDir dir, int depth, DirLister::Mode mode ) { // dont scan this dir, unchanged since last time. } - else if( mode != DirLister::MTimeOnly ) + else { - if( m_dirmtimes.contains( dir.absolutePath() ) ) + if( m_dirmtimes.contains( dir.absolutePath() ) || !m_recursive ) Database::instance()->enqueue( QSharedPointer( new DatabaseCommand_DeleteFiles( dir, SourceList::instance()->getLocal() ) ) ); dir.setFilter( QDir::Files | QDir::Readable | QDir::NoDotAndDotDot ); @@ -97,8 +97,6 @@ DirLister::scanDir( QDir dir, int depth, DirLister::Mode mode ) qDebug() << "m_dirmtimes contains it? " << (m_dirmtimes.contains( di.absoluteFilePath() ) ? "true" : "false"); if( mode == DirLister::Recursive || !m_dirmtimes.contains( di.absoluteFilePath() ) ) scanDir( di.absoluteFilePath(), depth + 1, DirLister::Recursive ); - //else //should be the non-recursive case since the second test above should only happen with a new dir - // scanDir( di.absoluteFilePath(), depth + 1, DirLister::MTimeOnly ); } } From 74fd09235582103e0ae74dd988788b52b57efc49 Mon Sep 17 00:00:00 2001 From: Jeff Mitchell Date: Sun, 3 Apr 2011 13:50:52 -0400 Subject: [PATCH 244/329] Ok. Now the watched scanning is really fixed, in the sense that I can no longer break it. Until someone finds another way. --- src/musicscanner.cpp | 8 +++++++- 1 file changed, 7 insertions(+), 1 deletion(-) diff --git a/src/musicscanner.cpp b/src/musicscanner.cpp index 71707012e..b6c0ef617 100644 --- a/src/musicscanner.cpp +++ b/src/musicscanner.cpp @@ -33,7 +33,6 @@ void DirLister::go() { qDebug() << Q_FUNC_INFO; - qDebug() << "Current mtimes: " << m_dirmtimes; qDebug() << "Recursive? : " << (m_recursive ? "true" : "false"); if( !m_recursive ) { @@ -67,6 +66,13 @@ void DirLister::scanDir( QDir dir, int depth, DirLister::Mode mode ) { qDebug() << "DirLister::scanDir scanning: " << dir.absolutePath() << " with mode " << mode; + + if( !dir.exists() ) + { + qDebug() << "Dir no longer exists, not scanning"; + return; + } + QFileInfoList dirs; const uint mtime = QFileInfo( dir.absolutePath() ).lastModified().toUTC().toTime_t(); m_newdirmtimes.insert( dir.absolutePath(), mtime ); From 5e2d196ba00251efccc7e92a5952966825f94dbb Mon Sep 17 00:00:00 2001 From: Christian Muehlhaeuser Date: Mon, 4 Apr 2011 10:36:07 +0200 Subject: [PATCH 245/329] * Fixed mtimes issue with windows paths. --- src/libtomahawk/database/databasecommand_dirmtimes.cpp | 10 ++++------ src/libtomahawk/database/databasecommand_dirmtimes.h | 2 +- 2 files changed, 5 insertions(+), 7 deletions(-) diff --git a/src/libtomahawk/database/databasecommand_dirmtimes.cpp b/src/libtomahawk/database/databasecommand_dirmtimes.cpp index 5b6034086..210f5e7e2 100644 --- a/src/libtomahawk/database/databasecommand_dirmtimes.cpp +++ b/src/libtomahawk/database/databasecommand_dirmtimes.cpp @@ -36,11 +36,12 @@ DatabaseCommand_DirMtimes::exec( DatabaseImpl* dbi ) void DatabaseCommand_DirMtimes::execSelect( DatabaseImpl* dbi ) { - qDebug() << Q_FUNC_INFO << m_prefix << m_update; + QDir dir( m_prefix ); + qDebug() << Q_FUNC_INFO << dir.absolutePath() << m_update; QMap mtimes; TomahawkSqlQuery query = dbi->newquery(); - if( m_prefix.isEmpty() ) + if ( m_prefix.isEmpty() ) { query.exec( "SELECT name, mtime FROM dirs_scanned" ); } @@ -49,15 +50,12 @@ DatabaseCommand_DirMtimes::execSelect( DatabaseImpl* dbi ) query.prepare( QString( "SELECT name, mtime " "FROM dirs_scanned " "WHERE name LIKE :prefix" ) ); - query.bindValue( ":prefix", m_prefix + "%" ); - qDebug() << query.lastQuery(); + query.bindValue( ":prefix", dir.absolutePath() + "%" ); query.exec(); - qDebug() << query.lastQuery(); } while( query.next() ) { - qDebug() << query.value( 0 ).toString(); mtimes.insert( query.value( 0 ).toString(), query.value( 1 ).toUInt() ); } diff --git a/src/libtomahawk/database/databasecommand_dirmtimes.h b/src/libtomahawk/database/databasecommand_dirmtimes.h index f35a78437..8b7c1c95d 100644 --- a/src/libtomahawk/database/databasecommand_dirmtimes.h +++ b/src/libtomahawk/database/databasecommand_dirmtimes.h @@ -34,7 +34,7 @@ class DLLEXPORT DatabaseCommand_DirMtimes : public DatabaseCommand Q_OBJECT public: - explicit DatabaseCommand_DirMtimes( const QString& prefix = "", QObject* parent = 0 ) + explicit DatabaseCommand_DirMtimes( const QString& prefix = QString(), QObject* parent = 0 ) : DatabaseCommand( parent ), m_prefix( prefix ), m_update( false ) {} From 60b9d48d73e182d09be957180e1a16249859f503 Mon Sep 17 00:00:00 2001 From: Christian Muehlhaeuser Date: Mon, 4 Apr 2011 10:48:48 +0200 Subject: [PATCH 246/329] * Updated Changelog. --- ChangeLog | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/ChangeLog b/ChangeLog index 3bcf6476b..03ede73fc 100644 --- a/ChangeLog +++ b/ChangeLog @@ -1,4 +1,5 @@ Version 0.0.3: + * Fixed an issue which caused duplicate items when rescanning. * Fix crashes in Twitter authentication. * Properly honor the chosen port number if a static host and port are marked as preferred. @@ -6,7 +7,7 @@ Version 0.0.3: speeds up importing sources a lot. * Faster painting of playlists with lots of unresolved tracks. * The tomahawk:// protocol handler works on Windows now. - * Fixed launching Tomahawk from Installer with administrative permissions. + * Fixed launching Tomahawk from Windows installer with admin privileges. * Prefer local results when results' score is equal. Version 0.0.2: From b08f4f1daff686f1497c1537a3598ae146a41d85 Mon Sep 17 00:00:00 2001 From: Jeff Mitchell Date: Mon, 4 Apr 2011 05:18:22 -0400 Subject: [PATCH 247/329] Changelogify++ --- ChangeLog | 3 +++ 1 file changed, 3 insertions(+) diff --git a/ChangeLog b/ChangeLog index 03ede73fc..88ee61dd1 100644 --- a/ChangeLog +++ b/ChangeLog @@ -1,5 +1,8 @@ Version 0.0.3: * Fixed an issue which caused duplicate items when rescanning. + * Revert change introduced in 0.0.2 causing Twitter protocol to not try + to reconnect to a peer if it couldn't connect the first time the plugin + was connected. This caused confusing (and for most unwanted) behavior. * Fix crashes in Twitter authentication. * Properly honor the chosen port number if a static host and port are marked as preferred. From fdc2f5d3e9233439fc524f26cbe32bde3e0b7e23 Mon Sep 17 00:00:00 2001 From: Christian Muehlhaeuser Date: Mon, 4 Apr 2011 13:25:10 +0200 Subject: [PATCH 248/329] * Fixed thread afinity issue related to WebKit. --- src/resolvers/qtscriptresolver.cpp | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/src/resolvers/qtscriptresolver.cpp b/src/resolvers/qtscriptresolver.cpp index 4a23439e6..3d1773fe8 100644 --- a/src/resolvers/qtscriptresolver.cpp +++ b/src/resolvers/qtscriptresolver.cpp @@ -68,6 +68,13 @@ QtScriptResolver::~QtScriptResolver() void QtScriptResolver::resolve( const Tomahawk::query_ptr& query ) { + if ( QThread::currentThread() != thread() ) + { + qDebug() << "Reinvoking in correct thread:" << Q_FUNC_INFO; + QMetaObject::invokeMethod( this, "resolve", Qt::QueuedConnection, Q_ARG(Tomahawk::query_ptr, query) ); + return; + } + qDebug() << Q_FUNC_INFO << query->toString(); QString eval = QString( "resolve( '%1', '%2', '%3', '%4' );" ) .arg( query->id().replace( "'", "\\'" ) ) From 9cc74abcccee235e28caa0d28ec248e149ca5d2e Mon Sep 17 00:00:00 2001 From: Leo Franchi Date: Mon, 4 Apr 2011 14:21:47 -0400 Subject: [PATCH 249/329] Keep the dynamic playlist description up to date when it is changed --- src/libtomahawk/playlist/dynamic/widgets/DynamicWidget.cpp | 4 ++++ src/libtomahawk/playlist/dynamic/widgets/DynamicWidget.h | 3 +++ src/libtomahawk/playlist/playlistmanager.cpp | 7 +++++++ 3 files changed, 14 insertions(+) diff --git a/src/libtomahawk/playlist/dynamic/widgets/DynamicWidget.cpp b/src/libtomahawk/playlist/dynamic/widgets/DynamicWidget.cpp index 75fc46351..6f1f2302a 100644 --- a/src/libtomahawk/playlist/dynamic/widgets/DynamicWidget.cpp +++ b/src/libtomahawk/playlist/dynamic/widgets/DynamicWidget.cpp @@ -345,6 +345,8 @@ DynamicWidget::controlsChanged() return; m_playlist->createNewRevision(); m_seqRevLaunched++; + + emit descriptionChanged( m_playlist->generator()->sentenceSummary() ); } void @@ -356,6 +358,8 @@ DynamicWidget::controlChanged( const Tomahawk::dyncontrol_ptr& control ) m_seqRevLaunched++; showPreview(); + + emit descriptionChanged( m_playlist->generator()->sentenceSummary() ); } void diff --git a/src/libtomahawk/playlist/dynamic/widgets/DynamicWidget.h b/src/libtomahawk/playlist/dynamic/widgets/DynamicWidget.h index 441db1e13..1771fd07b 100644 --- a/src/libtomahawk/playlist/dynamic/widgets/DynamicWidget.h +++ b/src/libtomahawk/playlist/dynamic/widgets/DynamicWidget.h @@ -94,6 +94,9 @@ public slots: void playlistChanged( PlaylistInterface* ); void tracksAdded(); +signals: + void descriptionChanged( const QString& caption ); + private slots: void generate( int = -1 ); void tracksGenerated( const QList< Tomahawk::query_ptr>& queries ); diff --git a/src/libtomahawk/playlist/playlistmanager.cpp b/src/libtomahawk/playlist/playlistmanager.cpp index 31570a41f..2ba638c48 100644 --- a/src/libtomahawk/playlist/playlistmanager.cpp +++ b/src/libtomahawk/playlist/playlistmanager.cpp @@ -553,6 +553,13 @@ PlaylistManager::setPage( ViewPage* page, bool trackHistory ) if ( !AudioEngine::instance()->isPlaying() ) AudioEngine::instance()->setPlaylist( currentPlaylistInterface() ); + // UGH! + if( QObject* obj = dynamic_cast< QObject* >( currentPage() ) ) { +// qDebug() << SIGNAL( descriptionChanged( QString ) ) << QMetaObject::normalizedSignature( SIGNAL( descriptionChanged( QString ) ) ) << obj->metaObject()->indexOfSignal( QMetaObject::normalizedSignature( SIGNAL( descriptionChanged( QString ) ) ) ); +// if( obj->metaObject()->indexOfSignal( QMetaObject::normalizedSignature( SIGNAL( descriptionChanged( QString ) ) ) ) > -1 ) // if the signal exists (just to hide the qobject runtime warning...) + connect( obj, SIGNAL( descriptionChanged( QString ) ), m_infobar, SLOT( setDescription( QString ) ) ); + } + m_stack->setCurrentWidget( page->widget() ); updateView(); } From 806b3a3281b036a65d1150a61ed44d9e30bd08a0 Mon Sep 17 00:00:00 2001 From: Leo Franchi Date: Mon, 4 Apr 2011 18:43:51 -0400 Subject: [PATCH 250/329] change loading animation, show animation while resolving a playlist --- data/images/loading-animation.gif | Bin 7517 -> 2608 bytes .../dynamic/widgets/LoadingSpinner.cpp | 4 +-- src/libtomahawk/playlist/playlistmodel.cpp | 23 ++++++++++++++++-- src/libtomahawk/playlist/playlistmodel.h | 6 ++--- src/libtomahawk/query.cpp | 2 ++ src/libtomahawk/query.h | 3 +++ 6 files changed, 30 insertions(+), 8 deletions(-) diff --git a/data/images/loading-animation.gif b/data/images/loading-animation.gif index 0be11d9d2fdb4cb093f016fcf9f48432883adf00..7fc9059880268a3e4eaea926e4e36093eb31bde2 100644 GIT binary patch literal 2608 zcmdVcdr(tX9tZGC9yiG~=H_*Q-0%n(lu>!CRf^T^KrYX$KoDxfM!SSdLcpq#Mg`ff zAj(T@DUYJiGRUI>S}W8Cs1za?QM3|>7$g!vg0@T1s_j(jUN+e6?Cc-gotm*^e5QBG!*+;~^apIPV`1UC= zGfNELC5*$w$US2ED`IYrxP6!SdWLxNl&EbXj?@z;&k~b1;^JkZe}MSxJn`ZMalVT< z-b&n`CVu>pXlf&Bnh4-GTORX^qC?~3uwYr}DqnxHgZ&^60>#o*N}VcMv?o=k5h>G@ z8E@}UCi|vtSMLBko>Lf*eb>r`D=i=Z;CxWFHiFNjQ_0y$da+w?lP)7jeCo|0)kRz2 z>v7HdvwgO9Y%X55ezX5|^=h;?k&HfW|40b=p+KfQB@5ZJC>Gkm$dR*Phz*dO1gPdP z6ADSrWpKDkA?TW?qQqwc)w{Q5(j=Qy9Nun>$I;TuGLN0ZxS3&ER#z-b8?KcZFjyO* zJ$dtlHd0$^0@zyV-J77)~HI?0x zsto8vFTZD8_Ln&ERGN-@n;n{PeSOiHD1Gi;0Z&u(B)@KXZ~`i-+D~hY49qsS?<_3U zO9OC9=gESBXao-Q<8;rdDTUm3D6=CB(k~lz%S24d&ql!aMS&ZRLzf*-xGtj?J<{s^ zQ|>VglEs>Co@&qii)QOnHQ>RFg|=lVUh=RZn56&^MC2Bdlxzn|<8m@8DxBRVw4793 zYJELRvPr?==`c6fPZ1Y@q^>!OfgK1;#y{)EglI%kRHUV<-vWM;;ngVUc9`l@7*`#Jv)W@n zP=w=fR|t$p;0*={kHm`&5;iU!8Ul^xGsxH&pW1J6vW_`c%CDFO(}**<(=0@18E5hH zUEnNorA03P$CClQE6sxz)*&6bRav6TU%m4QrQ0PB#-L7psV25QT;G;Kp74;m2jMa) zq#r-z8o0c5CbkD$>auo`N7AZXgS>)Oj$hE$?-E}tT^jR{;_$m6Iy`Lm`BD+pYw1%& zcUtuFm~%rHa9NA|WY=l@bAVQV$}Rg%nM3=tu%XJu;A`;!KP}?et8%VS-7b&ZJjmJU ztz-N~I4DwpCGnqKVYvXFSYB< ze<~>P4&OdfpXWd?7rn9FrNKZ3k08l)&%e_<SPAKR8XC#5W#~!MbbB@(5#n};ct*TT^I0Lt>%ut$4`!{9kku>$1CD+*}I8X zqYf>Nt^p3BlMcNBL4}TM_8xe|YxHJH0QB>X8*_s<&UB|1s@_KfUYY32%qVOWa#kB! zekeZ(RddABBZ1$3??wOBURyuic5@$YA0;;tg6#WuWZNT2;_h5f@aLZ*TS38C)zv`d z%Up74LR{T+7_ZJAOmG__wfCBu#Ari>`j9dOxvEKU-5yR9$2O-y48p1WHrd_x@$Sj% zApYJYdy0ym7_=y&aCq9(K*m|qH{>zu*CT}iU{Qq+XQ_A38nsh4IeR>ZQ9h0Ue@NzL zki5Q*Y2LOiOqpPM7%SfA=@Xi8`T;9+)na;NEBS$~;*V)&(}5*dH#zfO^)p7C@N7C3 zx-G24TRADVREqwq(_h8@j?lk?JN}2A+U4?deC)D&RBa}`B)7eG|I~}wdEt)aw zgl5C=)QBc3eZV@DYS8pq`?th&8GF)eZ%E=+$bHvd0vby1x7=Q_wiO7(%S9yr*apxm za7_s3iZ9E}`*A??pqKB_x);X25_UbNMB7)|+Td#HA$seKBuk$~>sNZjD U@0!tb_KI6)uefy!|D%`dpR|;|761SM literal 7517 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 Sn2#Wui8Ns8nm>c-{{I4f&Zn6G diff --git a/src/libtomahawk/playlist/dynamic/widgets/LoadingSpinner.cpp b/src/libtomahawk/playlist/dynamic/widgets/LoadingSpinner.cpp index f3814c238..a532d51da 100644 --- a/src/libtomahawk/playlist/dynamic/widgets/LoadingSpinner.cpp +++ b/src/libtomahawk/playlist/dynamic/widgets/LoadingSpinner.cpp @@ -81,7 +81,7 @@ LoadingSpinner::hideFinished() QSize LoadingSpinner::sizeHint() const { - return QSize( 64, 64 ); + return QSize( 31, 31 ); } void @@ -99,7 +99,7 @@ LoadingSpinner::reposition() int x = ( parentWidget()->width() / 2 ) - ( width() / 2 ); int y = ( parentWidget()->height() / 2 ) - ( height() / 2 ); move( x, y ); - resize( 64, 64 ); + resize( 31, 31 ); } diff --git a/src/libtomahawk/playlist/playlistmodel.cpp b/src/libtomahawk/playlist/playlistmodel.cpp index 2ab23bf51..84f6e91c4 100644 --- a/src/libtomahawk/playlist/playlistmodel.cpp +++ b/src/libtomahawk/playlist/playlistmodel.cpp @@ -95,9 +95,9 @@ PlaylistModel::loadPlaylist( const Tomahawk::playlist_ptr& playlist, bool loadEn int c = rowCount( QModelIndex() ); qDebug() << "Starting loading" << playlist->title(); - emit loadingStarts(); emit beginInsertRows( QModelIndex(), c, c + entries.count() - 1 ); + m_waitingForResolved.clear(); foreach( const plentry_ptr& entry, entries ) { qDebug() << entry->query()->toString(); @@ -105,6 +105,11 @@ PlaylistModel::loadPlaylist( const Tomahawk::playlist_ptr& playlist, bool loadEn plitem->index = createIndex( m_rootItem->children.count() - 1, 0, plitem ); connect( plitem, SIGNAL( dataChanged() ), SLOT( onDataChanged() ) ); + + if( !entry->query()->resolvingFinished() ) { + m_waitingForResolved.append( entry->query().data() ); + connect( entry->query().data(), SIGNAL( resolvingFinished( bool ) ), this, SLOT( trackResolved( bool ) ) ); + } } emit endInsertRows(); @@ -112,7 +117,9 @@ PlaylistModel::loadPlaylist( const Tomahawk::playlist_ptr& playlist, bool loadEn else qDebug() << "Playlist seems empty:" << playlist->title(); - emit loadingFinished(); + if( !m_waitingForResolved.isEmpty() ) + emit loadingStarted(); + emit trackCountChanged( rowCount( QModelIndex() ) ); } @@ -203,6 +210,18 @@ PlaylistModel::insert( unsigned int row, const Tomahawk::query_ptr& query ) onTracksInserted( row, ql ); } +void +PlaylistModel::trackResolved( bool ) +{ + Tomahawk::Query* q = qobject_cast< Query* >( sender() ); + Q_ASSERT( q ); + + m_waitingForResolved.removeAll( q ); + + if( m_waitingForResolved.isEmpty() ) + emit loadingFinished(); +} + void PlaylistModel::onTracksAdded( const QList& tracks ) diff --git a/src/libtomahawk/playlist/playlistmodel.h b/src/libtomahawk/playlist/playlistmodel.h index bc743a18a..e5ce24ae4 100644 --- a/src/libtomahawk/playlist/playlistmodel.h +++ b/src/libtomahawk/playlist/playlistmodel.h @@ -70,10 +70,6 @@ signals: void shuffleModeChanged( bool enabled ); void itemSizeChanged( const QModelIndex& index ); - - void loadingStarts(); - void loadingFinished(); - private slots: void onDataChanged(); @@ -83,11 +79,13 @@ private slots: void onTracksAdded( const QList& tracks ); void onTracksInserted( unsigned int row, const QList& tracks ); + void trackResolved( bool ); private: QList playlistEntries() const; Tomahawk::playlist_ptr m_playlist; bool m_waitForUpdate; + QList< Tomahawk::Query* > m_waitingForResolved; }; #endif // PLAYLISTMODEL_H diff --git a/src/libtomahawk/query.cpp b/src/libtomahawk/query.cpp index 10cf98c81..f3f70357a 100644 --- a/src/libtomahawk/query.cpp +++ b/src/libtomahawk/query.cpp @@ -45,6 +45,7 @@ Query::get( const QString& artist, const QString& track, const QString& album, c Query::Query( const QString& artist, const QString& track, const QString& album, const QID& qid ) : m_solved( false ) , m_playable( false ) + , m_resolveFinished( false ) , m_qid( qid ) , m_artist( artist ) , m_album( album ) @@ -114,6 +115,7 @@ void Query::onResolvingFinished() { // qDebug() << Q_FUNC_INFO << "Finished resolving." << toString(); + m_resolveFinished = true; emit resolvingFinished( m_solved ); } diff --git a/src/libtomahawk/query.h b/src/libtomahawk/query.h index 9c84d68bf..84e057493 100644 --- a/src/libtomahawk/query.h +++ b/src/libtomahawk/query.h @@ -64,6 +64,8 @@ public: /// true when any result has been found (score may be less than 1.0) bool playable() const { return m_playable; } + bool resolvingFinished() const { return m_resolveFinished; } + unsigned int lastPipelineWeight() const { return m_lastpipelineweight; } void setLastPipelineWeight( unsigned int w ) { m_lastpipelineweight = w; } @@ -108,6 +110,7 @@ private: QList< Tomahawk::result_ptr > m_results; bool m_solved; bool m_playable; + bool m_resolveFinished; mutable QID m_qid; unsigned int m_lastpipelineweight; From 8881fab205c409233811cd4403d2300dc52a549e Mon Sep 17 00:00:00 2001 From: Leo Franchi Date: Mon, 4 Apr 2011 18:50:10 -0400 Subject: [PATCH 251/329] clean up after ourselves --- src/libtomahawk/playlist/playlistmodel.cpp | 1 + 1 file changed, 1 insertion(+) diff --git a/src/libtomahawk/playlist/playlistmodel.cpp b/src/libtomahawk/playlist/playlistmodel.cpp index 84f6e91c4..a676252c9 100644 --- a/src/libtomahawk/playlist/playlistmodel.cpp +++ b/src/libtomahawk/playlist/playlistmodel.cpp @@ -217,6 +217,7 @@ PlaylistModel::trackResolved( bool ) Q_ASSERT( q ); m_waitingForResolved.removeAll( q ); + disconnect( q, SIGNAL( resolvingFinished( bool ) ), this, SLOT( trackResolved( bool ) ) ); if( m_waitingForResolved.isEmpty() ) emit loadingFinished(); From f8452b4fdcf795d74a35aca936621b226f85e8b7 Mon Sep 17 00:00:00 2001 From: Leo Franchi Date: Tue, 5 Apr 2011 13:14:01 -0400 Subject: [PATCH 252/329] only connect if there is that signal, no more warnings --- src/libtomahawk/playlist/playlistmanager.cpp | 7 +++---- 1 file changed, 3 insertions(+), 4 deletions(-) diff --git a/src/libtomahawk/playlist/playlistmanager.cpp b/src/libtomahawk/playlist/playlistmanager.cpp index 2ba638c48..92f30e991 100644 --- a/src/libtomahawk/playlist/playlistmanager.cpp +++ b/src/libtomahawk/playlist/playlistmanager.cpp @@ -19,6 +19,7 @@ #include "playlistmanager.h" #include +#include #include "audio/audioengine.h" #include "utils/animatedsplitter.h" @@ -554,10 +555,8 @@ PlaylistManager::setPage( ViewPage* page, bool trackHistory ) AudioEngine::instance()->setPlaylist( currentPlaylistInterface() ); // UGH! - if( QObject* obj = dynamic_cast< QObject* >( currentPage() ) ) { -// qDebug() << SIGNAL( descriptionChanged( QString ) ) << QMetaObject::normalizedSignature( SIGNAL( descriptionChanged( QString ) ) ) << obj->metaObject()->indexOfSignal( QMetaObject::normalizedSignature( SIGNAL( descriptionChanged( QString ) ) ) ); -// if( obj->metaObject()->indexOfSignal( QMetaObject::normalizedSignature( SIGNAL( descriptionChanged( QString ) ) ) ) > -1 ) // if the signal exists (just to hide the qobject runtime warning...) - connect( obj, SIGNAL( descriptionChanged( QString ) ), m_infobar, SLOT( setDescription( QString ) ) ); + if( QObject* obj = dynamic_cast< QObject* >( currentPage() ) ) {if( obj->metaObject()->indexOfSignal( "descriptionChanged(QString)" ) > -1 ) // if the signal exists (just to hide the qobject runtime warning...) + connect( obj, SIGNAL( descriptionChanged( QString ) ), m_infobar, SLOT( setDescription( QString ) ) ); } m_stack->setCurrentWidget( page->widget() ); From f8619d4aa5b81cd22ee201458f70e1cc91fad72e Mon Sep 17 00:00:00 2001 From: Leo Franchi Date: Tue, 5 Apr 2011 13:21:33 -0400 Subject: [PATCH 253/329] hm, add a newline where it belongs --- src/libtomahawk/playlist/playlistmanager.cpp | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/src/libtomahawk/playlist/playlistmanager.cpp b/src/libtomahawk/playlist/playlistmanager.cpp index 92f30e991..c32848b13 100644 --- a/src/libtomahawk/playlist/playlistmanager.cpp +++ b/src/libtomahawk/playlist/playlistmanager.cpp @@ -555,8 +555,9 @@ PlaylistManager::setPage( ViewPage* page, bool trackHistory ) AudioEngine::instance()->setPlaylist( currentPlaylistInterface() ); // UGH! - if( QObject* obj = dynamic_cast< QObject* >( currentPage() ) ) {if( obj->metaObject()->indexOfSignal( "descriptionChanged(QString)" ) > -1 ) // if the signal exists (just to hide the qobject runtime warning...) - connect( obj, SIGNAL( descriptionChanged( QString ) ), m_infobar, SLOT( setDescription( QString ) ) ); + if( QObject* obj = dynamic_cast< QObject* >( currentPage() ) ) { + if( obj->metaObject()->indexOfSignal( "descriptionChanged(QString)" ) > -1 ) // if the signal exists (just to hide the qobject runtime warning...) + connect( obj, SIGNAL( descriptionChanged( QString ) ), m_infobar, SLOT( setDescription( QString ) ) ); } m_stack->setCurrentWidget( page->widget() ); From b87732e9c679a6c3d4dfbe23310ef982335876f3 Mon Sep 17 00:00:00 2001 From: Jeff Mitchell Date: Tue, 5 Apr 2011 13:49:17 -0400 Subject: [PATCH 254/329] Add kdevelop stuff to gitignore --- .gitignore | 2 ++ 1 file changed, 2 insertions(+) diff --git a/.gitignore b/.gitignore index 7a9999082..e3f800ccc 100644 --- a/.gitignore +++ b/.gitignore @@ -10,3 +10,5 @@ moc_* *~ /tomahawk thirdparty/qtweetlib/WARNING-twitter-api-keys +.kdev4 +tomahawk.kdev4 From baa039bbf6abc68d1284de09a1898223e9905806 Mon Sep 17 00:00:00 2001 From: Jeff Mitchell Date: Tue, 5 Apr 2011 20:18:37 -0400 Subject: [PATCH 255/329] Do some explicit string emptiness checking in Twitter --- src/sip/twitter/twitter.cpp | 11 ++++++++++- 1 file changed, 10 insertions(+), 1 deletion(-) diff --git a/src/sip/twitter/twitter.cpp b/src/sip/twitter/twitter.cpp index 7947042ae..af3f0dfdc 100644 --- a/src/sip/twitter/twitter.cpp +++ b/src/sip/twitter/twitter.cpp @@ -265,6 +265,13 @@ TwitterPlugin::connectTimerFired() foreach( QString screenName, peerlist ) { QHash< QString, QVariant > peerData = m_cachedPeers[screenName].toHash(); + + if ( QDateTime::currentDateTimeUtc().toMSecsSinceEpoch() - peerData["lastseen"].toLongLong() > 1209600000 ) // 2 weeks + { + qDebug() << "Aging peer " << screenName << " out of cache"; + m_cachedPeers.remove( screenName ); + continue; + } if ( !peerData.contains( "host" ) || !peerData.contains( "port" ) || !peerData.contains( "pkey" ) ) { @@ -568,6 +575,7 @@ TwitterPlugin::registerOffer( const QString &screenName, const QHash< QString, Q if ( peersChanged ) { + _peerData["lastseen"] = QString::number( QDateTime::currentDateTimeUtc().toMSecsSinceEpoch() ); m_cachedPeers[screenName] = QVariant::fromValue< QHash< QString, QVariant > >( _peerData ); TomahawkSettings::instance()->setTwitterCachedPeers( m_cachedPeers ); } @@ -595,7 +603,8 @@ void TwitterPlugin::makeConnection( const QString &screenName, const QHash< QString, QVariant > &peerData ) { qDebug() << Q_FUNC_INFO; - if ( !peerData.contains( "host" ) || !peerData.contains( "port" ) || !peerData.contains( "pkey" ) || !peerData.contains( "node" ) ) + if ( !peerData.contains( "host" ) || !peerData.contains( "port" ) || !peerData.contains( "pkey" ) || !peerData.contains( "node" ) || + peerData["host"].toString().isEmpty() || peerData["port"].toString().isEmpty() || peerData["pkey"].toString().isEmpty() || peerData["node"].toString().isEmpty() ) { qDebug() << "TwitterPlugin could not find host and/or port and/or pkey and/or node for peer " << screenName; return; From db8088b30fcc915dd6ab55f86e872bf21f81f38d Mon Sep 17 00:00:00 2001 From: Jeff Mitchell Date: Tue, 5 Apr 2011 20:20:49 -0400 Subject: [PATCH 256/329] Actually start writing cached info to disk --- include/tomahawk/infosystem.h | 2 +- src/infosystem/infosystemcache.cpp | 119 ++++++++++++++++++++++++++--- src/infosystem/infosystemcache.h | 19 +++-- src/tomahawkapp.cpp | 2 +- 4 files changed, 120 insertions(+), 22 deletions(-) diff --git a/include/tomahawk/infosystem.h b/include/tomahawk/infosystem.h index e0efec86b..1409dabf9 100644 --- a/include/tomahawk/infosystem.h +++ b/include/tomahawk/infosystem.h @@ -36,7 +36,7 @@ namespace InfoSystem { class InfoSystemCache; enum InfoType { - InfoTrackID, + InfoTrackID = 0, InfoTrackArtist, InfoTrackAlbum, InfoTrackGenre, diff --git a/src/infosystem/infosystemcache.cpp b/src/infosystem/infosystemcache.cpp index 8e4801540..3a86c5634 100644 --- a/src/infosystem/infosystemcache.cpp +++ b/src/infosystem/infosystemcache.cpp @@ -17,29 +17,128 @@ */ #include +#include +#include +#include #include "infosystemcache.h" -void -Tomahawk::InfoSystem::InfoSystemCache::getCachedInfoSlot( Tomahawk::InfoSystem::InfoCacheCriteria criteria, QString caller, Tomahawk::InfoSystem::InfoType type, QVariant input, Tomahawk::InfoSystem::InfoCustomData customData ) + +namespace Tomahawk +{ + +namespace InfoSystem +{ + + +InfoSystemCache::InfoSystemCache( QObject* parent ) + : QObject(parent) { qDebug() << Q_FUNC_INFO; - if( !m_memCache.contains( type ) || !m_memCache[type].contains( criteria ) ) + QString cacheBaseDir = QDesktopServices::storageLocation( QDesktopServices::CacheLocation ); + for( int i = 0; i <= InfoNoInfo; i++ ) + { + InfoType type = (InfoType)(i); + if( m_dirtySet.contains( type ) && m_dataCache.contains( type ) ) + { + QString cacheDir = cacheBaseDir + QString::number( i ); + QDir dir( cacheDir ); + if( dir.exists() && QFile::exists( QString( cacheDir + '/' + QString::number( i ) ) ) ) + loadCache( type, QString( cacheDir + '/' + QString::number( i ) ) ); + } + } +} + + +InfoSystemCache::~InfoSystemCache() +{ + qDebug() << Q_FUNC_INFO; + qDebug() << "Saving infosystemcache to disk"; + QString cacheBaseDir = QDesktopServices::storageLocation( QDesktopServices::CacheLocation ); + for( int i = 0; i <= InfoNoInfo; i++ ) + { + InfoType type = (InfoType)(i); + if( m_dirtySet.contains( type ) && m_dataCache.contains( type ) ) + { + QString cacheDir = cacheBaseDir + QString::number( i ); + saveCache( type, cacheDir ); + } + } +} + + +void +InfoSystemCache::getCachedInfoSlot( Tomahawk::InfoSystem::InfoCacheCriteria criteria, QString caller, Tomahawk::InfoSystem::InfoType type, QVariant input, Tomahawk::InfoSystem::InfoCustomData customData ) +{ + qDebug() << Q_FUNC_INFO; + if( !m_dataCache.contains( type ) || !m_dataCache[type].contains( criteria ) ) { emit notInCache( criteria, caller, type, input, customData ); return; } - emit info( caller, type, input, m_memCache[type][criteria], customData ); + emit info( caller, type, input, m_dataCache[type][criteria], customData ); } + void -Tomahawk::InfoSystem::InfoSystemCache::updateCacheSlot( Tomahawk::InfoSystem::InfoCacheCriteria criteria, Tomahawk::InfoSystem::InfoType type, QVariant output ) +InfoSystemCache::updateCacheSlot( Tomahawk::InfoSystem::InfoCacheCriteria criteria, Tomahawk::InfoSystem::InfoType type, QVariant output ) { qDebug() << Q_FUNC_INFO; - QHash< InfoCacheCriteria, QVariant > typecache; - if( m_memCache.contains( type ) ) - typecache = m_memCache[type]; - typecache[criteria] = output; - m_memCache[type] = typecache; + QHash< InfoCacheCriteria, QVariant > typedatacache; + QHash< InfoCacheCriteria, QDateTime > typetimecache; + typedatacache[criteria] = output; + typetimecache[criteria] = QDateTime::currentDateTimeUtc(); + m_dataCache[type] = typedatacache; + m_timeCache[type] = typetimecache; + m_dirtySet.insert( type ); } + + +void +InfoSystemCache::loadCache( InfoType type, const QString &cacheDir ) +{ + qDebug() << Q_FUNC_INFO; +} + + +void +InfoSystemCache::saveCache( InfoType type, const QString &cacheDir ) +{ + qDebug() << Q_FUNC_INFO; + QDir dir( cacheDir ); + if( !dir.exists( cacheDir ) ) + { + qDebug() << "Creating cache directory " << cacheDir; + if( !dir.mkpath( cacheDir ) ) + { + qDebug() << "Failed to create cache dir! Bailing..."; + return; + } + } + + QSettings cacheFile( QString( cacheDir + '/' + QString::number( (int)type ) ), QSettings::IniFormat ); + + foreach( InfoCacheCriteria criteria, m_dataCache[type].keys() ) + { + cacheFile.beginGroup( "type_" + QString::number( type ) ); + cacheFile.beginWriteArray( "criteria" ); + QStringList keys = criteria.keys(); + for( int i = 0; i < criteria.size(); i++ ) + { + cacheFile.setArrayIndex( i ); + cacheFile.setValue( keys.at( i ), criteria[keys.at( i )] ); + } + cacheFile.endArray(); + cacheFile.setValue( "data", m_dataCache[type][criteria] ); + cacheFile.setValue( "time", m_timeCache[type][criteria] ); + cacheFile.endGroup(); + } + + m_dirtySet.remove( type ); +} + + +} //namespace InfoSystem + +} //namespace Tomahawk \ No newline at end of file diff --git a/src/infosystem/infosystemcache.h b/src/infosystem/infosystemcache.h index ef31710c2..b74cf8a40 100644 --- a/src/infosystem/infosystemcache.h +++ b/src/infosystem/infosystemcache.h @@ -19,6 +19,7 @@ #ifndef TOMAHAWK_INFOSYSTEMCACHE_H #define TOMAHAWK_INFOSYSTEMCACHE_H +#include #include #include @@ -35,16 +36,9 @@ class InfoSystemCache : public QObject Q_OBJECT public: - InfoSystemCache( QObject *parent = 0 ) - : QObject( parent ) - { - qDebug() << Q_FUNC_INFO; - } + InfoSystemCache( QObject *parent = 0 ); - virtual ~InfoSystemCache() - { - qDebug() << Q_FUNC_INFO; - } + virtual ~InfoSystemCache(); signals: void notInCache( Tomahawk::InfoSystem::InfoCacheCriteria criteria, QString caller, Tomahawk::InfoSystem::InfoType type, QVariant input, Tomahawk::InfoSystem::InfoCustomData customData ); @@ -55,7 +49,12 @@ public slots: void updateCacheSlot( Tomahawk::InfoSystem::InfoCacheCriteria criteria, Tomahawk::InfoSystem::InfoType type, QVariant output ); private: - QHash< InfoType, QHash< InfoCacheCriteria, QVariant > > m_memCache; + void loadCache( InfoType type, const QString &cache ); + void saveCache( InfoType type, const QString &cache ); + + QHash< InfoType, QHash< InfoCacheCriteria, QVariant > > m_dataCache; + QHash< InfoType, QHash< InfoCacheCriteria, QDateTime > > m_timeCache; + QSet< InfoType > m_dirtySet; }; } //namespace InfoSystem diff --git a/src/tomahawkapp.cpp b/src/tomahawkapp.cpp index 116b9c7a1..38d22a4b7 100644 --- a/src/tomahawkapp.cpp +++ b/src/tomahawkapp.cpp @@ -299,7 +299,7 @@ TomahawkApp::~TomahawkApp() delete m_mainwindow; delete m_audioEngine; #endif - + delete m_infoSystem; delete m_database; } From a52ecfb5b897acbbeec226ee5e3dfb407d705f9a Mon Sep 17 00:00:00 2001 From: Jeff Mitchell Date: Tue, 5 Apr 2011 20:18:37 -0400 Subject: [PATCH 257/329] Do some explicit string emptiness checking in Twitter --- src/sip/twitter/twitter.cpp | 11 ++++++++++- 1 file changed, 10 insertions(+), 1 deletion(-) diff --git a/src/sip/twitter/twitter.cpp b/src/sip/twitter/twitter.cpp index 1a5f91163..cc83c5b7b 100644 --- a/src/sip/twitter/twitter.cpp +++ b/src/sip/twitter/twitter.cpp @@ -265,6 +265,13 @@ TwitterPlugin::connectTimerFired() foreach( QString screenName, peerlist ) { QHash< QString, QVariant > peerData = m_cachedPeers[screenName].toHash(); + + if ( QDateTime::currentDateTimeUtc().toMSecsSinceEpoch() - peerData["lastseen"].toLongLong() > 1209600000 ) // 2 weeks + { + qDebug() << "Aging peer " << screenName << " out of cache"; + m_cachedPeers.remove( screenName ); + continue; + } if ( !peerData.contains( "host" ) || !peerData.contains( "port" ) || !peerData.contains( "pkey" ) ) { @@ -568,6 +575,7 @@ TwitterPlugin::registerOffer( const QString &screenName, const QHash< QString, Q if ( peersChanged ) { + _peerData["lastseen"] = QString::number( QDateTime::currentDateTimeUtc().toMSecsSinceEpoch() ); m_cachedPeers[screenName] = QVariant::fromValue< QHash< QString, QVariant > >( _peerData ); TomahawkSettings::instance()->setTwitterCachedPeers( m_cachedPeers ); } @@ -595,7 +603,8 @@ void TwitterPlugin::makeConnection( const QString &screenName, const QHash< QString, QVariant > &peerData ) { qDebug() << Q_FUNC_INFO; - if ( !peerData.contains( "host" ) || !peerData.contains( "port" ) || !peerData.contains( "pkey" ) || !peerData.contains( "node" ) ) + if ( !peerData.contains( "host" ) || !peerData.contains( "port" ) || !peerData.contains( "pkey" ) || !peerData.contains( "node" ) || + peerData["host"].toString().isEmpty() || peerData["port"].toString().isEmpty() || peerData["pkey"].toString().isEmpty() || peerData["node"].toString().isEmpty() ) { qDebug() << "TwitterPlugin could not find host and/or port and/or pkey and/or node for peer " << screenName; return; From 394f3ee3835bcbb333558b841da5446e1a386c31 Mon Sep 17 00:00:00 2001 From: Jeff Mitchell Date: Tue, 5 Apr 2011 20:50:53 -0400 Subject: [PATCH 258/329] Keep track of offered dbid to a peer and check offer key to make sure it matches --- src/sip/twitter/twitter.cpp | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/src/sip/twitter/twitter.cpp b/src/sip/twitter/twitter.cpp index af3f0dfdc..cda2b2880 100644 --- a/src/sip/twitter/twitter.cpp +++ b/src/sip/twitter/twitter.cpp @@ -536,11 +536,14 @@ TwitterPlugin::registerOffer( const QString &screenName, const QHash< QString, Q _peerData.remove( "resend" ); } - if ( !_peerData.contains( "okey" ) ) + if ( !_peerData.contains( "okey" ) || + !_peerData.contains( "onod" ) || + ( _peerData.contains( "onod" ) && _peerData["onod"] != Database::instance()->dbid() ) ) { QString okey = QUuid::createUuid().toString().split( '-' ).last(); okey.chop( 1 ); _peerData["okey"] = QVariant::fromValue< QString >( okey ); + _peerData["onod"] = QVariant::fromValue< QString >( Database::instance()->dbid() ); peersChanged = true; needToAddToCache = true; needToSend = true; From 73d88dcf7beb64f4767f182d315cb03bbd0be49e Mon Sep 17 00:00:00 2001 From: Jeff Mitchell Date: Tue, 5 Apr 2011 20:50:53 -0400 Subject: [PATCH 259/329] Keep track of offered dbid to a peer and check offer key to make sure it matches --- src/sip/twitter/twitter.cpp | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/src/sip/twitter/twitter.cpp b/src/sip/twitter/twitter.cpp index cc83c5b7b..8e9065617 100644 --- a/src/sip/twitter/twitter.cpp +++ b/src/sip/twitter/twitter.cpp @@ -536,11 +536,14 @@ TwitterPlugin::registerOffer( const QString &screenName, const QHash< QString, Q _peerData.remove( "resend" ); } - if ( !_peerData.contains( "okey" ) ) + if ( !_peerData.contains( "okey" ) || + !_peerData.contains( "onod" ) || + ( _peerData.contains( "onod" ) && _peerData["onod"] != Database::instance()->dbid() ) ) { QString okey = QUuid::createUuid().toString().split( '-' ).last(); okey.chop( 1 ); _peerData["okey"] = QVariant::fromValue< QString >( okey ); + _peerData["onod"] = QVariant::fromValue< QString >( Database::instance()->dbid() ); peersChanged = true; needToAddToCache = true; needToSend = true; From 47fb4f3b508021b924a278ae7ca4fdec2f36a37f Mon Sep 17 00:00:00 2001 From: Jeff Mitchell Date: Tue, 5 Apr 2011 20:58:47 -0400 Subject: [PATCH 260/329] Update twitter connect caching --- src/sip/twitter/twitter.cpp | 10 ++++++++-- 1 file changed, 8 insertions(+), 2 deletions(-) diff --git a/src/sip/twitter/twitter.cpp b/src/sip/twitter/twitter.cpp index cda2b2880..8fa13ef61 100644 --- a/src/sip/twitter/twitter.cpp +++ b/src/sip/twitter/twitter.cpp @@ -266,7 +266,13 @@ TwitterPlugin::connectTimerFired() { QHash< QString, QVariant > peerData = m_cachedPeers[screenName].toHash(); - if ( QDateTime::currentDateTimeUtc().toMSecsSinceEpoch() - peerData["lastseen"].toLongLong() > 1209600000 ) // 2 weeks + if ( Servent::instance()->connectedToSession( peerData["node"] ) ) + { + peerData["lastseen"] = QDateTime::currentMSecsSinceEpoch(); + m_cachedPeers[screenName] = peerData; + } + + if ( QDateTime::currentMSecsSinceEpoch() - peerData["lastseen"].toLongLong() > 1209600000 ) // 2 weeks { qDebug() << "Aging peer " << screenName << " out of cache"; m_cachedPeers.remove( screenName ); @@ -578,7 +584,7 @@ TwitterPlugin::registerOffer( const QString &screenName, const QHash< QString, Q if ( peersChanged ) { - _peerData["lastseen"] = QString::number( QDateTime::currentDateTimeUtc().toMSecsSinceEpoch() ); + _peerData["lastseen"] = QString::number( QDateTime::currentMSecsSinceEpoch() ); m_cachedPeers[screenName] = QVariant::fromValue< QHash< QString, QVariant > >( _peerData ); TomahawkSettings::instance()->setTwitterCachedPeers( m_cachedPeers ); } From 1ef9b7c241b3c99e2a3de8bb633720d7fa2cf71d Mon Sep 17 00:00:00 2001 From: Jeff Mitchell Date: Tue, 5 Apr 2011 20:58:47 -0400 Subject: [PATCH 261/329] Update twitter connect caching --- src/sip/twitter/twitter.cpp | 10 ++++++++-- 1 file changed, 8 insertions(+), 2 deletions(-) diff --git a/src/sip/twitter/twitter.cpp b/src/sip/twitter/twitter.cpp index 8e9065617..af759cd25 100644 --- a/src/sip/twitter/twitter.cpp +++ b/src/sip/twitter/twitter.cpp @@ -266,7 +266,13 @@ TwitterPlugin::connectTimerFired() { QHash< QString, QVariant > peerData = m_cachedPeers[screenName].toHash(); - if ( QDateTime::currentDateTimeUtc().toMSecsSinceEpoch() - peerData["lastseen"].toLongLong() > 1209600000 ) // 2 weeks + if ( Servent::instance()->connectedToSession( peerData["node"] ) ) + { + peerData["lastseen"] = QDateTime::currentMSecsSinceEpoch(); + m_cachedPeers[screenName] = peerData; + } + + if ( QDateTime::currentMSecsSinceEpoch() - peerData["lastseen"].toLongLong() > 1209600000 ) // 2 weeks { qDebug() << "Aging peer " << screenName << " out of cache"; m_cachedPeers.remove( screenName ); @@ -578,7 +584,7 @@ TwitterPlugin::registerOffer( const QString &screenName, const QHash< QString, Q if ( peersChanged ) { - _peerData["lastseen"] = QString::number( QDateTime::currentDateTimeUtc().toMSecsSinceEpoch() ); + _peerData["lastseen"] = QString::number( QDateTime::currentMSecsSinceEpoch() ); m_cachedPeers[screenName] = QVariant::fromValue< QHash< QString, QVariant > >( _peerData ); TomahawkSettings::instance()->setTwitterCachedPeers( m_cachedPeers ); } From a1848f46bdc33d865b08c02a686c2da9ceab301b Mon Sep 17 00:00:00 2001 From: Leo Franchi Date: Tue, 5 Apr 2011 14:47:53 -0400 Subject: [PATCH 262/329] Set a query loaded from a source as already resolved --- src/libtomahawk/database/databasecommand_alltracks.cpp | 3 ++- src/libtomahawk/query.cpp | 1 - src/libtomahawk/query.h | 3 ++- 3 files changed, 4 insertions(+), 3 deletions(-) diff --git a/src/libtomahawk/database/databasecommand_alltracks.cpp b/src/libtomahawk/database/databasecommand_alltracks.cpp index 5259ca64c..07072db4b 100644 --- a/src/libtomahawk/database/databasecommand_alltracks.cpp +++ b/src/libtomahawk/database/databasecommand_alltracks.cpp @@ -138,7 +138,8 @@ DatabaseCommand_AllTracks::exec( DatabaseImpl* dbi ) QList results; results << result; qry->addResults( results ); - + qry->setResolveFinished( true ); + ql << qry; } diff --git a/src/libtomahawk/query.cpp b/src/libtomahawk/query.cpp index f3f70357a..f07025411 100644 --- a/src/libtomahawk/query.cpp +++ b/src/libtomahawk/query.cpp @@ -62,7 +62,6 @@ Query::Query( const QString& artist, const QString& track, const QString& album, void Query::addResults( const QList< Tomahawk::result_ptr >& newresults ) { - bool becameSolved = false; { QMutexLocker lock( &m_mutex ); m_results.append( newresults ); diff --git a/src/libtomahawk/query.h b/src/libtomahawk/query.h index 84e057493..ddc447729 100644 --- a/src/libtomahawk/query.h +++ b/src/libtomahawk/query.h @@ -74,7 +74,8 @@ public: void setTrack( const QString& track ) { m_track = track; } void setResultHint( const QString& resultHint ) { m_resultHint = resultHint; } void setDuration( int duration ) { m_duration = duration; } - + void setResolveFinished( bool resolved ) { m_resolveFinished = resolved; } + QVariant toVariant() const; QString toString() const; From 3d93a82d0887acc1bfbadb620fb6a9f16269bd72 Mon Sep 17 00:00:00 2001 From: Leo Franchi Date: Tue, 5 Apr 2011 21:04:09 -0400 Subject: [PATCH 263/329] go back to previous page in history when deleting a playlist --- src/libtomahawk/playlist.cpp | 2 + src/libtomahawk/playlist.h | 3 ++ .../playlist/dynamic/DynamicPlaylist.cpp | 2 + .../playlist/dynamic/DynamicPlaylist.h | 2 + .../dynamic/widgets/DynamicWidget.cpp | 2 + src/libtomahawk/playlist/playlistmanager.cpp | 42 ++++++++++++++----- src/libtomahawk/playlist/playlistmanager.h | 3 ++ src/libtomahawk/playlist/playlistmodel.cpp | 2 +- src/libtomahawk/viewpage.h | 6 +++ src/tomahawkapp.cpp | 2 + 10 files changed, 55 insertions(+), 11 deletions(-) diff --git a/src/libtomahawk/playlist.cpp b/src/libtomahawk/playlist.cpp index 1ca21e31e..9d35c0fe6 100644 --- a/src/libtomahawk/playlist.cpp +++ b/src/libtomahawk/playlist.cpp @@ -229,6 +229,8 @@ Playlist::reportDeleted( const Tomahawk::playlist_ptr& self ) qDebug() << Q_FUNC_INFO; Q_ASSERT( self.data() == this ); m_source->collection()->deletePlaylist( self ); + + emit deleted( self ); } diff --git a/src/libtomahawk/playlist.h b/src/libtomahawk/playlist.h index f973927cc..d0a4ee490 100644 --- a/src/libtomahawk/playlist.h +++ b/src/libtomahawk/playlist.h @@ -182,6 +182,9 @@ signals: /// renamed etc. void changed(); + /// was deleted, eh? + void deleted( const Tomahawk::playlist_ptr& pl ); + void repeatModeChanged( PlaylistInterface::RepeatMode mode ); void shuffleModeChanged( bool enabled ); diff --git a/src/libtomahawk/playlist/dynamic/DynamicPlaylist.cpp b/src/libtomahawk/playlist/dynamic/DynamicPlaylist.cpp index 6ab5eb30f..6186024ee 100644 --- a/src/libtomahawk/playlist/dynamic/DynamicPlaylist.cpp +++ b/src/libtomahawk/playlist/dynamic/DynamicPlaylist.cpp @@ -265,6 +265,8 @@ DynamicPlaylist::reportDeleted( const Tomahawk::dynplaylist_ptr& self ) Q_ASSERT( self.data() == this ); // will emit Collection::playlistDeleted(...) author()->collection()->deleteDynamicPlaylist( self ); + + emit deleted( self ); } void DynamicPlaylist::addEntries(const QList< query_ptr >& queries, const QString& oldrev) diff --git a/src/libtomahawk/playlist/dynamic/DynamicPlaylist.h b/src/libtomahawk/playlist/dynamic/DynamicPlaylist.h index 864d6a9f4..bee37900a 100644 --- a/src/libtomahawk/playlist/dynamic/DynamicPlaylist.h +++ b/src/libtomahawk/playlist/dynamic/DynamicPlaylist.h @@ -113,6 +113,8 @@ signals: /// emitted when the playlist revision changes (whenever the playlist changes) void dynamicRevisionLoaded( Tomahawk::DynamicPlaylistRevision ); + void deleted( const Tomahawk::dynplaylist_ptr& pl ); + public slots: // want to update the playlist from the model? // generate a newrev using uuid() and call this: diff --git a/src/libtomahawk/playlist/dynamic/widgets/DynamicWidget.cpp b/src/libtomahawk/playlist/dynamic/widgets/DynamicWidget.cpp index 6f1f2302a..d63fe718f 100644 --- a/src/libtomahawk/playlist/dynamic/widgets/DynamicWidget.cpp +++ b/src/libtomahawk/playlist/dynamic/widgets/DynamicWidget.cpp @@ -137,6 +137,7 @@ DynamicWidget::loadDynamicPlaylist( const Tomahawk::dynplaylist_ptr& playlist ) disconnect( m_playlist->generator().data(), SIGNAL( generated( QList ) ), this, SLOT( tracksGenerated( QList ) ) ); disconnect( m_playlist.data(), SIGNAL( dynamicRevisionLoaded( Tomahawk::DynamicPlaylistRevision) ), this, SLOT(onRevisionLoaded( Tomahawk::DynamicPlaylistRevision) ) ); disconnect( m_playlist->generator().data(), SIGNAL( error( QString, QString ) ), this, SLOT( generatorError( QString, QString ) ) ); + disconnect( m_playlist.data(), SIGNAL( deleted( Tomahawk::dynplaylist_ptr ) ), this, SLOT( deleteLater() ) ); } @@ -163,6 +164,7 @@ DynamicWidget::loadDynamicPlaylist( const Tomahawk::dynplaylist_ptr& playlist ) connect( m_playlist->generator().data(), SIGNAL( generated( QList ) ), this, SLOT( tracksGenerated( QList ) ) ); connect( m_playlist.data(), SIGNAL( dynamicRevisionLoaded( Tomahawk::DynamicPlaylistRevision ) ), this, SLOT( onRevisionLoaded( Tomahawk::DynamicPlaylistRevision ) ) ); connect( m_playlist->generator().data(), SIGNAL( error( QString, QString ) ), this, SLOT( generatorError( QString, QString ) ) ); + connect( m_playlist.data(), SIGNAL( deleted( Tomahawk::dynplaylist_ptr ) ), this, SLOT( deleteLater() ) ); } diff --git a/src/libtomahawk/playlist/playlistmanager.cpp b/src/libtomahawk/playlist/playlistmanager.cpp index c32848b13..67c82afa4 100644 --- a/src/libtomahawk/playlist/playlistmanager.cpp +++ b/src/libtomahawk/playlist/playlistmanager.cpp @@ -163,6 +163,8 @@ PlaylistManager::show( const Tomahawk::playlist_ptr& playlist ) playlist->resolve(); m_playlistViews.insert( playlist, view ); + + connect( playlist.data(), SIGNAL( deleted( Tomahawk::playlist_ptr ) ), this, SLOT( onPlaylistDeleted( Tomahawk::playlist_ptr ) ) ); } else { @@ -184,6 +186,8 @@ PlaylistManager::show( const Tomahawk::dynplaylist_ptr& playlist ) { m_dynamicWidgets[ playlist ] = new Tomahawk::DynamicWidget( playlist, m_stack ); + connect( playlist.data(), SIGNAL( deleted( Tomahawk::dynplaylist_ptr ) ), this, SLOT( onDynamicDeleted( Tomahawk::dynplaylist_ptr ) ) ); + playlist->resolve(); } @@ -335,11 +339,6 @@ PlaylistManager::show( const Tomahawk::source_ptr& source ) bool PlaylistManager::show( ViewPage* page ) { - if ( m_stack->indexOf( page->widget() ) < 0 ) - { - connect( page->widget(), SIGNAL( destroyed( QWidget* ) ), SLOT( onWidgetDestroyed( QWidget* ) ) ); - } - setPage( page ); return true; @@ -490,6 +489,7 @@ PlaylistManager::showHistory( int historyPosition ) setHistoryPosition( historyPosition ); ViewPage* page = m_pageHistory.at( historyPosition ); + qDebug() << "Showing page after a deleting:" << page->widget()->metaObject()->className(); setPage( page, false ); } @@ -556,10 +556,14 @@ PlaylistManager::setPage( ViewPage* page, bool trackHistory ) // UGH! if( QObject* obj = dynamic_cast< QObject* >( currentPage() ) ) { - if( obj->metaObject()->indexOfSignal( "descriptionChanged(QString)" ) > -1 ) // if the signal exists (just to hide the qobject runtime warning...) + // if the signal exists (just to hide the qobject runtime warning...) + if( obj->metaObject()->indexOfSignal( "descriptionChanged(QString)" ) > -1 ) connect( obj, SIGNAL( descriptionChanged( QString ) ), m_infobar, SLOT( setDescription( QString ) ) ); + + if( obj->metaObject()->indexOfSignal( "deleted()" ) > -1 ) + connect( obj, SIGNAL( deleted() ), this, SLOT( pageDeleted() ) ); } - + m_stack->setCurrentWidget( page->widget() ); updateView(); } @@ -637,11 +641,29 @@ PlaylistManager::updateView() m_infobar->setPixmap( currentPage()->pixmap() ); } +void +PlaylistManager::onDynamicDeleted( const Tomahawk::dynplaylist_ptr& pl ) +{ + QWidget* w = m_dynamicWidgets.value( pl ); + m_dynamicWidgets.remove( pl ); + + onWidgetDestroyed( w ); +} + +void +PlaylistManager::onPlaylistDeleted( const Tomahawk::playlist_ptr& pl ) +{ + QWidget* w = m_playlistViews.value( pl ); + m_playlistViews.remove( pl ); + + onWidgetDestroyed( w ); +} + void PlaylistManager::onWidgetDestroyed( QWidget* widget ) -{ - qDebug() << "Destroyed child:" << widget; +{ + qDebug() << "Destroyed child:" << widget << widget->metaObject()->className(); bool resetWidget = ( m_stack->currentWidget() == widget ); m_stack->removeWidget( widget ); @@ -657,7 +679,7 @@ PlaylistManager::onWidgetDestroyed( QWidget* widget ) break; } } - + if ( resetWidget ) { if ( m_pageHistory.count() ) diff --git a/src/libtomahawk/playlist/playlistmanager.h b/src/libtomahawk/playlist/playlistmanager.h index 278037e98..fc86acf66 100644 --- a/src/libtomahawk/playlist/playlistmanager.h +++ b/src/libtomahawk/playlist/playlistmanager.h @@ -135,6 +135,9 @@ public slots: private slots: void setFilter( const QString& filter ); void applyFilter(); + + void onPlaylistDeleted( const Tomahawk::playlist_ptr& pl ); + void onDynamicDeleted( const Tomahawk::dynplaylist_ptr& pl ); void onWidgetDestroyed( QWidget* widget ); private: diff --git a/src/libtomahawk/playlist/playlistmodel.cpp b/src/libtomahawk/playlist/playlistmodel.cpp index a676252c9..cdcad71aa 100644 --- a/src/libtomahawk/playlist/playlistmodel.cpp +++ b/src/libtomahawk/playlist/playlistmodel.cpp @@ -106,7 +106,7 @@ PlaylistModel::loadPlaylist( const Tomahawk::playlist_ptr& playlist, bool loadEn connect( plitem, SIGNAL( dataChanged() ), SLOT( onDataChanged() ) ); - if( !entry->query()->resolvingFinished() ) { + if( !entry->query()->resolvingFinished() && entry->query()->playable() ) { m_waitingForResolved.append( entry->query().data() ); connect( entry->query().data(), SIGNAL( resolvingFinished( bool ) ), this, SLOT( trackResolved( bool ) ) ); } diff --git a/src/libtomahawk/viewpage.h b/src/libtomahawk/viewpage.h index b59695e7a..d775f451d 100644 --- a/src/libtomahawk/viewpage.h +++ b/src/libtomahawk/viewpage.h @@ -48,6 +48,12 @@ public: virtual bool jumpToCurrentTrack() = 0; + /** subclasses implementing ViewPage can emit the following signals: + * descriptionChanged( const QString& ) + * deleted() + * + * See DynamicWidget for an example + */ private: }; diff --git a/src/tomahawkapp.cpp b/src/tomahawkapp.cpp index 8af6a0457..e4198383f 100644 --- a/src/tomahawkapp.cpp +++ b/src/tomahawkapp.cpp @@ -342,6 +342,8 @@ TomahawkApp::registerMetaTypes() qRegisterMetaType< Tomahawk::query_ptr >("Tomahawk::query_ptr"); qRegisterMetaType< Tomahawk::source_ptr >("Tomahawk::source_ptr"); qRegisterMetaType< Tomahawk::dyncontrol_ptr >("Tomahawk::dyncontrol_ptr"); + qRegisterMetaType< Tomahawk::playlist_ptr >("Tomahawk::playlist_ptr"); + qRegisterMetaType< Tomahawk::dynplaylist_ptr >("Tomahawk::dynplaylist_ptr"); qRegisterMetaType< Tomahawk::geninterface_ptr >("Tomahawk::geninterface_ptr"); qRegisterMetaType< QList >("QList"); qRegisterMetaType< QList >("QList"); From 69011e3f90cd8a08ae122eb452a72a5ec7f3b419 Mon Sep 17 00:00:00 2001 From: Leo Franchi Date: Tue, 5 Apr 2011 21:06:24 -0400 Subject: [PATCH 264/329] cleanup --- src/libtomahawk/playlist/playlistmanager.cpp | 49 +++++++++----------- 1 file changed, 23 insertions(+), 26 deletions(-) diff --git a/src/libtomahawk/playlist/playlistmanager.cpp b/src/libtomahawk/playlist/playlistmanager.cpp index 67c82afa4..eac12794f 100644 --- a/src/libtomahawk/playlist/playlistmanager.cpp +++ b/src/libtomahawk/playlist/playlistmanager.cpp @@ -1,5 +1,5 @@ /* === This file is part of Tomahawk Player - === - * + * * Copyright 2010-2011, Christian Muehlhaeuser * * Tomahawk is free software: you can redistribute it and/or modify @@ -163,14 +163,14 @@ PlaylistManager::show( const Tomahawk::playlist_ptr& playlist ) playlist->resolve(); m_playlistViews.insert( playlist, view ); - + connect( playlist.data(), SIGNAL( deleted( Tomahawk::playlist_ptr ) ), this, SLOT( onPlaylistDeleted( Tomahawk::playlist_ptr ) ) ); } else { view = m_playlistViews.value( playlist ); } - + setPage( view ); TomahawkSettings::instance()->appendRecentlyPlayedPlaylist( playlist ); emit numSourcesChanged( SourceList::instance()->count() ); @@ -179,7 +179,7 @@ PlaylistManager::show( const Tomahawk::playlist_ptr& playlist ) } -bool +bool PlaylistManager::show( const Tomahawk::dynplaylist_ptr& playlist ) { if ( !m_dynamicWidgets.contains( playlist ) ) @@ -187,17 +187,17 @@ PlaylistManager::show( const Tomahawk::dynplaylist_ptr& playlist ) m_dynamicWidgets[ playlist ] = new Tomahawk::DynamicWidget( playlist, m_stack ); connect( playlist.data(), SIGNAL( deleted( Tomahawk::dynplaylist_ptr ) ), this, SLOT( onDynamicDeleted( Tomahawk::dynplaylist_ptr ) ) ); - + playlist->resolve(); } - + setPage( m_dynamicWidgets.value( playlist ) ); if ( playlist->mode() == Tomahawk::OnDemand ) m_queueView->hide(); else m_queueView->show(); - + TomahawkSettings::instance()->appendRecentlyPlayedPlaylist( playlist ); emit numSourcesChanged( SourceList::instance()->count() ); @@ -225,7 +225,7 @@ PlaylistManager::show( const Tomahawk::artist_ptr& artist ) { view = m_artistViews.value( artist ); } - + setPage( view ); emit numSourcesChanged( 1 ); @@ -252,7 +252,7 @@ PlaylistManager::show( const Tomahawk::album_ptr& album ) { view = m_albumViews.value( album ); } - + setPage( view ); emit numSourcesChanged( 1 ); @@ -359,10 +359,10 @@ PlaylistManager::showSuperCollection() } } m_superCollectionFlatModel->addCollections( toAdd ); - + m_superCollectionFlatModel->setTitle( tr( "All available tracks" ) ); m_superAlbumModel->setTitle( tr( "All available albums" ) ); - + if ( m_currentMode == 0 ) { setPage( m_superCollectionView ); @@ -472,7 +472,7 @@ PlaylistManager::historyForward() { if ( m_historyPosition >= m_pageHistory.count() - 1 ) return; - + showHistory( m_historyPosition + 1 ); } @@ -557,13 +557,10 @@ PlaylistManager::setPage( ViewPage* page, bool trackHistory ) // UGH! if( QObject* obj = dynamic_cast< QObject* >( currentPage() ) ) { // if the signal exists (just to hide the qobject runtime warning...) - if( obj->metaObject()->indexOfSignal( "descriptionChanged(QString)" ) > -1 ) + if( obj->metaObject()->indexOfSignal( "descriptionChanged(QString)" ) > -1 ) connect( obj, SIGNAL( descriptionChanged( QString ) ), m_infobar, SLOT( setDescription( QString ) ) ); - - if( obj->metaObject()->indexOfSignal( "deleted()" ) > -1 ) - connect( obj, SIGNAL( deleted() ), this, SLOT( pageDeleted() ) ); } - + m_stack->setCurrentWidget( page->widget() ); updateView(); } @@ -623,7 +620,7 @@ PlaylistManager::updateView() emit modeChanged( currentPlaylistInterface()->viewMode() ); } - if ( currentPage()->queueVisible() ) + if ( currentPage()->queueVisible() ) m_queueView->show(); else m_queueView->hide(); @@ -641,28 +638,28 @@ PlaylistManager::updateView() m_infobar->setPixmap( currentPage()->pixmap() ); } -void +void PlaylistManager::onDynamicDeleted( const Tomahawk::dynplaylist_ptr& pl ) { QWidget* w = m_dynamicWidgets.value( pl ); m_dynamicWidgets.remove( pl ); - + onWidgetDestroyed( w ); } -void +void PlaylistManager::onPlaylistDeleted( const Tomahawk::playlist_ptr& pl ) { QWidget* w = m_playlistViews.value( pl ); m_playlistViews.remove( pl ); - + onWidgetDestroyed( w ); } void PlaylistManager::onWidgetDestroyed( QWidget* widget ) -{ +{ qDebug() << "Destroyed child:" << widget << widget->metaObject()->className(); bool resetWidget = ( m_stack->currentWidget() == widget ); @@ -679,7 +676,7 @@ PlaylistManager::onWidgetDestroyed( QWidget* widget ) break; } } - + if ( resetWidget ) { if ( m_pageHistory.count() ) @@ -704,7 +701,7 @@ PlaylistManager::setShuffled( bool enabled ) } -void +void PlaylistManager::createPlaylist( const Tomahawk::source_ptr& src, const QVariant& contents ) { @@ -714,7 +711,7 @@ PlaylistManager::createPlaylist( const Tomahawk::source_ptr& src, } -void +void PlaylistManager::createDynamicPlaylist( const Tomahawk::source_ptr& src, const QVariant& contents ) { From 17733fea1dd5472262a355bf44b90e5d3415eadd Mon Sep 17 00:00:00 2001 From: Jeff Mitchell Date: Tue, 5 Apr 2011 21:14:32 -0400 Subject: [PATCH 265/329] Fix build --- src/sip/twitter/twitter.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/sip/twitter/twitter.cpp b/src/sip/twitter/twitter.cpp index 8fa13ef61..d1ef42b33 100644 --- a/src/sip/twitter/twitter.cpp +++ b/src/sip/twitter/twitter.cpp @@ -266,7 +266,7 @@ TwitterPlugin::connectTimerFired() { QHash< QString, QVariant > peerData = m_cachedPeers[screenName].toHash(); - if ( Servent::instance()->connectedToSession( peerData["node"] ) ) + if ( Servent::instance()->connectedToSession( peerData["node"].toString() ) ) { peerData["lastseen"] = QDateTime::currentMSecsSinceEpoch(); m_cachedPeers[screenName] = peerData; From dc43452bfc77828af0daa244cadaca55401538e6 Mon Sep 17 00:00:00 2001 From: Jeff Mitchell Date: Tue, 5 Apr 2011 21:14:32 -0400 Subject: [PATCH 266/329] Fix build --- src/sip/twitter/twitter.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/sip/twitter/twitter.cpp b/src/sip/twitter/twitter.cpp index af759cd25..aa80d9dca 100644 --- a/src/sip/twitter/twitter.cpp +++ b/src/sip/twitter/twitter.cpp @@ -266,7 +266,7 @@ TwitterPlugin::connectTimerFired() { QHash< QString, QVariant > peerData = m_cachedPeers[screenName].toHash(); - if ( Servent::instance()->connectedToSession( peerData["node"] ) ) + if ( Servent::instance()->connectedToSession( peerData["node"].toString() ) ) { peerData["lastseen"] = QDateTime::currentMSecsSinceEpoch(); m_cachedPeers[screenName] = peerData; From ab2443792ac24d39be185d93fe34162fe2aa1edc Mon Sep 17 00:00:00 2001 From: Christian Muehlhaeuser Date: Wed, 6 Apr 2011 06:18:00 +0200 Subject: [PATCH 267/329] * Don't manually delete the root item. This speeds up shut down, since the proxy will be disconnected before the root item gets deleted (automatically). --- src/libtomahawk/playlist/trackmodel.cpp | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/libtomahawk/playlist/trackmodel.cpp b/src/libtomahawk/playlist/trackmodel.cpp index f96d3b026..a8dacfd26 100644 --- a/src/libtomahawk/playlist/trackmodel.cpp +++ b/src/libtomahawk/playlist/trackmodel.cpp @@ -1,5 +1,5 @@ /* === This file is part of Tomahawk Player - === - * + * * Copyright 2010-2011, Christian Muehlhaeuser * * Tomahawk is free software: you can redistribute it and/or modify @@ -45,7 +45,7 @@ TrackModel::TrackModel( QObject* parent ) TrackModel::~TrackModel() { - delete m_rootItem; +// delete m_rootItem; } From 51a3eb39be5f4a8d5b8a76c23c592a7226ae944f Mon Sep 17 00:00:00 2001 From: Christian Muehlhaeuser Date: Wed, 6 Apr 2011 07:26:57 +0200 Subject: [PATCH 268/329] * Fixed page-history related crash when deleting / creating playlists. --- .../dynamic/widgets/DynamicWidget.cpp | 145 +++++++++--------- .../playlist/dynamic/widgets/DynamicWidget.h | 46 +++--- src/libtomahawk/playlist/playlistmanager.cpp | 51 +++--- src/libtomahawk/playlist/playlistmanager.h | 22 ++- src/libtomahawk/playlist/playlistmodel.cpp | 18 ++- src/libtomahawk/playlist/playlistmodel.h | 6 +- src/libtomahawk/playlist/playlistview.cpp | 12 +- src/libtomahawk/playlist/playlistview.h | 7 +- src/libtomahawk/viewpage.h | 12 +- src/libtomahawk/widgets/newplaylistwidget.h | 4 +- 10 files changed, 176 insertions(+), 147 deletions(-) diff --git a/src/libtomahawk/playlist/dynamic/widgets/DynamicWidget.cpp b/src/libtomahawk/playlist/dynamic/widgets/DynamicWidget.cpp index d63fe718f..bd76b5683 100644 --- a/src/libtomahawk/playlist/dynamic/widgets/DynamicWidget.cpp +++ b/src/libtomahawk/playlist/dynamic/widgets/DynamicWidget.cpp @@ -1,5 +1,5 @@ /* === This file is part of Tomahawk Player - === - * + * * Copyright 2010-2011, Christian Muehlhaeuser * * Tomahawk is free software: you can redistribute it and/or modify @@ -58,30 +58,30 @@ DynamicWidget::DynamicWidget( const Tomahawk::dynplaylist_ptr& playlist, QWidget , m_controls( 0 ) , m_view( 0 ) , m_model() -{ +{ m_controls = new CollapsibleControls( this ); m_layout->addWidget( m_controls ); setContentsMargins( 0, 0, 0, 1 ); // to align the bottom with the bottom of the sourcelist - + m_model = new DynamicModel( this ); m_view = new DynamicView( this ); m_view->setModel( m_model ); m_view->setContentsMargins( 0, 0, 0, 0 ); m_layout->addWidget( m_view, 1 ); - + connect( m_model, SIGNAL( collapseFromTo( int, int ) ), m_view, SLOT( collapseEntries( int, int ) ) ); - connect( m_model, SIGNAL( trackGenerationFailure( QString ) ), this, SLOT( stationFailed( QString ) ) ); - - m_loading = new LoadingSpinner( m_view ); + connect( m_model, SIGNAL( trackGenerationFailure( QString ) ), this, SLOT( stationFailed( QString ) ) ); + + m_loading = new LoadingSpinner( m_view ); connect( m_model, SIGNAL( tracksAdded() ), m_loading, SLOT( fadeOut() ) ); - + m_setup = new DynamicSetupWidget( playlist, this ); m_setup->fadeIn(); - + connect( m_model, SIGNAL( tracksAdded() ), this, SLOT( tracksAdded() ) ); - + loadDynamicPlaylist( playlist ); - + m_layout->setContentsMargins( 0, 0, 0, 0 ); m_layout->setMargin( 0 ); m_layout->setSpacing( 0 ); @@ -103,7 +103,7 @@ DynamicWidget::~DynamicWidget() { } -void +void DynamicWidget::loadDynamicPlaylist( const Tomahawk::dynplaylist_ptr& playlist ) { // special case: if we have launched multiple setRevision calls, and the number of controls is different, it means that we're getting an intermediate setRevision @@ -114,61 +114,61 @@ DynamicWidget::loadDynamicPlaylist( const Tomahawk::dynplaylist_ptr& playlist ) return; } m_seqRevLaunched = 0; - + // if we're being told to load the same dynamic playlist over again, only do it if the controls have a different number if( !m_playlist.isNull() && ( m_playlist.data() == playlist.data() ) // same playlist pointer && m_playlist->generator()->controls().size() == playlist->generator()->controls().size() ) { // we can skip our work. just let the dynamiccontrollist show the difference m_controls->setControls( m_playlist, m_playlist->author()->isLocal() ); - + m_playlist = playlist; - + if( !m_runningOnDemand ) { m_model->loadPlaylist( m_playlist ); } else if( !m_controlsChanged ) { // if the controls changed, we already dealt with that and don't want to change station yet m_model->changeStation(); } m_controlsChanged = false; - + return; } - + if( !m_playlist.isNull() ) { disconnect( m_playlist->generator().data(), SIGNAL( generated( QList ) ), this, SLOT( tracksGenerated( QList ) ) ); disconnect( m_playlist.data(), SIGNAL( dynamicRevisionLoaded( Tomahawk::DynamicPlaylistRevision) ), this, SLOT(onRevisionLoaded( Tomahawk::DynamicPlaylistRevision) ) ); disconnect( m_playlist->generator().data(), SIGNAL( error( QString, QString ) ), this, SLOT( generatorError( QString, QString ) ) ); - disconnect( m_playlist.data(), SIGNAL( deleted( Tomahawk::dynplaylist_ptr ) ), this, SLOT( deleteLater() ) ); + disconnect( m_playlist.data(), SIGNAL( deleted( Tomahawk::dynplaylist_ptr ) ), this, SLOT( onDeleted() ) ); } - - + + m_playlist = playlist; m_view->setOnDemand( m_playlist->mode() == OnDemand ); m_view->setReadOnly( !m_playlist->author()->isLocal() ); m_model->loadPlaylist( m_playlist ); m_controlsChanged = false; m_setup->setPlaylist( m_playlist ); - - + + if( !m_playlist->author()->isLocal() ) { // hide controls, as we show the description in the summary m_layout->removeWidget( m_controls ); } else if( m_layout->indexOf( m_controls ) == -1 ) { m_layout->insertWidget( 0, m_controls ); - } - + } + if( m_playlist->mode() == OnDemand && !m_playlist->generator()->controls().isEmpty() ) showPreview(); - + if( !m_playlist.isNull() ) m_controls->setControls( m_playlist, m_playlist->author()->isLocal() ); - + connect( m_playlist->generator().data(), SIGNAL( generated( QList ) ), this, SLOT( tracksGenerated( QList ) ) ); connect( m_playlist.data(), SIGNAL( dynamicRevisionLoaded( Tomahawk::DynamicPlaylistRevision ) ), this, SLOT( onRevisionLoaded( Tomahawk::DynamicPlaylistRevision ) ) ); connect( m_playlist->generator().data(), SIGNAL( error( QString, QString ) ), this, SLOT( generatorError( QString, QString ) ) ); - connect( m_playlist.data(), SIGNAL( deleted( Tomahawk::dynplaylist_ptr ) ), this, SLOT( deleteLater() ) ); + connect( m_playlist.data(), SIGNAL( deleted( Tomahawk::dynplaylist_ptr ) ), this, SLOT( onDeleted() ) ); } -void +void DynamicWidget::onRevisionLoaded( const Tomahawk::DynamicPlaylistRevision& rev ) { qDebug() << "DynamicWidget::onRevisionLoaded"; @@ -180,7 +180,7 @@ DynamicWidget::onRevisionLoaded( const Tomahawk::DynamicPlaylistRevision& rev ) } } -PlaylistInterface* +PlaylistInterface* DynamicWidget::playlistInterface() const { return m_view->proxyModel(); @@ -194,36 +194,36 @@ DynamicWidget::sizeHint() const return QSize( 5000, 5000 ); } -void +void DynamicWidget::resizeEvent(QResizeEvent* ) { layoutFloatingWidgets(); } -void +void DynamicWidget::layoutFloatingWidgets() { if( !m_runningOnDemand ) { int x = ( width() / 2 ) - ( m_setup->size().width() / 2 ); int y = height() - m_setup->size().height() - 40; // padding - + m_setup->move( x, y ); } else if( m_runningOnDemand && m_steering ) { int x = ( width() / 2 ) - ( m_steering->size().width() / 2 ); int y = height() - m_steering->size().height() - 40; // padding - + m_steering->move( x, y ); } } -void +void DynamicWidget::playlistChanged( PlaylistInterface* pl ) { if( pl == static_cast< PlaylistInterface* >( m_view->proxyModel() ) ) { // same playlist m_activePlaylist = true; } else { m_activePlaylist = false; - + // user started playing something somewhere else, so give it a rest if( m_runningOnDemand ) { stopStation( false ); @@ -231,7 +231,7 @@ DynamicWidget::playlistChanged( PlaylistInterface* pl ) } } -void +void DynamicWidget::showEvent(QShowEvent* ) { if( !m_playlist.isNull() && !m_runningOnDemand ) { @@ -240,7 +240,7 @@ DynamicWidget::showEvent(QShowEvent* ) } -void +void DynamicWidget::generate( int num ) { // get the items from the generator, and put them in the playlist @@ -249,27 +249,27 @@ DynamicWidget::generate( int num ) m_playlist->generator()->generate( num ); } -void +void DynamicWidget::stationFailed( const QString& msg ) { m_view->setDynamicWorking( false ); m_view->showMessage( msg ); m_loading->fadeOut(); - + stopStation( false ); } -void +void DynamicWidget::trackStarted() -{ +{ if( m_activePlaylist && !m_playlist.isNull() && m_playlist->mode() == OnDemand && !m_runningOnDemand ) { - + startStation(); } } -void +void DynamicWidget::tracksAdded() { if( m_playlist->mode() == OnDemand && m_runningOnDemand && m_setup->isVisible() ) @@ -277,94 +277,94 @@ DynamicWidget::tracksAdded() } -void +void DynamicWidget::stopStation( bool stopPlaying ) { m_model->stopOnDemand( stopPlaying ); m_runningOnDemand = false; - + // TODO until i add a qwidget interface QMetaObject::invokeMethod( m_steering, "fadeOut", Qt::DirectConnection ); m_setup->fadeIn(); } -void +void DynamicWidget::startStation() { m_runningOnDemand = true; m_model->startOnDemand(); - + m_setup->fadeOut(); // show the steering controls if( m_playlist->generator()->onDemandSteerable() ) { // position it horizontally centered, above the botton. m_steering = m_playlist->generator()->steeringWidget(); Q_ASSERT( m_steering ); - + int x = ( width() / 2 ) - ( m_steering->size().width() / 2 ); int y = height() - m_steering->size().height() - 40; // padding - + m_steering->setParent( this ); m_steering->move( x, y ); - + // TODO until i add a qwidget interface QMetaObject::invokeMethod( m_steering, "fadeIn", Qt::DirectConnection ); - + connect( m_steering, SIGNAL( resized() ), this, SLOT( layoutFloatingWidgets() ) ); } } -void +void DynamicWidget::playlistTypeChanged( QString ) { // TODO } -void +void DynamicWidget::tracksGenerated( const QList< query_ptr >& queries ) -{ +{ int limit = -1; // only limit the "preview" of a station if( m_playlist->author()->isLocal() && m_playlist->mode() == Static ) { m_resolveOnNextLoad = true; } else if( m_playlist->mode() == OnDemand ) limit = 5; - + if( m_playlist->mode() != OnDemand ) m_loading->fadeOut(); m_model->tracksGenerated( queries, limit ); } -void +void DynamicWidget::controlsChanged() { // controlsChanged() is emitted when a control is added or removed // in the case of addition, it's blank by default... so to avoid an error // when playing a station just ignore it till we're ready and get a controlChanged() m_controlsChanged = true; - + if( !m_playlist->author()->isLocal() ) return; m_playlist->createNewRevision(); m_seqRevLaunched++; - + emit descriptionChanged( m_playlist->generator()->sentenceSummary() ); } -void +void DynamicWidget::controlChanged( const Tomahawk::dyncontrol_ptr& control ) -{ +{ if( !m_playlist->author()->isLocal() ) - return; + return; m_playlist->createNewRevision(); m_seqRevLaunched++; - + showPreview(); - + emit descriptionChanged( m_playlist->generator()->sentenceSummary() ); } -void +void DynamicWidget::showPreview() { if( m_playlist->mode() == OnDemand && !m_runningOnDemand && m_model->rowCount( QModelIndex() ) == 0 ) { // if this is a not running station, preview matching tracks @@ -373,7 +373,7 @@ DynamicWidget::showPreview() } -void +void DynamicWidget::generatorError( const QString& title, const QString& content ) { if( m_runningOnDemand ) { @@ -386,17 +386,17 @@ DynamicWidget::generatorError( const QString& title, const QString& content ) void DynamicWidget::paintRoundedFilledRect( QPainter& p, QPalette& pal, QRect& r, qreal opacity ) -{ +{ p.setBackgroundMode( Qt::TransparentMode ); p.setRenderHint( QPainter::Antialiasing ); p.setOpacity( opacity ); - + QPen pen( pal.dark().color(), .5 ); p.setPen( pen ); p.setBrush( pal.highlight() ); - + p.drawRoundedRect( r, 10, 10 ); - + p.setOpacity( opacity + .2 ); p.setBrush( QBrush() ); p.setPen( pen ); @@ -409,3 +409,10 @@ DynamicWidget::jumpToCurrentTrack() m_view->scrollTo( m_view->proxyModel()->currentItem(), QAbstractItemView::PositionAtCenter ); return true; } + +void +DynamicWidget::onDeleted() +{ + emit destroyed( widget() ); + deleteLater(); +} diff --git a/src/libtomahawk/playlist/dynamic/widgets/DynamicWidget.h b/src/libtomahawk/playlist/dynamic/widgets/DynamicWidget.h index 1771fd07b..6b8ef96e3 100644 --- a/src/libtomahawk/playlist/dynamic/widgets/DynamicWidget.h +++ b/src/libtomahawk/playlist/dynamic/widgets/DynamicWidget.h @@ -1,5 +1,5 @@ /* === This file is part of Tomahawk Player - === - * + * * Copyright 2010-2011, Christian Muehlhaeuser * * Tomahawk is free software: you can redistribute it and/or modify @@ -58,57 +58,59 @@ class CollapsibleControls; */ class DynamicWidget : public QWidget, public Tomahawk::ViewPage { -Q_OBJECT +Q_OBJECT public: explicit DynamicWidget( const dynplaylist_ptr& playlist, QWidget* parent = 0); virtual ~DynamicWidget(); - + void loadDynamicPlaylist( const dynplaylist_ptr& playlist ); - + virtual PlaylistInterface* playlistInterface() const; - + virtual QSize sizeHint() const; virtual void resizeEvent( QResizeEvent* ); virtual void showEvent(QShowEvent* ); - + static void paintRoundedFilledRect( QPainter& p, QPalette& pal, QRect& r, qreal opacity = .95 ); virtual QWidget* widget() { return this; } - + virtual QString title() const { return m_model->title(); } virtual QString description() const { return m_model->description(); } virtual QPixmap pixmap() const { return QPixmap( RESPATH "images/playlist-icon.png" ); } - + virtual bool jumpToCurrentTrack(); - + public slots: void onRevisionLoaded( const Tomahawk::DynamicPlaylistRevision& rev ); void playlistTypeChanged(QString); - + void startStation(); void stopStation( bool stopPlaying = true ); - + void trackStarted(); void stationFailed( const QString& ); - + void playlistChanged( PlaylistInterface* ); void tracksAdded(); - + signals: void descriptionChanged( const QString& caption ); - + void destroyed( QWidget* widget ); + private slots: void generate( int = -1 ); void tracksGenerated( const QList< Tomahawk::query_ptr>& queries ); void generatorError( const QString& title, const QString& content ); - + void controlsChanged(); void controlChanged( const Tomahawk::dyncontrol_ptr& control ); void showPreview(); - - void layoutFloatingWidgets(); -private: + void layoutFloatingWidgets(); + void onDeleted(); + +private: dynplaylist_ptr m_playlist; QVBoxLayout* m_layout; bool m_resolveOnNextLoad; @@ -117,17 +119,17 @@ private: // loading animation LoadingSpinner* m_loading; - + // setup controls DynamicSetupWidget* m_setup; - + // used in OnDemand mode bool m_runningOnDemand; bool m_controlsChanged; QWidget* m_steering; - + CollapsibleControls* m_controls; - + DynamicView* m_view; DynamicModel* m_model; }; diff --git a/src/libtomahawk/playlist/playlistmanager.cpp b/src/libtomahawk/playlist/playlistmanager.cpp index eac12794f..657f75645 100644 --- a/src/libtomahawk/playlist/playlistmanager.cpp +++ b/src/libtomahawk/playlist/playlistmanager.cpp @@ -163,8 +163,6 @@ PlaylistManager::show( const Tomahawk::playlist_ptr& playlist ) playlist->resolve(); m_playlistViews.insert( playlist, view ); - - connect( playlist.data(), SIGNAL( deleted( Tomahawk::playlist_ptr ) ), this, SLOT( onPlaylistDeleted( Tomahawk::playlist_ptr ) ) ); } else { @@ -540,11 +538,11 @@ PlaylistManager::setPage( ViewPage* page, bool trackHistory ) setHistoryPosition( m_pageHistory.count() - 1 ); } - if ( playlistForInterface( currentPlaylistInterface() ) ) + if ( !playlistForInterface( currentPlaylistInterface() ).isNull() ) emit playlistActivated( playlistForInterface( currentPlaylistInterface() ) ); - if ( dynamicPlaylistForInterface( currentPlaylistInterface() ) ) + if ( !dynamicPlaylistForInterface( currentPlaylistInterface() ).isNull() ) emit dynamicPlaylistActivated( dynamicPlaylistForInterface( currentPlaylistInterface() ) ); - if ( collectionForInterface( currentPlaylistInterface() ) ) + if ( !collectionForInterface( currentPlaylistInterface() ).isNull() ) emit collectionActivated( collectionForInterface( currentPlaylistInterface() ) ); if ( isSuperCollectionVisible() ) emit superCollectionActivated(); @@ -555,10 +553,17 @@ PlaylistManager::setPage( ViewPage* page, bool trackHistory ) AudioEngine::instance()->setPlaylist( currentPlaylistInterface() ); // UGH! - if( QObject* obj = dynamic_cast< QObject* >( currentPage() ) ) { + if ( QObject* obj = dynamic_cast< QObject* >( currentPage() ) ) + { // if the signal exists (just to hide the qobject runtime warning...) if( obj->metaObject()->indexOfSignal( "descriptionChanged(QString)" ) > -1 ) - connect( obj, SIGNAL( descriptionChanged( QString ) ), m_infobar, SLOT( setDescription( QString ) ) ); + connect( obj, SIGNAL( descriptionChanged( QString ) ), m_infobar, SLOT( setDescription( QString ) ), Qt::UniqueConnection ); + } + if ( QObject* obj = dynamic_cast< QObject* >( currentPage() ) ) + { + // if the signal exists (just to hide the qobject runtime warning...) + if( obj->metaObject()->indexOfSignal( "destroyed(QWidget*)" ) > -1 ) + connect( obj, SIGNAL( destroyed( QWidget* ) ), SLOT( onWidgetDestroyed( QWidget* ) ), Qt::UniqueConnection ); } m_stack->setCurrentWidget( page->widget() ); @@ -638,24 +643,6 @@ PlaylistManager::updateView() m_infobar->setPixmap( currentPage()->pixmap() ); } -void -PlaylistManager::onDynamicDeleted( const Tomahawk::dynplaylist_ptr& pl ) -{ - QWidget* w = m_dynamicWidgets.value( pl ); - m_dynamicWidgets.remove( pl ); - - onWidgetDestroyed( w ); -} - -void -PlaylistManager::onPlaylistDeleted( const Tomahawk::playlist_ptr& pl ) -{ - QWidget* w = m_playlistViews.value( pl ); - m_playlistViews.remove( pl ); - - onWidgetDestroyed( w ); -} - void PlaylistManager::onWidgetDestroyed( QWidget* widget ) @@ -663,11 +650,20 @@ PlaylistManager::onWidgetDestroyed( QWidget* widget ) qDebug() << "Destroyed child:" << widget << widget->metaObject()->className(); bool resetWidget = ( m_stack->currentWidget() == widget ); - m_stack->removeWidget( widget ); for ( int i = 0; i < m_pageHistory.count(); i++ ) { ViewPage* page = m_pageHistory.at( i ); + + if ( !playlistForInterface( page->playlistInterface() ).isNull() ) + { + m_playlistViews.remove( playlistForInterface( page->playlistInterface() ) ); + } + if ( !dynamicPlaylistForInterface( page->playlistInterface() ).isNull() ) + { + m_dynamicWidgets.remove( dynamicPlaylistForInterface( page->playlistInterface() ) ); + } + if ( page->widget() == widget ) { m_pageHistory.removeAt( i ); @@ -677,6 +673,8 @@ PlaylistManager::onWidgetDestroyed( QWidget* widget ) } } + m_stack->removeWidget( widget ); + if ( resetWidget ) { if ( m_pageHistory.count() ) @@ -783,6 +781,7 @@ PlaylistManager::playlistForInterface( PlaylistInterface* interface ) const { foreach ( PlaylistView* view, m_playlistViews.values() ) { + qDebug() << "LAAAA:" << view; if ( view->playlistInterface() == interface ) { return m_playlistViews.key( view ); diff --git a/src/libtomahawk/playlist/playlistmanager.h b/src/libtomahawk/playlist/playlistmanager.h index fc86acf66..6c7f66578 100644 --- a/src/libtomahawk/playlist/playlistmanager.h +++ b/src/libtomahawk/playlist/playlistmanager.h @@ -1,5 +1,5 @@ /* === This file is part of Tomahawk Player - === - * + * * Copyright 2010-2011, Christian Muehlhaeuser * * Tomahawk is free software: you can redistribute it and/or modify @@ -104,7 +104,7 @@ signals: void collectionActivated( const Tomahawk::collection_ptr& collection ); void playlistActivated( const Tomahawk::playlist_ptr& playlist ); void dynamicPlaylistActivated( const Tomahawk::dynplaylist_ptr& playlist ); - + public slots: bool showSuperCollection(); void showWelcomePage(); @@ -123,21 +123,19 @@ public slots: void setRepeatMode( PlaylistInterface::RepeatMode mode ); void setShuffled( bool enabled ); - + // called by the playlist creation dbcmds void createPlaylist( const Tomahawk::source_ptr& src, const QVariant& contents ); void createDynamicPlaylist( const Tomahawk::source_ptr& src, const QVariant& contents ); - + // ugh need to set up the connection in tomahawk to libtomahawk void onPlayClicked(); void onPauseClicked(); - + private slots: void setFilter( const QString& filter ); void applyFilter(); - - void onPlaylistDeleted( const Tomahawk::playlist_ptr& pl ); - void onDynamicDeleted( const Tomahawk::dynplaylist_ptr& pl ); + void onWidgetDestroyed( QWidget* widget ); private: @@ -149,7 +147,7 @@ private: Tomahawk::playlist_ptr playlistForInterface( PlaylistInterface* interface ) const; Tomahawk::dynplaylist_ptr dynamicPlaylistForInterface( PlaylistInterface* interface ) const; Tomahawk::collection_ptr collectionForInterface( PlaylistInterface* interface ) const; - + QWidget* m_widget; InfoBar* m_infobar; TopBar* m_topbar; @@ -164,7 +162,7 @@ private: CollectionFlatModel* m_superCollectionFlatModel; CollectionView* m_superCollectionView; WelcomeWidget* m_welcomeWidget; - + QList< Tomahawk::collection_ptr > m_superCollections; QHash< Tomahawk::dynplaylist_ptr, Tomahawk::DynamicWidget* > m_dynamicWidgets; @@ -174,13 +172,13 @@ private: QHash< Tomahawk::album_ptr, PlaylistView* > m_albumViews; QHash< Tomahawk::playlist_ptr, PlaylistView* > m_playlistViews; QHash< Tomahawk::source_ptr, SourceInfoWidget* > m_sourceViews; - + QList m_pageHistory; int m_historyPosition; Tomahawk::collection_ptr m_currentCollection; int m_currentMode; - + QTimer m_filterTimer; QString m_filter; diff --git a/src/libtomahawk/playlist/playlistmodel.cpp b/src/libtomahawk/playlist/playlistmodel.cpp index cdcad71aa..9f95c554b 100644 --- a/src/libtomahawk/playlist/playlistmodel.cpp +++ b/src/libtomahawk/playlist/playlistmodel.cpp @@ -1,5 +1,5 @@ /* === This file is part of Tomahawk Player - === - * + * * Copyright 2010-2011, Christian Muehlhaeuser * * Tomahawk is free software: you can redistribute it and/or modify @@ -71,7 +71,10 @@ void PlaylistModel::loadPlaylist( const Tomahawk::playlist_ptr& playlist, bool loadEntries ) { if ( !m_playlist.isNull() ) + { disconnect( m_playlist.data(), SIGNAL( revisionLoaded( Tomahawk::PlaylistRevision ) ), this, SLOT( onRevisionLoaded( Tomahawk::PlaylistRevision ) ) ); + disconnect( m_playlist.data(), SIGNAL( deleted( Tomahawk::playlist_ptr ) ), this, SIGNAL( playlistDeleted() ) ); + } if ( rowCount( QModelIndex() ) && loadEntries ) { @@ -80,6 +83,7 @@ PlaylistModel::loadPlaylist( const Tomahawk::playlist_ptr& playlist, bool loadEn m_playlist = playlist; connect( playlist.data(), SIGNAL( revisionLoaded( Tomahawk::PlaylistRevision ) ), SLOT( onRevisionLoaded( Tomahawk::PlaylistRevision ) ) ); + connect( playlist.data(), SIGNAL( deleted( Tomahawk::playlist_ptr ) ), this, SIGNAL( playlistDeleted() ) ); setReadOnly( !m_playlist->author()->isLocal() ); setTitle( playlist->title() ); @@ -105,7 +109,7 @@ PlaylistModel::loadPlaylist( const Tomahawk::playlist_ptr& playlist, bool loadEn plitem->index = createIndex( m_rootItem->children.count() - 1, 0, plitem ); connect( plitem, SIGNAL( dataChanged() ), SLOT( onDataChanged() ) ); - + if( !entry->query()->resolvingFinished() && entry->query()->playable() ) { m_waitingForResolved.append( entry->query().data() ); connect( entry->query().data(), SIGNAL( resolvingFinished( bool ) ), this, SLOT( trackResolved( bool ) ) ); @@ -119,7 +123,7 @@ PlaylistModel::loadPlaylist( const Tomahawk::playlist_ptr& playlist, bool loadEn if( !m_waitingForResolved.isEmpty() ) emit loadingStarted(); - + emit trackCountChanged( rowCount( QModelIndex() ) ); } @@ -145,7 +149,7 @@ PlaylistModel::loadHistory( const Tomahawk::source_ptr& source, unsigned int amo } -void +void PlaylistModel::clear() { if ( rowCount( QModelIndex() ) ) @@ -210,15 +214,15 @@ PlaylistModel::insert( unsigned int row, const Tomahawk::query_ptr& query ) onTracksInserted( row, ql ); } -void +void PlaylistModel::trackResolved( bool ) { Tomahawk::Query* q = qobject_cast< Query* >( sender() ); Q_ASSERT( q ); - + m_waitingForResolved.removeAll( q ); disconnect( q, SIGNAL( resolvingFinished( bool ) ), this, SLOT( trackResolved( bool ) ) ); - + if( m_waitingForResolved.isEmpty() ) emit loadingFinished(); } diff --git a/src/libtomahawk/playlist/playlistmodel.h b/src/libtomahawk/playlist/playlistmodel.h index e5ce24ae4..ad9bd9411 100644 --- a/src/libtomahawk/playlist/playlistmodel.h +++ b/src/libtomahawk/playlist/playlistmodel.h @@ -1,5 +1,5 @@ /* === This file is part of Tomahawk Player - === - * + * * Copyright 2010-2011, Christian Muehlhaeuser * * Tomahawk is free software: you can redistribute it and/or modify @@ -70,6 +70,9 @@ signals: void shuffleModeChanged( bool enabled ); void itemSizeChanged( const QModelIndex& index ); + + void playlistDeleted(); + private slots: void onDataChanged(); @@ -80,6 +83,7 @@ private slots: void onTracksInserted( unsigned int row, const QList& tracks ); void trackResolved( bool ); + private: QList playlistEntries() const; diff --git a/src/libtomahawk/playlist/playlistview.cpp b/src/libtomahawk/playlist/playlistview.cpp index 9b66257ad..79026e872 100644 --- a/src/libtomahawk/playlist/playlistview.cpp +++ b/src/libtomahawk/playlist/playlistview.cpp @@ -1,5 +1,5 @@ /* === This file is part of Tomahawk Player - === - * + * * Copyright 2010-2011, Christian Muehlhaeuser * * Tomahawk is free software: you can redistribute it and/or modify @@ -58,6 +58,7 @@ PlaylistView::setModel( PlaylistModel* model ) setGuid( "playlistview" ); connect( model, SIGNAL( trackCountChanged( unsigned int ) ), SLOT( onTrackCountChanged( unsigned int ) ) ); + connect( model, SIGNAL( playlistDeleted() ), SLOT( onDeleted() ) ); } @@ -154,3 +155,12 @@ PlaylistView::jumpToCurrentTrack() scrollTo( proxyModel()->currentItem(), QAbstractItemView::PositionAtCenter ); return true; } + + +void +PlaylistView::onDeleted() +{ + qDebug() << Q_FUNC_INFO; + emit destroyed( widget() ); + deleteLater(); +} diff --git a/src/libtomahawk/playlist/playlistview.h b/src/libtomahawk/playlist/playlistview.h index 3629099b1..5fa0d3a51 100644 --- a/src/libtomahawk/playlist/playlistview.h +++ b/src/libtomahawk/playlist/playlistview.h @@ -1,5 +1,5 @@ /* === This file is part of Tomahawk Player - === - * + * * Copyright 2010-2011, Christian Muehlhaeuser * * Tomahawk is free software: you can redistribute it and/or modify @@ -50,6 +50,9 @@ public: virtual bool jumpToCurrentTrack(); +signals: + void destroyed( QWidget* widget ); + protected: void keyPressEvent( QKeyEvent* event ); @@ -60,6 +63,8 @@ private slots: void addItemsToPlaylist(); void deleteItems(); + void onDeleted(); + private: void setupMenus(); diff --git a/src/libtomahawk/viewpage.h b/src/libtomahawk/viewpage.h index d775f451d..a515c47e6 100644 --- a/src/libtomahawk/viewpage.h +++ b/src/libtomahawk/viewpage.h @@ -1,5 +1,5 @@ /* === This file is part of Tomahawk Player - === - * + * * Copyright 2010-2011, Christian Muehlhaeuser * * Tomahawk is free software: you can redistribute it and/or modify @@ -29,7 +29,7 @@ namespace Tomahawk { - + class DLLEXPORT ViewPage { public: @@ -45,18 +45,18 @@ public: virtual bool showStatsBar() const { return true; } virtual bool showModes() const { return false; } virtual bool queueVisible() const { return true; } - + virtual bool jumpToCurrentTrack() = 0; /** subclasses implementing ViewPage can emit the following signals: * descriptionChanged( const QString& ) - * deleted() - * + * destroyed( QWidget* widget ); + * * See DynamicWidget for an example */ private: }; - + }; // ns #endif //VIEWPAGE_H diff --git a/src/libtomahawk/widgets/newplaylistwidget.h b/src/libtomahawk/widgets/newplaylistwidget.h index bb6c27629..61799ffd6 100644 --- a/src/libtomahawk/widgets/newplaylistwidget.h +++ b/src/libtomahawk/widgets/newplaylistwidget.h @@ -1,5 +1,5 @@ /* === This file is part of Tomahawk Player - === - * + * * Copyright 2010-2011, Christian Muehlhaeuser * * Tomahawk is free software: you can redistribute it and/or modify @@ -47,7 +47,7 @@ public: virtual QWidget* widget() { return this; } virtual PlaylistInterface* playlistInterface() const { return 0; } - + virtual QString title() const { return tr( "Create a new playlist" ); } virtual QString description() const { return QString(); } From 01befb0955b3335214f8c2f6e0f7601d6bc568e9 Mon Sep 17 00:00:00 2001 From: Christian Muehlhaeuser Date: Wed, 6 Apr 2011 08:06:54 +0200 Subject: [PATCH 269/329] * Added better quality icon images and merged stable. --- data/icons/tomahawk-icon-128x128.png | Bin 18334 -> 18676 bytes data/icons/tomahawk-icon-16x16.png | Bin 1047 -> 1362 bytes data/icons/tomahawk-icon-256x256.png | Bin 58137 -> 46412 bytes data/icons/tomahawk-icon-32x32.png | Bin 2507 -> 2627 bytes data/icons/tomahawk-icon-512x512.png | Bin 159739 -> 112854 bytes data/icons/tomahawk-icon-64x64.png | Bin 6830 -> 7051 bytes 6 files changed, 0 insertions(+), 0 deletions(-) diff --git a/data/icons/tomahawk-icon-128x128.png b/data/icons/tomahawk-icon-128x128.png index 6c6b0974d696f681ed1a232c2114b7ed66683c04..bbee6eb8044bb1affba590bd1338c0980e3b2aa9 100644 GIT binary patch literal 18676 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 XGCD9Ys@u4*00000NkvXXu0mjfMnE)( literal 18334 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 diff --git a/data/icons/tomahawk-icon-256x256.png b/data/icons/tomahawk-icon-256x256.png index a744288976c41869f047fce77537ccd930aa2148..227abc83849e83eb03bc84a7604dc0df04f92895 100644 GIT binary patch literal 46412 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
    ;WvL!P3qEdT}bZ{aeH%?5eNEvjT2A*;OpUBRG&n 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

    J-Oy_`h=s$kc0)e?Du`qH8pXI5o(stNk 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


    d(*YMg)} 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%0A`m_Ig0v2ARcg|ALcu20fGSvK@1?97{E zx*P_LrE08Us0DH*m1{+x4I(Fte{8W@5H$&WsS`R3bkn9+;)J_@NA>pKb3SM-q$bMa zdEo+I$@`TQ-NLU@=U(VA*2+**CrMsry>i}4lEom z{sstzw1gojpQMk820Uz33RYzWi`Gae zV(&z9mfns~Rg@gIH%>O7zlSoKk}QhKR-Lh`79pJDFYj#KGA9onnl86`0A<>j(8LD|%$Q`aNq<0i_gK&Ot$=s*NKKhx~NV*wkJ%d_X%SkI|-!d+*QzK7+lg+nhJ z25KPB0)?(Mso_OY0)cQU#i3s~=)vUVB;W)**nJcbw1c=qkEB`r8!&LX)*R6+Bgj25 z=|5hZP$4{^le@E(wVP~P*IxrO*KTU8=C!DCW z9G6u}oVDC??s7W%ZUD}wiFC+9BlE`M&fSo#R~4VO)HkM8`MxH8gv%+RN=4&%Tgz!A z<|lGO-XBE8lEkyKha3VgEDg@ncMevo9tNud)IA}`i(9N_(>BE(G2*{PW#i~5)S0rQ) zM&zwPF07)wB&uJV1&ew&yw4oBwzifJhO{lcP#D_3V`553i}Es=uPb?Xr<<))u0KpA zWS1Wo`Q$OJ?dG6$wY{Mg>B>~&=5 znMX#$96f>!Nm7r@N(Qb+u$={v@1Zkyd@27MT!-Jp9SeT{1spG!B5-kesAkMBMXy9#`_hkie$glHQ4iUDG;5(fDirlxU{;S!1@LlgYeqj@(%cg)_g)e=| zF=H{deJfBhz@Ha^zrQI~i+ zt!Ri$Ib`c|t@VzxpK%%go8uY+m^G{QDhWlJ-mGjM{-GSv3zfEtP||LB(;=-q>v<^d z^s!c^U}4TJ@nXls7joxHqg(YYuVh{tneYfsA7qE$HYi`&{G9Z9Vk2u3j{XOhq@yX! ziH`8c9L(lyao~IQzI-S2%IbN|0l@0RnkICc=#WhI)o8=&1r0apLHl^0yC>a&ujUyTnhajMngD%oV6paDn`5EQgMt5c6HG z(f@fgR(taZPk~<%JgC%EKUce3?rvJ$<6dzO`qBRiC=8{KOjrGoyvWJG1x_9nyU(b- z@1WFvZw_i7z^Khcarw+*irs<(F*84>Z$3sAx0K{<(LzMN0^;dGw_A-#_S=!V zUz-@pevJp#zUi+R9aQcq`6o$c3$@tn{c$z+6p73Osrp<&QAu|zReq{GRS_yV=p@mz zBdOi^kZ-iNjYm91kHC{5=FUMolC)jX)ja5){x+CycH=V2&f`>2=9VAdVJoVoO=iyv zwWYTnfU0hCb5j(|wGM?GSI$cLA(a`>g4NUze#bcB^GlY+Qc0b%XAx!M-W9A}jl{3) zLn#Hb5@Ryo^>vSRD%kaN7QG97!0uvB)QZ~d?&2}IO|b1{hTqgEqcAbFDQPS=MhWPg z6fX)GB#r4Je7+)NmwBkLKL<}IOv8bCO6kQ2j&HnKW=aC~I0@Suw^!d-AYcM;0cTTJ z$v#*n|MF{2wAgH+Kr_)#5esCC!my`C2WneS{=;2w^so=hT3vs6&MLMAa9@7(d6_-~ zTmGVG!9X0WcDuhBsM#UBdy*Wo3F?quJ*Ejqn0P5)68$T->6iY~T&*npSd7;cxG;tw z4QNsaB5%B?lIYv6kVb{T+c*%VuKo?40^?ftB zZ%5Fm`ydPY(m!eTJ_gw1vxZjk9FG((!P{TA(mcFpqC26JB#7kmK{;5m(8c^U7Rhrr z=;HO8SBqfqnbUc1XB}OBn`~wOE2f0Q_D>Ve+FtUEfZs|qI?6g8T7G zzf;4p$2}Mm%|jX)aer~Y2yYGn#-!iny?B4*QvQ~YwJ)oxpPJ=Y%dhstifKOB4BmhN zGSPp85jp!bF1#Ob+UFxZCBL09AD#olq~=G(c^UKu_cch%wd0B+_@+eAB5e$2!$FAi z45z-n_bRFcdY9RFDEyg9DbYnV-+;oX&zXp7JumrZqy(Y%~*5Hk4F`1 zcDIS&J?VZ~HQ6=xI?#Wu4k9j^>;k zvV3RgSn$)&==voN>`?4>uu4+RCymHe+54P5`cs&xgG4qKoZGzRPD#p<3WV z(i{ie7cm~L!EEGhhbJL0UK+i>4gZ|IMf44cWX&T=58ByeJeCSO(Sfj-)q_|=x?Kh`(m7A80{U8>2x*)i}sv9^ermtTz3+1eSzZ69JkdTScVp$=K% zx*DH@;kRmLqwWbi=wk1RnA}4dxeQ298-13&1oBY_qpU6o5dkjcVZoqbt#JJ#ifdl# zSM#Y+Z9P#oP$i0n;VNvEt~2XwrbVaP2^|Oe)s*JO_sr~w!0z&HA4!K4QF1wRQ^;ir zlR4DWtXsrjN$zZFhW>97H*{`!IQbtTk(4v?Ec7I^RfHF+cGi)jJi2GYlyj;Dy`;Ux zau!-Ou<$I2I@*PQ)@p$DN$V$&LdG zl*5H4#|iR6<Q4vO?d&_u7A%3n zd9>SsGXZtU<1`!Y|IUCRjOdZQLM^^?+;&w4Qd@Zq5v|c?$}$gTGI-G3zEu%MALveo zKq%`(Tvp^YI2vM2^Jd8yYREqixd-RHioHC{9$QDv`9#j zk8Fm-wRtenslVj>AwP9U=0)(!+m*8>sE}dN05?7vZm+=>H``CmtmUk41|pUp;95Z& ztpBc#NMoque(aK{H18}*<^0VB0aMOI@ z+F9uwoo7`-!)-F@Q!sOviaAwR*T?bf4;4pMGso6)@Y7_L2211lt`jKUaZPj)O-+dWCZGcE>m6a zIDGR=!zb_1r-^(Vxc=-uR=IP=>yKirwIvDubs{8=`!gIYU|L${%xk2~MvzuP05WgkyrL*)i#NYXp zMAs1=a<;K;L+ z<5s!cuN<*MtsZplnnF%e@34%W*HoMyU}f`baQE%o$agn2nCRx?8Q#47g1uuUW7Ic& zG%0)0H@)YY)hh2=e3lwPzCo(}#{E^wj0y@E$Enpk3#L}*Bh%&NH~=~g(|XE+P}tQS zoxSTk?-p4L7N_2`_2aGhJO|CIu_KNf_nwViM}hzs_z2H9!GAEw%a85yz9%BvuPj{# zheC7#iQA1rq4^EA?`k)S9<-ycr@jj7JKHWbv;c@-ujv(8>T%5g(l1*rkFSutqf0vl z6&HsaU6c+ZUtS?y!949%*8 z1Zp66v;F8Z2|gNdabe{rW)CX6Ui0=GgO`?;MaF=UqR`0rNb7+wLF_6Drw1QBzj8Lf zoG+-hu`GV@Db_Xjwk1>R_Yn)a!~eN08LTO6Io66=bK*!Td|AQ=2h$@} zYgud}kAUtSuffNC-onqSvO1a0zM!>abydjO3a4M_lDU(faAeSf7#G=o<%4O3P7qrJ zY(h;=Wmd^8McY>XuIV@cem5$Vft|CrlmTl>UZrFWASCIa7dZ7KDj`RNxobnf)Nh&vnGTyhiu7tZr63HGzc6;tp-46gfZw0x&M^Xyt zH>?p{M0GTe%J0)Q&mR|>swjTf7FpYp+EX;Ro{Q3&fnR0HL5IEdW11XKdg18Cu77HU6`P6G!R^bcWMxrt z^>q3A%fxq9?QOPo3ZgogyZ^3O;7WfmY z&)Hs#zx8%r?s55nPtg$oEKDPc3HgLTtJlJ&+*gr#uY&7F!vJ3iC`tTb=cn)wg}r5& zAw<)YH8%sB5ydpkai)wfh42Pzy%hU*KZN_ai90t|)7j%?rsbq?+&ELt+gqr9Q%=xG zy};Ua(*%5`7aKr+;>t1yxD`6rt-gblBODZY-oJPC|8!w$^_$`tV^cC|XHnPlf{-Cl zi>azwrmg49&mZ2UDBJDW2KUybrUU=dsKnMM7QI`fPfWvJDzlS$qDeFAZ_J|A;jK`~ zg`;HOXYU$i%KXp?$3#a5hjPmW4Ih7ArTUXh0ZMkfydQz|7HXR*XXiZab_+$H9|y`2 ze0=ye!5)231K7%`W!YPoLMZe^p|OZJ>FuyIKy#7N(YX|I%`S~V$Z^TSfX@JDZ-&p9 z7}EDCZ-Evw3d4c%;q?DPrXv#lti&C*9huti@(!>+P?Wsc98;ry0FXqr>npi(y9S?7 z*Z?M_U&ueMd3RzN90KpjEXrHViM)QmAYT*EX7m?92X>~xz~Nt;VN1zbI%Xy2 z!}pz48FqER-9P7csO|A)XB#V(KFZ@k=H{Y@r>CcYx+bCgaY`M~WOa({{Q)RL?w;Q= zl;Q*BesJ3XY^f)c2l61H!=0aH#iQxE<-5bMv78CaK&I={>A!ELo3Zz1HqW!Y)}tH; z2~+DrxbNc~qh zQUc1!1P1k+{=FTq#49;YJ^BGTf}rZYM{V3p_T`PC$CtJl0O1HwVh3Hn%j@fDyK(?0 zPIQbU>5(qXZ@ZOexG=&5dKnm+e_ff%io>3hwF6Ez$y1#5R0;17IwA#kEx0$qcVY!X zjvpQ)<4Y38DPXYCs$RH9ADp=RoS}P(SlD!I!Ma202EB~t{~oq%2(J9ksBF3QV~T%~ z8P*Aa^1ST0!JZdr4oFN6%r%(&XG_Ao9=LLiI;GKErE2u8w7lVFG#_$2RsBZ1bHpVA z;d60vChzvJ94BqL2k?0IU`;itdx+}(WVrAd*z#1$CzwQU50d`@q-L@o#*pFiKInEROa;DK*Y7F&I)LX+oKJYq|FiZA7bJA?+k0&|bajIRymOWOwBgaFqm*E zmNSEyARwNz8vB6cB^U@?6jrE^lXW+Ay#z zRHn)2GV`RRlWeA$L8QM&F)t&CZP`C{zd;RratYf4;2hjk#Y#_^Lxoe-QE)j>utt(k z?hSJvs0#3DFO8>WrEw?!eN?Ms+z5CDe0tkZe2t~}hOJ`(!Ni&=RCxdARlHx02(^B^ zZU0S)DQjE_;u@zmc4_(uhRAJ9=vElqVTEp<(uvzj^v zI!6t(wGL})8E9&nJTFB5KM#b4Ukthw|NnnLYhBFPx#AY({UO#lFTCIA3{ga82g z0001h=l}q9FaQARU;qF*m;eA5aGbhPJdrURe+}0F01ejxLMWSf000_*Nkl6;4c25yB}=7JO{%%x{N5c;f4HwiNh(=YMx>Ly)~hdd-+lZ4 zf8YD;v-dvx2>zKx*L6J415kk^&;Zl}F+c%qAPbBEV?v0kloBcB??oT+&jDJ93qNB4 z%Yd6CYz4U#Ned7M^qM|CP(_Gw;GBTNz%F1vFbv3RNyhJ$fGNP!z?~A_iIg`gLeyte zRm@d2+BA)NRV5|_5~xVYnD5i?d-Qo8e-plsFQttTrv&^A_#to{2(ASYzc&JE;Efva zj{(~nRn@%7itySWf79c7 z$BTh~9w^sd5s4~FOS`78SZ3%qY&5MKH<{)GXj|=d^iG&TW_Xf5sEiP!x#}qe6)Lg%HM+gmtut>r~e|3s-!)NLn zxXFs}ja>G~4~m74=X`%Bu&gB$egBd<^>10!F}Hp0+%_fM&_XnkV&`+u^YEijaET>- z;4JWuz=KlCb1_A^`K|H9-+m;O-g3yX`Q+FrBfei2@Co4S%yO5wCIr-!PDuFsm=N#y zY<&Z-jYRqU#Q1YRDC9o?f1FK4jeoaxVe6k?zj5t?WotK)nX>>RmO{~Vq?CN|OJCxt zC!gdBt}KBQz~4yZkC1e{C7$@&UuWv>JZRf|d~Ae#5DW`=C$JM{9vIbY1Rmhu3-}Ko zOr_O#B$9kFJMo+E7V_@_vK>vSFTMVzO&@>LTkdOEal`9KwaiDef1)75n32t9`M>}4 zPvmmBD-j@oMqs-@q!Hq=la9T!AmxU4CKB@_io)J%B_?43@CZ1szVVK?-g@`j)J)rAgr*~E-;**R@I6kRJi()nJ%;Cb zTqT(4z*YeZ5aQ>@e{6eCTv2ZSx5-r9QOBm+^V$Ur0?*GhC~DWj`V!u139<4+sWe@# z>wT|~|AHfB-@1jZ-@fmJqDHHbfn53zuz8r zZofMgGoGlFH3@eS?i|6@lvl3yz*Ve|gw1J1iETD5e-7By?qOZAwyv1}kxkofRm@}^ zo@29j=OY~7{|xW@<9m7c2mg$=#j8MqQz_zBOZcvh@B5^Zao+xho2XC632J87R6#PL zk&0?)>SY$y1$=zi^Ys0;eQLdFQLm~<*hpAmUR7Qd0X6Uza1T$2(TeYhF>`wh-NlaQ4Je4(;2;f1kYP?JVE)TI^C5r!azB&Rqsx20k(f zP*sJ6ZB3}k<*I3_AY!QK>ZSh^o0?R0b%Uz1(X`NnNJ{u$z-NGr5CTP+9_p-OKrOkL zBjKB-5O?1fkMp5qn&pN;pXZTlPLYU4FcWDi#XQeG@ffeaWfL23ybZgY!zqm7mGk)a zfArud?H#qu8qt?WKTQ#+s=)Iv{_jD@X3Y2b&zU;b8zv9rCNig8_b&y^1wJOF9RHm> zpl11qgwI5Txce{C8UD7ukyVC)0_;`-occJf>md=mbm$-v#pbR%Zo>~`2)>fTvy1pn zwFch13_L&(1k-|_6kQP)+s;Qm)zz_xEtyk)Iqz-co0wph69Kw8n;9u5cVZ+nTlyPe;yD=%8g5P zgL%4s!TJg?76c3+@jM^L^%%{U*|=&x+izS=JYwS6B?7l{0X%Yg%h%W@S1dC!k-u6| z6#l*q#& zU$%+Iqx_rKZy{ppL`)qSfB5)L72kF7Jr~dO@O^*UdR-BM!O;nFh4PFD2uXm5p9`cD4)3~W z8*NP){2(BZ61(bP+b*u>Ur28SplT|np%JkRbX^Mx5pcF|1lw_Ef2bB9K~oi?rcSZy z;7G}6Z6^q7UZ5)~F-2iK2r@MSva=;10_X`v;nMu1A|*w~K`T~RvAB(!H!Q&q0w!`r zx_i!Zeq@|Nsf_Ko1TxGf4PD1F4dT%V>13SdhBQqLDIC}1_?f<%cg(O^CBXFqVwORv z>JR{>Kwcmqs6Au|e+9s-eP-{C zKbZVbD{%#is$iKWjrD0t6`QW!VH8DqrGCI=9v}#pk0M0aPnZIJ@^huP&6I#L;N*jV zsY}-fR7DW@5=~WEIJc1?2QwE*5>TY% zP_>MYf0P2ThfB^4u1bKEp?eB=JR1b}|D;^XyuQw2SVAR`R3+4#2A#z+&mZk#`;Ds@ z9m|u+Bxq^OU>Z6tjrDk*PY?vPbuK{&BBnuoCP7^$j;3iOV-ZHO1>9P|j9D6~xQ?#X z$dF(=KK|^bL%WOhjpFNa1;j-@1RzqCyCCUBfaB zq?9yd60|mD7#`0f5jcK8C2uocbTG6qU$eb{Qq`kuhdIU4wnnyZUe2>e&*FMMhN7_9 ze~LgYSJ5OqTFf!%`v~|6;c?qVGNZ0=Ez9x*Y^3}30Xg&A|-CLj&)n^ zq*@$jHsm=CxxbBY1|wmdqTkTe*R!fAuggcJ{GlRR^st8IBw~ zhvx~}+gnkDKq3%AK~Z(2@8Y>O5bom(+L}={70>%^Ggs99goi26l#a7$Njqy6f6T$q zHJ&-r#aO(cY9{f91Ru<~F1;b&byc5%!(x10?IMD0jsYlzoq16$?C+ z&obisMG^J|e*n{;7tXjJAf=p$CsN9s_Jy=BT}iaQk%4NN{oUsn%NJSNHV0KzsB22| zVrL&;`TkA{9LCNhM+zC0n|xf6$nYv3fx( z%iEes#Uhxx#&EXCqrd5-Sg|n`h0UhPuZlT7F?^n{PK+_)``rRQ3Va1{uXI>?)fiCg zs0u0Nt*+zTtEyVEW5F^akp!v`ES=xZj+?Kit+^53QdzNd3FrIH^WC5Ql1Rj0%c}W| zjOGaZfKs{4@X#o>?O>S-e~R#HzoDRN5foMD`NJm|9L)k@C$}jRr#X`(6^jxz4U(}4 zs!(`%&oN%^9}UkKfn$!%u1X~s^8G#u-xu%~z~g}bN+-qIOu+lT{|-e_zPw=3>gFvs z+(|r!eH;=Gra2!e_Kf;qWFFQ2!g=p zTz3yq3tF1fxUPq8m}r^~j)&_7)RIF4VCp)$rlG4UhOQDZbh4!?=Z3NumQnyzr92P( zR{>8D&J=hvIU>Iz0h9H05ct~_Mg8)UW$T)6x@8BdW@6|XZ@T3MZdkh#MN#lPpS?$p zXfqzEmZZh~n687{z4R(2PdH?eFy2^Vn6FWHQRha|86AAE})Q zLLemqfv%|-nu?|C7@EphzD&h&xuj*F0egX$XEPviwHPp0O8zWU*VsIN(JE9;N7Gcc z-?*9URxYj013~w|5HEI~!PLXXXe=IM&C=D3=SmEWWJx9>eFgdNf02mN*qEZRF-db%n%34jd_N$YE1@WYOe&60Mc4?LVpa)^uiNIxHSxcK*8&P?Kdy`R&H0xwmfJww*4-DO^y2ce*|@P;YU#wa`_UD&74?rwpGBB2BqF9-(XZWfH1+U}*zp$&- zuUE_CY~8wrLOz^f6hd&*hSl7*VGVnJy@!FG^XL^9Es(5Owvd=+q8U2bY>vmCew3=6lt~aVbj}QnVY}XRPt6B5 zTq^>MAn@kx*s+6L!N>MM*HvD(bt89e-oTMPhp0FEa3rN+cS-E^M0tHpoDA;)h z&iB&OdzL^-_8#fF&=9TK4#`-IyS8oQzPq-usJ#Wx_g_&-p$OU<(%7!Yss7O`J!SxF ze+Xp3_UCA|JUr-?^R4xaAEncn>;B^3n0tmd9BJUqqv?|#i|Uh}?=e+A1F z+tFFKbTO{y5z%zIPjoS7-W;;o0*bDZX-czb=|UP?nmFG*LMdNi_t9rLcyu?4^sx+$ zh@oQYg5ilG+tw|{GIb0+JP&AVZD7TsHX1TX3`1vRB9HIO3#G8Ewb8F%xsYPT<<~F1 zjOz!l)G+T7Lj0I;OMF!x(3(!Cf8V=x>-B2yKo*3`!ILjzs2Z9Zux!mz%%sKO=m^RB zBy|mS%xQ1Mbv#r}rEYGTp1~ffr2>hFK_a4&jA&T8!rmiid1Tko3){S^D#Rit%NMrs z*6rJP?|paBkWOAyu?14n-k6~&o#5#sU6iZN)lLg;C9MA9Gd@|m#QJZ#f9|?XYb$nu z#KiGDmd~HZ%^TOy(%yoRH0d20VBYdJlBqat^X8(eDz4{K*PNuTHN}m$Y{T^>aZ4o~ z)i6}(9Vt>M+kE>cyE%G#;G(8yASFQ%5RaN9;}PVP@;GK0Y+N#r126Y;vS%0}#28_N z_DT{1?g##G64z`zAQ_9rf8Mcf-8!|WZxq9{Xs%21`fZzuMIw0kJhtlze)i-qNTlP` z)n`CJK3~KzbsV=s=ebT^I(eM_kv!dldCm?Q`6YIYys!Ti#&Pg41OR>gp(d01_oXMlMd`4f7}&jMZk5-mMy!^ zc04>^C1RRvzi~5j8XG9uC7yY451;zXf5G)!Ow&ZwHFAXlX2irabWXf{l6^<_^VHK% z0x&pHp=+>6uH+5_Uj^PMg!uga^>+`m`_O5IMzWMjRRUj9sX7!&6%<9GJ`rcb zlKBi}%l!O>6O?VIe}N5I<%OWjxUgc$`xZIXE@ohY)B07M?(O2x z%g6X1U-|+kPn_6onr0#vi>=di4KrdAk4Dinjnik(P;@KoeSR-N5KIyf)U5SmA%v_c zUIiWyLL3;+f0sV`(`OFf_W1M1)5(~Hp{h8pN5yd|S6%!d7#Bk96oRi3mbsT+i#ZB> z4fv0QGmDe*Jn$gQY-E127D%Mi={dgdGd?_avf^f1^65bVjNeHnAI7aO(Zz`DDdwjxMBBz0S2=5>s1ik~DnThqM5)inq>&N49 z=5@@aceI-mJ;!T!cg!n5dWkJ)li0}JIAw&1q4SM=|8P1RL)KgF4dEUc9 zhz}87e_ipW>@F86Uy};(LX9xNZ{v|a6YEX!s2>9Vhj7ii%<{&_Bmv{Y!^2NK^UO1A z7B61RGtWNDv17-&UDtg;2=O3L1maT4pjxd^s+5UX7GBlk$f=h&-g$!K$Bz#YUS%s! zDlij8YR||?;Iok_@AqHL_W`siZuz-Ad-ja)f7!EV8KLVvtSHLKDRerHe3ENPdi;Kk^Iu`ug?=A^uKE`7h)C-Yn#@z4A&6$PY_aO9*k#>NTsi zf1&YVp4s&rPe1*1x0Le3LWpDkzrp-oW%6d2s;UnzUc9*4(b3^4it-{?7(xBlBCkg4 zObv8W2=SDZ^2~J6{$GpyKO}6i05UK!F)c7O zEig1xF*Q0eH###pD=;!TFfiMO01N;CI{+ngMObuXVRU6WZEs|0W_bWIFflPLFf=VN jG*mG)Ix;spGdU|TGCD9Ys@u4*00000NkvXXu0mjf30HQ4 delta 6786 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 Date: Wed, 6 Apr 2011 08:19:10 +0200 Subject: [PATCH 270/329] * Remove debug output. --- src/libtomahawk/playlist/playlistmanager.cpp | 1 - src/sourcetree/sourcetreeview.cpp | 35 +++++++++++--------- 2 files changed, 19 insertions(+), 17 deletions(-) diff --git a/src/libtomahawk/playlist/playlistmanager.cpp b/src/libtomahawk/playlist/playlistmanager.cpp index 657f75645..1fada1bc0 100644 --- a/src/libtomahawk/playlist/playlistmanager.cpp +++ b/src/libtomahawk/playlist/playlistmanager.cpp @@ -781,7 +781,6 @@ PlaylistManager::playlistForInterface( PlaylistInterface* interface ) const { foreach ( PlaylistView* view, m_playlistViews.values() ) { - qDebug() << "LAAAA:" << view; if ( view->playlistInterface() == interface ) { return m_playlistViews.key( view ); diff --git a/src/sourcetree/sourcetreeview.cpp b/src/sourcetree/sourcetreeview.cpp index 98d9e5b16..050f895db 100644 --- a/src/sourcetree/sourcetreeview.cpp +++ b/src/sourcetree/sourcetreeview.cpp @@ -1,5 +1,5 @@ /* === This file is part of Tomahawk Player - === - * + * * Copyright 2010-2011, Christian Muehlhaeuser * * Tomahawk is free software: you can redistribute it and/or modify @@ -81,7 +81,7 @@ SourceTreeView::SourceTreeView( QWidget* parent ) setUniformRowHeights( false ); setIndentation( 16 ); setAnimated( true ); - + setItemDelegate( new SourceDelegate( this ) ); setContextMenuPolicy( Qt::CustomContextMenu ); @@ -132,10 +132,10 @@ SourceTreeView::setupMenus() if ( type == SourcesModel::PlaylistSource || type == SourcesModel::DynamicPlaylistSource ) { playlist_ptr playlist = SourcesModel::indexToDynamicPlaylist( m_contextMenuIndex ); - if( playlist.isNull() ) + if ( playlist.isNull() ) { playlist = SourcesModel::indexToPlaylist( m_contextMenuIndex ); - } + } if ( !playlist.isNull() ) { readonly = !playlist->author()->isLocal(); @@ -263,7 +263,7 @@ SourceTreeView::onItemActivated( const QModelIndex& index ) if ( !playlist.isNull() ) { qDebug() << "Dynamic Playlist activated:" << playlist->title(); - + PlaylistManager::instance()->show( playlist ); } } @@ -298,13 +298,16 @@ SourceTreeView::deletePlaylist() playlist_ptr playlist = SourcesModel::indexToPlaylist( idx ); if ( !playlist.isNull() ) { - qDebug() << "Playlist about to be deleted:" << playlist->title(); Playlist::remove( playlist ); } - } else if( type == SourcesModel::DynamicPlaylistSource ) { - dynplaylist_ptr playlist = SourcesModel::indexToDynamicPlaylist( idx ); + } + else if ( type == SourcesModel::DynamicPlaylistSource ) + { + dynplaylist_ptr playlist = SourcesModel::indexToDynamicPlaylist( idx ); if( !playlist.isNull() ) + { DynamicPlaylist::remove( playlist ); + } } } @@ -505,7 +508,7 @@ SourceDelegate::paint( QPainter* painter, const QStyleOptionViewItem& option, co painter->setFont( smaller ); o.font = smaller; #endif - + if ( ( option.state & QStyle::State_Enabled ) == QStyle::State_Enabled ) { o.state = QStyle::State_Enabled; @@ -515,13 +518,13 @@ SourceDelegate::paint( QPainter* painter, const QStyleOptionViewItem& option, co o.palette.setColor( QPalette::Text, o.palette.color( QPalette::HighlightedText ) ); } } - + QStyleOptionViewItemV4 o3 = option; if ( index.data( SourceTreeItem::Type ) != SourcesModel::CollectionSource ) o3.rect.setX( 0 ); - + QApplication::style()->drawControl( QStyle::CE_ItemViewItem, &o3, painter ); - + if ( index.data( SourceTreeItem::Type ) == SourcesModel::CollectionSource ) { painter->save(); @@ -529,7 +532,7 @@ SourceDelegate::paint( QPainter* painter, const QStyleOptionViewItem& option, co QFont normal = painter->font(); QFont bold = painter->font(); bold.setBold( true ); - + SourceTreeItem* sti = SourcesModel::indexToTreeItem( index ); bool status = !( !sti || sti->source().isNull() || !sti->source()->isOnline() ); QString tracks; @@ -548,7 +551,7 @@ SourceDelegate::paint( QPainter* painter, const QStyleOptionViewItem& option, co { painter->setPen( o.palette.color( QPalette::HighlightedText ) ); } - + QRect textRect = option.rect.adjusted( iconRect.width() + 8, 6, -figWidth - 24, 0 ); if ( status || sti->source().isNull() ) painter->setFont( bold ); @@ -608,8 +611,8 @@ SourceDelegate::paint( QPainter* painter, const QStyleOptionViewItem& option, co { QStyledItemDelegate::paint( painter, o, index ); } - + #ifdef Q_WS_MAC painter->setFont( savedFont ); -#endif +#endif } From 17c1cdc2c1ef4338050d9efe1ef565fb2544fe0f Mon Sep 17 00:00:00 2001 From: Christian Muehlhaeuser Date: Wed, 6 Apr 2011 11:48:49 +0200 Subject: [PATCH 271/329] * Renamed PlItem to TrackModelItem. --- src/libtomahawk/CMakeLists.txt | 4 +-- .../playlist/collectionflatmodel.cpp | 18 +++++----- .../playlist/collectionflatmodel.h | 4 +-- src/libtomahawk/playlist/collectionmodel.cpp | 33 +++++++++---------- src/libtomahawk/playlist/collectionmodel.h | 10 +++--- .../playlist/collectionproxymodel.cpp | 6 ++-- .../playlist/playlistitemdelegate.cpp | 12 +++---- src/libtomahawk/playlist/playlistmodel.cpp | 16 ++++----- src/libtomahawk/playlist/playlistmodel.h | 2 +- src/libtomahawk/playlist/trackmodel.cpp | 32 +++++++++--------- src/libtomahawk/playlist/trackmodel.h | 8 ++--- .../{plitem.cpp => trackmodelitem.cpp} | 26 +++++++-------- .../playlist/{plitem.h => trackmodelitem.h} | 22 ++++++------- src/libtomahawk/playlist/trackproxymodel.cpp | 10 +++--- src/libtomahawk/playlist/trackproxymodel.h | 4 +-- src/libtomahawk/playlist/trackview.cpp | 12 +++---- 16 files changed, 109 insertions(+), 110 deletions(-) rename src/libtomahawk/playlist/{plitem.cpp => trackmodelitem.cpp} (81%) rename src/libtomahawk/playlist/{plitem.h => trackmodelitem.h} (69%) diff --git a/src/libtomahawk/CMakeLists.txt b/src/libtomahawk/CMakeLists.txt index c7f3ac4e2..188f6c1b3 100644 --- a/src/libtomahawk/CMakeLists.txt +++ b/src/libtomahawk/CMakeLists.txt @@ -78,7 +78,6 @@ set( libSources playlist/collectionflatmodel.cpp playlist/collectionview.cpp playlist/playlistmanager.cpp - playlist/plitem.cpp playlist/playlistmodel.cpp playlist/playlistproxymodel.cpp playlist/playlistview.cpp @@ -86,6 +85,7 @@ set( libSources playlist/queueproxymodel.cpp playlist/queueview.cpp playlist/trackmodel.cpp + playlist/trackmodelitem.cpp playlist/trackproxymodel.cpp playlist/trackview.cpp playlist/trackheader.cpp @@ -232,7 +232,6 @@ set( libHeaders playlist/collectionflatmodel.h playlist/collectionview.h playlist/playlistmanager.h - playlist/plitem.h playlist/playlistmodel.h playlist/playlistproxymodel.h playlist/playlistview.h @@ -240,6 +239,7 @@ set( libHeaders playlist/queueproxymodel.h playlist/queueview.h playlist/trackmodel.h + playlist/trackmodelitem.h playlist/trackproxymodel.h playlist/trackview.h playlist/trackheader.h diff --git a/src/libtomahawk/playlist/collectionflatmodel.cpp b/src/libtomahawk/playlist/collectionflatmodel.cpp index 83d4a6386..aa7cae7e6 100644 --- a/src/libtomahawk/playlist/collectionflatmodel.cpp +++ b/src/libtomahawk/playlist/collectionflatmodel.cpp @@ -1,5 +1,5 @@ /* === This file is part of Tomahawk Player - === - * + * * Copyright 2010-2011, Christian Muehlhaeuser * * Tomahawk is free software: you can redistribute it and/or modify @@ -42,11 +42,11 @@ CollectionFlatModel::~CollectionFlatModel() } -void +void CollectionFlatModel::addCollections( const QList< collection_ptr >& collections ) { qDebug() << Q_FUNC_INFO << "Adding collections!"; - foreach( const collection_ptr& col, collections ) + foreach( const collection_ptr& col, collections ) { addCollection( col ); } @@ -120,7 +120,7 @@ CollectionFlatModel::removeCollection( const collection_ptr& collection ) QTime timer; timer.start(); -// QList plitems = m_collectionIndex.values( collection ); +// QList plitems = m_collectionIndex.values( collection ); QList< QPair< int, int > > rows; QList< QPair< int, int > > sortrows; QPair< int, int > row; @@ -167,7 +167,7 @@ CollectionFlatModel::removeCollection( const collection_ptr& collection ) emit beginRemoveRows( QModelIndex(), row.first, row.second ); for ( int i = row.second; i >= row.first; i-- ) { - PlItem* item = itemFromIndex( index( i, 0, QModelIndex() ) ); + TrackModelItem* item = itemFromIndex( index( i, 0, QModelIndex() ) ); delete item; } emit endRemoveRows(); @@ -212,12 +212,12 @@ CollectionFlatModel::processTracksToAdd() emit beginInsertRows( QModelIndex(), c, c + maxc - 1 ); //beginResetModel(); - PlItem* plitem; + TrackModelItem* plitem; QList< Tomahawk::query_ptr >::iterator iter = m_tracksToAdd.begin(); for( int i = 0; i < maxc; ++i ) { - plitem = new PlItem( *iter, m_rootItem ); + plitem = new TrackModelItem( *iter, m_rootItem ); plitem->index = createIndex( m_rootItem->children.count() - 1, 0, plitem ); connect( plitem, SIGNAL( dataChanged() ), SLOT( onDataChanged() ) ); @@ -245,7 +245,7 @@ CollectionFlatModel::onTracksRemoved( const QList& tracks ) QList t = tracks; for ( int i = rowCount( QModelIndex() ); i >= 0 && t.count(); i-- ) { - PlItem* item = itemFromIndex( index( i, 0, QModelIndex() ) ); + TrackModelItem* item = itemFromIndex( index( i, 0, QModelIndex() ) ); if ( !item ) continue; @@ -275,7 +275,7 @@ CollectionFlatModel::onTracksRemoved( const QList& tracks ) void CollectionFlatModel::onDataChanged() { - PlItem* p = (PlItem*)sender(); + TrackModelItem* p = (TrackModelItem*)sender(); // emit itemSizeChanged( p->index ); if ( p ) diff --git a/src/libtomahawk/playlist/collectionflatmodel.h b/src/libtomahawk/playlist/collectionflatmodel.h index cbfee0d36..fee195376 100644 --- a/src/libtomahawk/playlist/collectionflatmodel.h +++ b/src/libtomahawk/playlist/collectionflatmodel.h @@ -1,5 +1,5 @@ /* === This file is part of Tomahawk Player - === - * + * * Copyright 2010-2011, Christian Muehlhaeuser * * Tomahawk is free software: you can redistribute it and/or modify @@ -23,7 +23,7 @@ #include #include -#include "plitem.h" +#include "trackmodelitem.h" #include "trackmodel.h" #include "collection.h" #include "query.h" diff --git a/src/libtomahawk/playlist/collectionmodel.cpp b/src/libtomahawk/playlist/collectionmodel.cpp index 4ba38d861..ac0a5dc18 100644 --- a/src/libtomahawk/playlist/collectionmodel.cpp +++ b/src/libtomahawk/playlist/collectionmodel.cpp @@ -1,5 +1,5 @@ /* === This file is part of Tomahawk Player - === - * + * * Copyright 2010-2011, Christian Muehlhaeuser * * Tomahawk is free software: you can redistribute it and/or modify @@ -40,7 +40,6 @@ CollectionModel::CollectionModel( QObject* parent ) CollectionModel::~CollectionModel() { -// delete m_rootItem; } @@ -50,8 +49,8 @@ CollectionModel::index( int row, int column, const QModelIndex& parent ) const if ( !m_rootItem || row < 0 || column < 0 ) return QModelIndex(); - PlItem* parentItem = itemFromIndex( parent ); - PlItem* childItem = parentItem->children.value( row ); + TrackModelItem* parentItem = itemFromIndex( parent ); + TrackModelItem* childItem = parentItem->children.value( row ); if ( !childItem ) return QModelIndex(); @@ -65,7 +64,7 @@ CollectionModel::rowCount( const QModelIndex& parent ) const if ( parent.column() > 0 ) return 0; - PlItem* parentItem = itemFromIndex( parent ); + TrackModelItem* parentItem = itemFromIndex( parent ); if ( !parentItem ) return 0; @@ -83,15 +82,15 @@ CollectionModel::columnCount( const QModelIndex& parent ) const QModelIndex CollectionModel::parent( const QModelIndex& child ) const { - PlItem* entry = itemFromIndex( child ); + TrackModelItem* entry = itemFromIndex( child ); if ( !entry ) return QModelIndex(); - PlItem* parentEntry = entry->parent; + TrackModelItem* parentEntry = entry->parent; if ( !parentEntry ) return QModelIndex(); - PlItem* grandparentEntry = parentEntry->parent; + TrackModelItem* grandparentEntry = parentEntry->parent; if ( !grandparentEntry ) return QModelIndex(); @@ -106,7 +105,7 @@ CollectionModel::data( const QModelIndex& index, int role ) const if ( role != Qt::DisplayRole ) return QVariant(); - PlItem* entry = itemFromIndex( index ); + TrackModelItem* entry = itemFromIndex( index ); if ( !entry ) return QVariant(); @@ -199,7 +198,7 @@ CollectionModel::removeCollection( const collection_ptr& collection ) disconnect( collection.data(), SIGNAL( tracksFinished( Tomahawk::collection_ptr ) ), this, SLOT( onTracksAddingFinished( Tomahawk::collection_ptr ) ) ); - QList plitems = m_collectionIndex.values( collection ); + QList plitems = m_collectionIndex.values( collection ); m_collectionIndex.remove( collection ); } @@ -210,17 +209,17 @@ CollectionModel::onTracksAdded( const QList& tracks, const { // int c = rowCount( QModelIndex() ); - PlItem* plitem; + TrackModelItem* plitem; foreach( const Tomahawk::query_ptr& query, tracks ) { - PlItem* parent = m_rootItem; + TrackModelItem* parent = m_rootItem; if ( parent->hash.contains( query->artist() ) ) { parent = parent->hash.value( query->artist() ); } else { - parent = new PlItem( query->artist(), m_rootItem ); + parent = new TrackModelItem( query->artist(), m_rootItem ); m_rootItem->hash.insert( query->artist(), parent ); } @@ -232,14 +231,14 @@ CollectionModel::onTracksAdded( const QList& tracks, const } else { - PlItem* subitem = new PlItem( query->album(), parent ); + TrackModelItem* subitem = new TrackModelItem( query->album(), parent ); parent->hash.insert( query->album(), subitem ); parent->childCount++; subitem->childCount++; parent = subitem; } - plitem = new PlItem( query, parent ); + plitem = new TrackModelItem( query, parent ); m_collectionIndex.insertMulti( collection, plitem ); } @@ -275,11 +274,11 @@ CollectionModel::onSourceOffline( Tomahawk::source_ptr src ) } -PlItem* +TrackModelItem* CollectionModel::itemFromIndex( const QModelIndex& index ) const { if ( index.isValid() ) - return static_cast( index.internalPointer() ); + return static_cast( index.internalPointer() ); else { return m_rootItem; diff --git a/src/libtomahawk/playlist/collectionmodel.h b/src/libtomahawk/playlist/collectionmodel.h index 346133013..d6563922f 100644 --- a/src/libtomahawk/playlist/collectionmodel.h +++ b/src/libtomahawk/playlist/collectionmodel.h @@ -1,5 +1,5 @@ /* === This file is part of Tomahawk Player - === - * + * * Copyright 2010-2011, Christian Muehlhaeuser * * Tomahawk is free software: you can redistribute it and/or modify @@ -23,7 +23,7 @@ #include #include -#include "plitem.h" +#include "trackmodelitem.h" #include "collection.h" #include "query.h" #include "typedefs.h" @@ -62,7 +62,7 @@ public: virtual void setRepeatMode( PlaylistInterface::RepeatMode mode ) {} virtual void setShuffled( bool shuffled ) {} - PlItem* itemFromIndex( const QModelIndex& index ) const; + TrackModelItem* itemFromIndex( const QModelIndex& index ) const; signals: void repeatModeChanged( PlaylistInterface::RepeatMode mode ); @@ -79,8 +79,8 @@ private slots: void onSourceOffline( Tomahawk::source_ptr src ); private: - PlItem* m_rootItem; - QMap< Tomahawk::collection_ptr, PlItem* > m_collectionIndex; + TrackModelItem* m_rootItem; + QMap< Tomahawk::collection_ptr, TrackModelItem* > m_collectionIndex; }; #endif // COLLECTIONMODEL_H diff --git a/src/libtomahawk/playlist/collectionproxymodel.cpp b/src/libtomahawk/playlist/collectionproxymodel.cpp index f25532d1f..f6c5dd7b5 100644 --- a/src/libtomahawk/playlist/collectionproxymodel.cpp +++ b/src/libtomahawk/playlist/collectionproxymodel.cpp @@ -1,5 +1,5 @@ /* === This file is part of Tomahawk Player - === - * + * * Copyright 2010-2011, Christian Muehlhaeuser * * Tomahawk is free software: you can redistribute it and/or modify @@ -35,8 +35,8 @@ CollectionProxyModel::CollectionProxyModel( QObject* parent ) bool CollectionProxyModel::lessThan( const QModelIndex& left, const QModelIndex& right ) const { - PlItem* p1 = itemFromIndex( left ); - PlItem* p2 = itemFromIndex( right ); + TrackModelItem* p1 = itemFromIndex( left ); + TrackModelItem* p2 = itemFromIndex( right ); if ( !p1 ) return true; diff --git a/src/libtomahawk/playlist/playlistitemdelegate.cpp b/src/libtomahawk/playlist/playlistitemdelegate.cpp index 0edc22e7f..8e9adb3a7 100644 --- a/src/libtomahawk/playlist/playlistitemdelegate.cpp +++ b/src/libtomahawk/playlist/playlistitemdelegate.cpp @@ -1,5 +1,5 @@ /* === This file is part of Tomahawk Player - === - * + * * Copyright 2010-2011, Christian Muehlhaeuser * * Tomahawk is free software: you can redistribute it and/or modify @@ -24,10 +24,10 @@ #include "query.h" #include "result.h" -#include "playlist/plitem.h" -#include "playlist/trackproxymodel.h" -#include "playlist/trackview.h" -#include "playlist/trackheader.h" +#include "trackmodelitem.h" +#include "trackproxymodel.h" +#include "trackview.h" +#include "trackheader.h" #include "utils/tomahawkutils.h" @@ -68,7 +68,7 @@ PlaylistItemDelegate::createEditor( QWidget* parent, const QStyleOptionViewItem& void PlaylistItemDelegate::paint( QPainter* painter, const QStyleOptionViewItem& option, const QModelIndex& index ) const { - PlItem* item = m_model->itemFromIndex( m_model->mapToSource( index ) ); + TrackModelItem* item = m_model->itemFromIndex( m_model->mapToSource( index ) ); if ( !item || item->query().isNull() ) return; diff --git a/src/libtomahawk/playlist/playlistmodel.cpp b/src/libtomahawk/playlist/playlistmodel.cpp index 9f95c554b..fe5f6451e 100644 --- a/src/libtomahawk/playlist/playlistmodel.cpp +++ b/src/libtomahawk/playlist/playlistmodel.cpp @@ -92,7 +92,7 @@ PlaylistModel::loadPlaylist( const Tomahawk::playlist_ptr& playlist, bool loadEn if ( !loadEntries ) return; - PlItem* plitem; + TrackModelItem* plitem; QList entries = playlist->entries(); if ( entries.count() ) { @@ -105,7 +105,7 @@ PlaylistModel::loadPlaylist( const Tomahawk::playlist_ptr& playlist, bool loadEn foreach( const plentry_ptr& entry, entries ) { qDebug() << entry->query()->toString(); - plitem = new PlItem( entry, m_rootItem ); + plitem = new TrackModelItem( entry, m_rootItem ); plitem->index = createIndex( m_rootItem->children.count() - 1, 0, plitem ); connect( plitem, SIGNAL( dataChanged() ), SLOT( onDataChanged() ) ); @@ -158,7 +158,7 @@ PlaylistModel::clear() delete m_rootItem; m_rootItem = 0; emit endResetModel(); - m_rootItem = new PlItem( 0, this ); + m_rootItem = new TrackModelItem( 0, this ); } } @@ -252,13 +252,13 @@ PlaylistModel::onTracksInserted( unsigned int row, const QListsetQuery( query ); - plitem = new PlItem( entry, m_rootItem, row + i ); + plitem = new TrackModelItem( entry, m_rootItem, row + i ); plitem->index = createIndex( row + i, 0, plitem ); i++; @@ -274,7 +274,7 @@ PlaylistModel::onTracksInserted( unsigned int row, const QListindex.isValid() ) emit dataChanged( p->index, p->index.sibling( p->index.row(), columnCount() - 1 ) ); } @@ -350,7 +350,7 @@ PlaylistModel::dropMimeData( const QMimeData* data, Qt::DropAction action, int r e->setAnnotation( "" ); // FIXME e->setQuery( query ); - PlItem* plitem = new PlItem( e, m_rootItem, beginRow ); + TrackModelItem* plitem = new TrackModelItem( e, m_rootItem, beginRow ); plitem->index = createIndex( beginRow++, 0, plitem ); connect( plitem, SIGNAL( dataChanged() ), SLOT( onDataChanged() ) ); @@ -411,7 +411,7 @@ PlaylistModel::playlistEntries() const if ( !idx.isValid() ) continue; - PlItem* item = itemFromIndex( idx ); + TrackModelItem* item = itemFromIndex( idx ); if ( item ) l << item->entry(); } diff --git a/src/libtomahawk/playlist/playlistmodel.h b/src/libtomahawk/playlist/playlistmodel.h index ad9bd9411..c3ad13e09 100644 --- a/src/libtomahawk/playlist/playlistmodel.h +++ b/src/libtomahawk/playlist/playlistmodel.h @@ -22,7 +22,7 @@ #include #include -#include "plitem.h" +#include "trackmodelitem.h" #include "trackmodel.h" #include "collection.h" #include "query.h" diff --git a/src/libtomahawk/playlist/trackmodel.cpp b/src/libtomahawk/playlist/trackmodel.cpp index a8dacfd26..6bcb438a8 100644 --- a/src/libtomahawk/playlist/trackmodel.cpp +++ b/src/libtomahawk/playlist/trackmodel.cpp @@ -33,7 +33,7 @@ using namespace Tomahawk; TrackModel::TrackModel( QObject* parent ) : QAbstractItemModel( parent ) - , m_rootItem( new PlItem( 0, this ) ) + , m_rootItem( new TrackModelItem( 0, this ) ) , m_readOnly( true ) { qDebug() << Q_FUNC_INFO; @@ -55,8 +55,8 @@ TrackModel::index( int row, int column, const QModelIndex& parent ) const if ( !m_rootItem || row < 0 || column < 0 ) return QModelIndex(); - PlItem* parentItem = itemFromIndex( parent ); - PlItem* childItem = parentItem->children.value( row ); + TrackModelItem* parentItem = itemFromIndex( parent ); + TrackModelItem* childItem = parentItem->children.value( row ); if ( !childItem ) return QModelIndex(); @@ -70,7 +70,7 @@ TrackModel::rowCount( const QModelIndex& parent ) const if ( parent.column() > 0 ) return 0; - PlItem* parentItem = itemFromIndex( parent ); + TrackModelItem* parentItem = itemFromIndex( parent ); if ( !parentItem ) return 0; @@ -88,15 +88,15 @@ TrackModel::columnCount( const QModelIndex& parent ) const QModelIndex TrackModel::parent( const QModelIndex& child ) const { - PlItem* entry = itemFromIndex( child ); + TrackModelItem* entry = itemFromIndex( child ); if ( !entry ) return QModelIndex(); - PlItem* parentEntry = entry->parent; + TrackModelItem* parentEntry = entry->parent; if ( !parentEntry ) return QModelIndex(); - PlItem* grandparentEntry = parentEntry->parent; + TrackModelItem* grandparentEntry = parentEntry->parent; if ( !grandparentEntry ) return QModelIndex(); @@ -108,7 +108,7 @@ TrackModel::parent( const QModelIndex& child ) const QVariant TrackModel::data( const QModelIndex& index, int role ) const { - PlItem* entry = itemFromIndex( index ); + TrackModelItem* entry = itemFromIndex( index ); if ( !entry ) return QVariant(); @@ -222,13 +222,13 @@ void TrackModel::setCurrentItem( const QModelIndex& index ) { qDebug() << Q_FUNC_INFO; - PlItem* oldEntry = itemFromIndex( m_currentIndex ); + TrackModelItem* oldEntry = itemFromIndex( m_currentIndex ); if ( oldEntry ) { oldEntry->setIsPlaying( false ); } - PlItem* entry = itemFromIndex( index ); + TrackModelItem* entry = itemFromIndex( index ); if ( entry ) { m_currentIndex = index; @@ -283,7 +283,7 @@ TrackModel::mimeData( const QModelIndexList &indexes ) const continue; QModelIndex idx = index( i.row(), 0, i.parent() ); - PlItem* item = itemFromIndex( idx ); + TrackModelItem* item = itemFromIndex( idx ); if ( item ) { const query_ptr& query = item->query(); @@ -315,7 +315,7 @@ TrackModel::removeIndex( const QModelIndex& index, bool moreToCome ) if ( index.column() > 0 ) return; - PlItem* item = itemFromIndex( index ); + TrackModelItem* item = itemFromIndex( index ); if ( item ) { emit beginRemoveRows( index.parent(), index.row(), index.row() ); @@ -337,11 +337,11 @@ TrackModel::removeIndexes( const QList& indexes ) } -PlItem* +TrackModelItem* TrackModel::itemFromIndex( const QModelIndex& index ) const { if ( index.isValid() ) - return static_cast( index.internalPointer() ); + return static_cast( index.internalPointer() ); else { return m_rootItem; @@ -352,7 +352,7 @@ TrackModel::itemFromIndex( const QModelIndex& index ) const void TrackModel::onPlaybackFinished( const Tomahawk::result_ptr& result ) { - PlItem* oldEntry = itemFromIndex( m_currentIndex ); + TrackModelItem* oldEntry = itemFromIndex( m_currentIndex ); if ( oldEntry && !oldEntry->query().isNull() && oldEntry->query()->results().contains( result ) ) { oldEntry->setIsPlaying( false ); @@ -363,7 +363,7 @@ TrackModel::onPlaybackFinished( const Tomahawk::result_ptr& result ) void TrackModel::onPlaybackStopped() { - PlItem* oldEntry = itemFromIndex( m_currentIndex ); + TrackModelItem* oldEntry = itemFromIndex( m_currentIndex ); if ( oldEntry ) { oldEntry->setIsPlaying( false ); diff --git a/src/libtomahawk/playlist/trackmodel.h b/src/libtomahawk/playlist/trackmodel.h index b3a5e0c24..3fbb1e868 100644 --- a/src/libtomahawk/playlist/trackmodel.h +++ b/src/libtomahawk/playlist/trackmodel.h @@ -1,5 +1,5 @@ /* === This file is part of Tomahawk Player - === - * + * * Copyright 2010-2011, Christian Muehlhaeuser * * Tomahawk is free software: you can redistribute it and/or modify @@ -22,7 +22,7 @@ #include #include "playlistinterface.h" -#include "playlist/plitem.h" +#include "trackmodelitem.h" #include "dllmacro.h" @@ -80,9 +80,9 @@ public: virtual void append( const Tomahawk::query_ptr& query ) = 0; - PlItem* itemFromIndex( const QModelIndex& index ) const; + TrackModelItem* itemFromIndex( const QModelIndex& index ) const; - PlItem* m_rootItem; + TrackModelItem* m_rootItem; signals: void repeatModeChanged( PlaylistInterface::RepeatMode mode ); diff --git a/src/libtomahawk/playlist/plitem.cpp b/src/libtomahawk/playlist/trackmodelitem.cpp similarity index 81% rename from src/libtomahawk/playlist/plitem.cpp rename to src/libtomahawk/playlist/trackmodelitem.cpp index 853df520a..e88659601 100644 --- a/src/libtomahawk/playlist/plitem.cpp +++ b/src/libtomahawk/playlist/trackmodelitem.cpp @@ -1,5 +1,5 @@ /* === This file is part of Tomahawk Player - === - * + * * Copyright 2010-2011, Christian Muehlhaeuser * * Tomahawk is free software: you can redistribute it and/or modify @@ -16,7 +16,7 @@ * along with Tomahawk. If not, see . */ -#include "plitem.h" +#include "trackmodelitem.h" #include "utils/tomahawkutils.h" #include "playlist.h" @@ -27,7 +27,7 @@ using namespace Tomahawk; -PlItem::~PlItem() +TrackModelItem::~TrackModelItem() { // Don't use qDeleteAll here! The children will remove themselves // from the list when they get deleted and the qDeleteAll iterator @@ -42,7 +42,7 @@ PlItem::~PlItem() } -PlItem::PlItem( PlItem* parent, QAbstractItemModel* model ) +TrackModelItem::TrackModelItem( TrackModelItem* parent, QAbstractItemModel* model ) { this->parent = parent; this->model = model; @@ -56,7 +56,7 @@ PlItem::PlItem( PlItem* parent, QAbstractItemModel* model ) } -PlItem::PlItem( const QString& caption, PlItem* parent ) +TrackModelItem::TrackModelItem( const QString& caption, TrackModelItem* parent ) { this->parent = parent; this->caption = caption; @@ -72,35 +72,35 @@ PlItem::PlItem( const QString& caption, PlItem* parent ) } -PlItem::PlItem( const Tomahawk::query_ptr& query, PlItem* parent, int row ) +TrackModelItem::TrackModelItem( const Tomahawk::query_ptr& query, TrackModelItem* parent, int row ) : QObject( parent ) { setupItem( query, parent, row ); } -PlItem::PlItem( const Tomahawk::plentry_ptr& entry, PlItem* parent, int row ) +TrackModelItem::TrackModelItem( const Tomahawk::plentry_ptr& entry, TrackModelItem* parent, int row ) : QObject( parent ) , m_entry( entry ) { setupItem( entry->query(), parent, row ); } -const Tomahawk::plentry_ptr& -PlItem::entry() const +const Tomahawk::plentry_ptr& +TrackModelItem::entry() const { return m_entry; } -const Tomahawk::query_ptr& -PlItem::query() const +const Tomahawk::query_ptr& +TrackModelItem::query() const { if ( !m_entry.isNull() ) return m_entry->query(); else return m_query; } void -PlItem::setupItem( const Tomahawk::query_ptr& query, PlItem* parent, int row ) +TrackModelItem::setupItem( const Tomahawk::query_ptr& query, TrackModelItem* parent, int row ) { this->parent = parent; if ( parent ) @@ -132,7 +132,7 @@ PlItem::setupItem( const Tomahawk::query_ptr& query, PlItem* parent, int row ) connect( query.data(), SIGNAL( resultsRemoved( Tomahawk::result_ptr ) ), SIGNAL( dataChanged() ) ); - + connect( query.data(), SIGNAL( resultsChanged() ), SIGNAL( dataChanged() ) ); } diff --git a/src/libtomahawk/playlist/plitem.h b/src/libtomahawk/playlist/trackmodelitem.h similarity index 69% rename from src/libtomahawk/playlist/plitem.h rename to src/libtomahawk/playlist/trackmodelitem.h index 3d28957fc..70b93923c 100644 --- a/src/libtomahawk/playlist/plitem.h +++ b/src/libtomahawk/playlist/trackmodelitem.h @@ -1,5 +1,5 @@ /* === This file is part of Tomahawk Player - === - * + * * Copyright 2010-2011, Christian Muehlhaeuser * * Tomahawk is free software: you can redistribute it and/or modify @@ -29,17 +29,17 @@ #include "dllmacro.h" -class DLLEXPORT PlItem : public QObject +class DLLEXPORT TrackModelItem : public QObject { Q_OBJECT public: - virtual ~PlItem(); + virtual ~TrackModelItem(); - explicit PlItem( PlItem* parent = 0, QAbstractItemModel* model = 0 ); - explicit PlItem( const QString& caption, PlItem* parent = 0 ); - explicit PlItem( const Tomahawk::query_ptr& query, PlItem* parent = 0, int row = -1 ); - explicit PlItem( const Tomahawk::plentry_ptr& entry, PlItem* parent = 0, int row = -1 ); + explicit TrackModelItem( TrackModelItem* parent = 0, QAbstractItemModel* model = 0 ); + explicit TrackModelItem( const QString& caption, TrackModelItem* parent = 0 ); + explicit TrackModelItem( const Tomahawk::query_ptr& query, TrackModelItem* parent = 0, int row = -1 ); + explicit TrackModelItem( const Tomahawk::plentry_ptr& entry, TrackModelItem* parent = 0, int row = -1 ); const Tomahawk::plentry_ptr& entry() const; const Tomahawk::query_ptr& query() const; @@ -47,9 +47,9 @@ public: bool isPlaying() { return m_isPlaying; } void setIsPlaying( bool b ) { m_isPlaying = b; emit dataChanged(); } - PlItem* parent; - QVector children; - QHash hash; + TrackModelItem* parent; + QVector children; + QHash hash; QString caption; int childCount; QPersistentModelIndex index; @@ -60,7 +60,7 @@ signals: void dataChanged(); private: - void setupItem( const Tomahawk::query_ptr& query, PlItem* parent, int row = -1 ); + void setupItem( const Tomahawk::query_ptr& query, TrackModelItem* parent, int row = -1 ); Tomahawk::plentry_ptr m_entry; Tomahawk::query_ptr m_query; diff --git a/src/libtomahawk/playlist/trackproxymodel.cpp b/src/libtomahawk/playlist/trackproxymodel.cpp index 3b66b70de..6fa2ac3f6 100644 --- a/src/libtomahawk/playlist/trackproxymodel.cpp +++ b/src/libtomahawk/playlist/trackproxymodel.cpp @@ -1,5 +1,5 @@ /* === This file is part of Tomahawk Player - === - * + * * Copyright 2010-2011, Christian Muehlhaeuser * * Tomahawk is free software: you can redistribute it and/or modify @@ -76,7 +76,7 @@ TrackProxyModel::tracks() for ( int i = 0; i < rowCount( QModelIndex() ); i++ ) { - PlItem* item = itemFromIndex( mapToSource( index( i, 0 ) ) ); + TrackModelItem* item = itemFromIndex( mapToSource( index( i, 0 ) ) ); if ( item ) queries << item->query(); } @@ -135,7 +135,7 @@ TrackProxyModel::siblingItem( int itemsAway ) // Try to find the next available PlaylistItem (with results) if ( idx.isValid() ) do { - PlItem* item = itemFromIndex( mapToSource( idx ) ); + TrackModelItem* item = itemFromIndex( mapToSource( idx ) ); qDebug() << item->query()->toString(); if ( item && item->query()->playable() ) { @@ -156,14 +156,14 @@ TrackProxyModel::siblingItem( int itemsAway ) bool TrackProxyModel::filterAcceptsRow( int sourceRow, const QModelIndex& sourceParent ) const { - PlItem* pi = itemFromIndex( sourceModel()->index( sourceRow, 0, sourceParent ) ); + TrackModelItem* pi = itemFromIndex( sourceModel()->index( sourceRow, 0, sourceParent ) ); if ( !pi ) return false; const Tomahawk::query_ptr& q = pi->query(); if( q.isNull() ) // uh oh? filter out invalid queries i guess return false; - + Tomahawk::result_ptr r; if ( q->numResults() ) r = q->results().first(); diff --git a/src/libtomahawk/playlist/trackproxymodel.h b/src/libtomahawk/playlist/trackproxymodel.h index 4452ef20d..fa68b8c4b 100644 --- a/src/libtomahawk/playlist/trackproxymodel.h +++ b/src/libtomahawk/playlist/trackproxymodel.h @@ -1,5 +1,5 @@ /* === This file is part of Tomahawk Player - === - * + * * Copyright 2010-2011, Christian Muehlhaeuser * * Tomahawk is free software: you can redistribute it and/or modify @@ -59,7 +59,7 @@ public: bool showOfflineResults() const { return m_showOfflineResults; } void setShowOfflineResults( bool b ) { m_showOfflineResults = b; } - PlItem* itemFromIndex( const QModelIndex& index ) const { return sourceModel()->itemFromIndex( index ); } + TrackModelItem* itemFromIndex( const QModelIndex& index ) const { return sourceModel()->itemFromIndex( index ); } signals: void repeatModeChanged( PlaylistInterface::RepeatMode mode ); diff --git a/src/libtomahawk/playlist/trackview.cpp b/src/libtomahawk/playlist/trackview.cpp index d3d064ec2..3ec4ce4c7 100644 --- a/src/libtomahawk/playlist/trackview.cpp +++ b/src/libtomahawk/playlist/trackview.cpp @@ -1,5 +1,5 @@ /* === This file is part of Tomahawk Player - === - * + * * Copyright 2010-2011, Christian Muehlhaeuser * * Tomahawk is free software: you can redistribute it and/or modify @@ -69,12 +69,12 @@ TrackView::TrackView( QWidget* parent ) f.setPointSize( f.pointSize() - 1 ); setFont( f ); #endif - + #ifdef Q_WS_MAC f.setPointSize( f.pointSize() - 2 ); setFont( f ); #endif - + connect( this, SIGNAL( doubleClicked( QModelIndex ) ), SLOT( onItemActivated( QModelIndex ) ) ); } @@ -119,7 +119,7 @@ TrackView::setModel( TrackModel* model ) connect( m_model, SIGNAL( itemSizeChanged( QModelIndex ) ), SLOT( onItemResized( QModelIndex ) ) ); connect( m_model, SIGNAL( loadingStarted() ), m_loadingSpinner, SLOT( fadeIn() ) ); connect( m_model, SIGNAL( loadingFinished() ), m_loadingSpinner, SLOT( fadeOut() ) ); - + connect( m_proxyModel, SIGNAL( filterChanged( QString ) ), SLOT( onFilterChanged( QString ) ) ); setAcceptDrops( true ); @@ -129,7 +129,7 @@ TrackView::setModel( TrackModel* model ) void TrackView::onItemActivated( const QModelIndex& index ) { - PlItem* item = m_model->itemFromIndex( m_proxyModel->mapToSource( index ) ); + TrackModelItem* item = m_model->itemFromIndex( m_proxyModel->mapToSource( index ) ); if ( item && item->query()->numResults() ) { qDebug() << "Result activated:" << item->query()->toString() << item->query()->results().first()->url(); @@ -178,7 +178,7 @@ TrackView::addItemsToQueue() if ( idx.column() ) continue; - PlItem* item = model()->itemFromIndex( proxyModel()->mapToSource( idx ) ); + TrackModelItem* item = model()->itemFromIndex( proxyModel()->mapToSource( idx ) ); if ( item && item->query()->numResults() ) { PlaylistManager::instance()->queue()->model()->append( item->query() ); From 0c52b37bb25f0921e4f4f9311f76f7cd2a81308b Mon Sep 17 00:00:00 2001 From: Jeff Mitchell Date: Wed, 6 Apr 2011 19:42:50 -0400 Subject: [PATCH 272/329] InfoSystemCache saving and loading from disk works. We might have to tweak exactly how it's stored on disk at some point, depending on efficiency, but this works. --- src/infosystem/infosystemcache.cpp | 68 +++++++++++++++++++++--------- src/infosystem/infosystemcache.h | 4 +- 2 files changed, 49 insertions(+), 23 deletions(-) diff --git a/src/infosystem/infosystemcache.cpp b/src/infosystem/infosystemcache.cpp index 3a86c5634..7d73c6ade 100644 --- a/src/infosystem/infosystemcache.cpp +++ b/src/infosystem/infosystemcache.cpp @@ -39,13 +39,11 @@ InfoSystemCache::InfoSystemCache( QObject* parent ) for( int i = 0; i <= InfoNoInfo; i++ ) { InfoType type = (InfoType)(i); - if( m_dirtySet.contains( type ) && m_dataCache.contains( type ) ) - { - QString cacheDir = cacheBaseDir + QString::number( i ); - QDir dir( cacheDir ); - if( dir.exists() && QFile::exists( QString( cacheDir + '/' + QString::number( i ) ) ) ) - loadCache( type, QString( cacheDir + '/' + QString::number( i ) ) ); - } + QString cacheDir = cacheBaseDir + "/InfoSystemCache/" + QString::number( i ); + QString cacheFile = cacheDir + '/' + QString::number( i ); + QDir dir( cacheDir ); + if( dir.exists() && QFile::exists( cacheFile ) ) + loadCache( type, cacheFile ); } } @@ -60,7 +58,7 @@ InfoSystemCache::~InfoSystemCache() InfoType type = (InfoType)(i); if( m_dirtySet.contains( type ) && m_dataCache.contains( type ) ) { - QString cacheDir = cacheBaseDir + QString::number( i ); + QString cacheDir = cacheBaseDir + "/InfoSystemCache/" + QString::number( i ); saveCache( type, cacheDir ); } } @@ -85,8 +83,8 @@ void InfoSystemCache::updateCacheSlot( Tomahawk::InfoSystem::InfoCacheCriteria criteria, Tomahawk::InfoSystem::InfoType type, QVariant output ) { qDebug() << Q_FUNC_INFO; - QHash< InfoCacheCriteria, QVariant > typedatacache; - QHash< InfoCacheCriteria, QDateTime > typetimecache; + QHash< InfoCacheCriteria, QVariant > typedatacache = m_dataCache[type]; + QHash< InfoCacheCriteria, QDateTime > typetimecache = m_timeCache[type]; typedatacache[criteria] = output; typetimecache[criteria] = QDateTime::currentDateTimeUtc(); m_dataCache[type] = typedatacache; @@ -96,9 +94,32 @@ InfoSystemCache::updateCacheSlot( Tomahawk::InfoSystem::InfoCacheCriteria criter void -InfoSystemCache::loadCache( InfoType type, const QString &cacheDir ) +InfoSystemCache::loadCache( InfoType type, const QString &cacheFile ) { qDebug() << Q_FUNC_INFO; + QSettings cachedSettings( cacheFile, QSettings::IniFormat ); + + foreach( QString group, cachedSettings.childGroups() ) + { + QHash< InfoCacheCriteria, QVariant > dataHash = m_dataCache[type]; + QHash< InfoCacheCriteria, QDateTime > dateHash = m_timeCache[type]; + InfoCacheCriteria criteria; + cachedSettings.beginGroup( group ); + int numCriteria = cachedSettings.beginReadArray( "criteria" ); + for( int i = 0; i < numCriteria; i++ ) + { + cachedSettings.setArrayIndex( i ); + QStringList criteriaValues = cachedSettings.value( QString::number( i ) ).toStringList(); + for( int j = 0; j < criteriaValues.length(); j += 2 ) + criteria[criteriaValues.at( j )] = criteriaValues.at( j + 1 ); + } + cachedSettings.endArray(); + dataHash[criteria] = cachedSettings.value( "data" ); + dateHash[criteria] = cachedSettings.value( "time" ).toDateTime(); + cachedSettings.endGroup(); + m_dataCache[type] = dataHash; + m_timeCache[type] = dateHash; + } } @@ -117,22 +138,27 @@ InfoSystemCache::saveCache( InfoType type, const QString &cacheDir ) } } - QSettings cacheFile( QString( cacheDir + '/' + QString::number( (int)type ) ), QSettings::IniFormat ); - + QSettings cachedSettings( QString( cacheDir + '/' + QString::number( (int)type ) ), QSettings::IniFormat ); + + int criteriaNumber = 0; + foreach( InfoCacheCriteria criteria, m_dataCache[type].keys() ) { - cacheFile.beginGroup( "type_" + QString::number( type ) ); - cacheFile.beginWriteArray( "criteria" ); + cachedSettings.beginGroup( "group_" + QString::number( criteriaNumber ) ); + cachedSettings.beginWriteArray( "criteria" ); QStringList keys = criteria.keys(); for( int i = 0; i < criteria.size(); i++ ) { - cacheFile.setArrayIndex( i ); - cacheFile.setValue( keys.at( i ), criteria[keys.at( i )] ); + cachedSettings.setArrayIndex( i ); + QStringList critVal; + critVal << keys.at( i ) << criteria[keys.at( i )]; + cachedSettings.setValue( QString::number( i ), critVal ); } - cacheFile.endArray(); - cacheFile.setValue( "data", m_dataCache[type][criteria] ); - cacheFile.setValue( "time", m_timeCache[type][criteria] ); - cacheFile.endGroup(); + cachedSettings.endArray(); + cachedSettings.setValue( "data", m_dataCache[type][criteria] ); + cachedSettings.setValue( "time", m_timeCache[type][criteria] ); + cachedSettings.endGroup(); + ++criteriaNumber; } m_dirtySet.remove( type ); diff --git a/src/infosystem/infosystemcache.h b/src/infosystem/infosystemcache.h index b74cf8a40..ca347f7bc 100644 --- a/src/infosystem/infosystemcache.h +++ b/src/infosystem/infosystemcache.h @@ -49,8 +49,8 @@ public slots: void updateCacheSlot( Tomahawk::InfoSystem::InfoCacheCriteria criteria, Tomahawk::InfoSystem::InfoType type, QVariant output ); private: - void loadCache( InfoType type, const QString &cache ); - void saveCache( InfoType type, const QString &cache ); + void loadCache( InfoType type, const QString &cacheFile ); + void saveCache( InfoType type, const QString &cacheDir ); QHash< InfoType, QHash< InfoCacheCriteria, QVariant > > m_dataCache; QHash< InfoType, QHash< InfoCacheCriteria, QDateTime > > m_timeCache; From 8690a76df70598af60539bada28649f43b12231b Mon Sep 17 00:00:00 2001 From: Jeff Mitchell Date: Wed, 6 Apr 2011 19:51:58 -0400 Subject: [PATCH 273/329] Fix hang-on-exit in ScanManager. Turns out all it needed was to be explicitly deleted; I guess the parenting wasn't working right, or something? --- include/tomahawk/tomahawkapp.h | 2 ++ src/scanmanager.cpp | 2 +- src/tomahawkapp.cpp | 5 +++-- 3 files changed, 6 insertions(+), 3 deletions(-) diff --git a/include/tomahawk/tomahawkapp.h b/include/tomahawk/tomahawkapp.h index 7775a6528..5919a4496 100644 --- a/include/tomahawk/tomahawkapp.h +++ b/include/tomahawk/tomahawkapp.h @@ -44,6 +44,7 @@ class AudioEngine; class Database; +class ScanManager; class SipHandler; class TomahawkSettings; class XMPPBot; @@ -118,6 +119,7 @@ private: QList m_scriptResolvers; Database* m_database; + ScanManager *m_scanManager; AudioEngine* m_audioEngine; SipHandler* m_sipHandler; Servent* m_servent; diff --git a/src/scanmanager.cpp b/src/scanmanager.cpp index 0073c73b9..dba8ffcc3 100644 --- a/src/scanmanager.cpp +++ b/src/scanmanager.cpp @@ -88,7 +88,7 @@ ScanManager::~ScanManager() QCoreApplication::processEvents( QEventLoop::AllEvents, 200 ); TomahawkUtils::Sleep::msleep( 100 ); } - + if( m_scanner ) { delete m_scanner; diff --git a/src/tomahawkapp.cpp b/src/tomahawkapp.cpp index 95fbfb05c..31630965e 100644 --- a/src/tomahawkapp.cpp +++ b/src/tomahawkapp.cpp @@ -145,6 +145,7 @@ using namespace Tomahawk; TomahawkApp::TomahawkApp( int& argc, char *argv[] ) : TOMAHAWK_APPLICATION( argc, argv ) , m_database( 0 ) + , m_scanManager( 0 ) , m_audioEngine( 0 ) , m_sipHandler( 0 ) , m_servent( 0 ) @@ -173,7 +174,7 @@ TomahawkApp::TomahawkApp( int& argc, char *argv[] ) new TomahawkSettings( this ); m_audioEngine = new AudioEngine; - new ScanManager( this ); + m_scanManager = new ScanManager( this ); new Pipeline( this ); m_servent = new Servent( this ); @@ -294,7 +295,7 @@ TomahawkApp::~TomahawkApp() delete m_sipHandler; delete m_servent; - + delete m_scanManager; #ifndef TOMAHAWK_HEADLESS delete m_mainwindow; delete m_audioEngine; From 83909e53f90a4520b941f953d297789477d18717 Mon Sep 17 00:00:00 2001 From: Jeff Mitchell Date: Wed, 6 Apr 2011 19:58:24 -0400 Subject: [PATCH 274/329] Don't unconditionally scan for changes on startup --- src/scanmanager.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/scanmanager.cpp b/src/scanmanager.cpp index dba8ffcc3..f28cf0f82 100644 --- a/src/scanmanager.cpp +++ b/src/scanmanager.cpp @@ -146,7 +146,7 @@ ScanManager::setInitialPaths( QMap< QString, unsigned int > pathMap ) qDebug() << "Adding " << path << " to watcher"; m_dirWatcher->addPath( path ); } - if( TomahawkSettings::instance()->hasScannerPaths() ) + if( TomahawkSettings::instance()->hasScannerPaths() && TomahawkSettings::instance()->watchForChanges() ) runManualScan( TomahawkSettings::instance()->scannerPaths() ); } From 87c554c6f0fc71d46f2bc001c89897b3d54b9983 Mon Sep 17 00:00:00 2001 From: Christian Muehlhaeuser Date: Thu, 7 Apr 2011 04:47:31 +0200 Subject: [PATCH 275/329] * Fixed TomahawkApp CTOR for secondary instances. --- include/tomahawk/tomahawkapp.h | 11 ++-- src/main.cpp | 16 +++--- src/tomahawkapp.cpp | 94 ++++++++++++++++++---------------- 3 files changed, 66 insertions(+), 55 deletions(-) diff --git a/include/tomahawk/tomahawkapp.h b/include/tomahawk/tomahawkapp.h index 7775a6528..fe8648ad5 100644 --- a/include/tomahawk/tomahawkapp.h +++ b/include/tomahawk/tomahawkapp.h @@ -1,5 +1,5 @@ /* === This file is part of Tomahawk Player - === - * + * * Copyright 2010-2011, Christian Muehlhaeuser * * Tomahawk is free software: you can redistribute it and/or modify @@ -78,6 +78,7 @@ public: TomahawkApp( int& argc, char *argv[] ); virtual ~TomahawkApp(); + void init(); static TomahawkApp* instance(); SipHandler* sipHandler() { return m_sipHandler; } @@ -98,10 +99,10 @@ public: // because QApplication::arguments() is expensive bool scrubFriendlyName() const { return m_scrubFriendlyName; } - + public slots: void instanceStarted( KDSingleApplicationGuard::Instance ); - + private slots: void setupSIP(); @@ -124,14 +125,14 @@ private: XMPPBot* m_xmppBot; Tomahawk::ShortcutHandler* m_shortcutHandler; bool m_scrubFriendlyName; - + #ifdef LIBLASTFM_FOUND Scrobbler* m_scrobbler; #endif #ifndef TOMAHAWK_HEADLESS TomahawkWindow* m_mainwindow; -#endif +#endif bool m_headless; diff --git a/src/main.cpp b/src/main.cpp index 3a6d6936e..f213d74de 100644 --- a/src/main.cpp +++ b/src/main.cpp @@ -1,5 +1,5 @@ /* === This file is part of Tomahawk Player - === - * + * * Copyright 2010-2011, Christian Muehlhaeuser * * Tomahawk is free software: you can redistribute it and/or modify @@ -18,15 +18,15 @@ #include "tomahawk/tomahawkapp.h" +#include "kdsingleapplicationguard/kdsingleapplicationguard.h" + #ifdef Q_WS_MAC #include "tomahawkapp_mac.h" #include static pascal OSErr appleEventHandler( const AppleEvent*, AppleEvent*, long ); #endif -#include - -#include "kdsingleapplicationguard/kdsingleapplicationguard.h" + int main( int argc, char *argv[] ) { @@ -42,11 +42,15 @@ main( int argc, char *argv[] ) TomahawkApp a( argc, argv ); KDSingleApplicationGuard guard( &a, KDSingleApplicationGuard::AutoKillOtherInstances ); - QObject::connect( &guard, SIGNAL( instanceStarted( KDSingleApplicationGuard::Instance ) ), &a, SLOT( instanceStarted( KDSingleApplicationGuard::Instance ) ) ); - + QObject::connect( &guard, SIGNAL( instanceStarted( KDSingleApplicationGuard::Instance ) ), &a, SLOT( instanceStarted( KDSingleApplicationGuard::Instance ) ) ); + + if ( guard.isPrimaryInstance() ) + a.init(); + return a.exec(); } + #ifdef Q_WS_MAC static pascal OSErr appleEventHandler( const AppleEvent* e, AppleEvent*, long ) diff --git a/src/tomahawkapp.cpp b/src/tomahawkapp.cpp index e4198383f..826887587 100644 --- a/src/tomahawkapp.cpp +++ b/src/tomahawkapp.cpp @@ -152,31 +152,53 @@ TomahawkApp::TomahawkApp( int& argc, char *argv[] ) , m_mainwindow( 0 ) , m_infoSystem( 0 ) { - qsrand( QTime( 0, 0, 0 ).secsTo( QTime::currentTime() ) ); - -#ifdef TOMAHAWK_HEADLESS - m_headless = true; -#else - m_mainwindow = 0; - m_headless = arguments().contains( "--headless" ); - setWindowIcon( QIcon( RESPATH "icons/tomahawk-icon-128x128.png" ) ); -#endif - qDebug() << "TomahawkApp thread:" << this->thread(); setOrganizationName( QLatin1String( ORGANIZATION_NAME ) ); setOrganizationDomain( QLatin1String( ORGANIZATION_DOMAIN ) ); setApplicationName( QLatin1String( APPLICATION_NAME ) ); setApplicationVersion( QLatin1String( VERSION ) ); - registerMetaTypes(); setupLogfile(); - +} + + +TomahawkApp::~TomahawkApp() +{ + qDebug() << Q_FUNC_INFO; + + delete m_sipHandler; + delete m_servent; + +#ifndef TOMAHAWK_HEADLESS + delete m_mainwindow; + delete m_audioEngine; +#endif + + delete m_database; +} + + +void +TomahawkApp::init() +{ + qsrand( QTime( 0, 0, 0 ).secsTo( QTime::currentTime() ) ); + + #ifdef TOMAHAWK_HEADLESS + m_headless = true; + #else + m_mainwindow = 0; + m_headless = arguments().contains( "--headless" ); + setWindowIcon( QIcon( RESPATH "icons/tomahawk-icon-128x128.png" ) ); + #endif + + registerMetaTypes(); + Echonest::Config::instance()->setAPIKey( "JRIHWEP6GPOER2QQ6" ); - + new TomahawkSettings( this ); m_audioEngine = new AudioEngine; new ScanManager( this ); new Pipeline( this ); - + m_servent = new Servent( this ); connect( m_servent, SIGNAL( ready() ), SLOT( setupSIP() ) ); @@ -185,15 +207,15 @@ TomahawkApp::TomahawkApp( int& argc, char *argv[] ) qDebug() << "Init Echonest Factory."; GeneratorFactory::registerFactory( "echonest", new EchonestFactory ); - + m_scrubFriendlyName = arguments().contains( "--demo" ); // Register shortcut handler for this platform -#ifdef Q_WS_MAC + #ifdef Q_WS_MAC m_shortcutHandler = new MacShortcutHandler( this ); Tomahawk::setShortcutHandler( static_cast( m_shortcutHandler) ); Tomahawk::setApplicationHandler( this ); -#endif + #endif // Connect up shortcuts if ( m_shortcutHandler ) @@ -208,7 +230,7 @@ TomahawkApp::TomahawkApp( int& argc, char *argv[] ) connect( m_shortcutHandler, SIGNAL( mute() ), m_audioEngine, SLOT( mute() ) ); } -#ifdef LIBLASTFM_FOUND + #ifdef LIBLASTFM_FOUND qDebug() << "Init Scrobbler."; m_scrobbler = new Scrobbler( this ); qDebug() << "Setting NAM."; @@ -225,10 +247,10 @@ TomahawkApp::TomahawkApp( int& argc, char *argv[] ) connect( m_audioEngine, SIGNAL( stopped() ), m_scrobbler, SLOT( trackStopped() ), Qt::QueuedConnection ); -#else + #else qDebug() << "Setting NAM."; TomahawkUtils::setNam( new QNetworkAccessManager ); -#endif + #endif // Set up proxy if( TomahawkSettings::instance()->proxyType() != QNetworkProxy::NoProxy && @@ -258,7 +280,7 @@ TomahawkApp::TomahawkApp( int& argc, char *argv[] ) m_mainwindow->setWindowTitle( "Tomahawk" ); m_mainwindow->show(); } -#endif + #endif qDebug() << "Init Local Collection."; initLocalCollection(); @@ -273,28 +295,12 @@ TomahawkApp::TomahawkApp( int& argc, char *argv[] ) startHTTP(); } -#ifndef TOMAHAWK_HEADLESS + #ifndef TOMAHAWK_HEADLESS if ( !TomahawkSettings::instance()->hasScannerPath() ) { m_mainwindow->showSettingsDialog(); } -#endif -} - - -TomahawkApp::~TomahawkApp() -{ - qDebug() << Q_FUNC_INFO; - - delete m_sipHandler; - delete m_servent; - -#ifndef TOMAHAWK_HEADLESS - delete m_mainwindow; - delete m_audioEngine; -#endif - - delete m_database; + #endif } @@ -332,7 +338,7 @@ TomahawkApp::registerMetaTypes() qRegisterMetaType< QMap >("QMap"); qRegisterMetaType< QMap< QString, plentry_ptr > >("QMap< QString, plentry_ptr >"); qRegisterMetaType< QHash< QString, QMap > >("QHash< QString, QMap >"); - + qRegisterMetaType< GeneratorMode>("GeneratorMode"); qRegisterMetaType("Tomahawk::GeneratorMode"); // Extra definition for namespaced-versions of signals/slots required @@ -525,16 +531,16 @@ TomahawkApp::loadUrl( const QString& url ) } -void +void TomahawkApp::instanceStarted( KDSingleApplicationGuard::Instance instance ) { qDebug() << "INSTANCE STARTED!" << instance.pid << instance.arguments; - - if( instance.arguments.size() < 2 ) + + if( instance.arguments.size() < 2 ) { return; } - + loadUrl( instance.arguments.at( 1 ) ); } From 3b3ff063dc26ed291c6396ba1f2ed141aa2d0eeb Mon Sep 17 00:00:00 2001 From: Christian Muehlhaeuser Date: Thu, 7 Apr 2011 05:07:14 +0200 Subject: [PATCH 276/329] * Updated Changelog. --- ChangeLog | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/ChangeLog b/ChangeLog index 88ee61dd1..75917dc87 100644 --- a/ChangeLog +++ b/ChangeLog @@ -9,9 +9,10 @@ Version 0.0.3: * Don't automatically try to resolve all incoming playback logs. This speeds up importing sources a lot. * Faster painting of playlists with lots of unresolved tracks. - * The tomahawk:// protocol handler works on Windows now. - * Fixed launching Tomahawk from Windows installer with admin privileges. * Prefer local results when results' score is equal. + * (Windows) The tomahawk:// protocol handler works on Windows now. + * (Windows) Fixed launching Tomahawk from Windows installer with admin privileges. + * (Windows) Prevent launching a second instance on Windows. Version 0.0.2: * Don't reconnect to Jabber if the settings dialog is closed successfully @@ -27,7 +28,6 @@ Version 0.0.2: the Tomahawk XMPP presence. * Incompatible change: Twitter SIP protocol has changed slightly. 0.0.1 clients will not be able to talk to newer clients. - * Hopefully fixed crashes during Twitter authentication. * Don't let long playlist or summary names force a large Tomahawk window. * Tomahawk now asks you to authorize new contacts. From c6442e4dfda10a4ca72a4bc489bf3467f0355e34 Mon Sep 17 00:00:00 2001 From: Leo Franchi Date: Wed, 6 Apr 2011 23:15:16 -0400 Subject: [PATCH 277/329] changelog++ --- ChangeLog | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/ChangeLog b/ChangeLog index 75917dc87..2db4e9bd0 100644 --- a/ChangeLog +++ b/ChangeLog @@ -1,4 +1,8 @@ Version 0.0.3: + * Show spinner while resolving playlists. + * Go back to previous page visible when deleting a playlist. + * Fixed issue where automatic playlists and station summaries were not + updated in the playlist header. * Fixed an issue which caused duplicate items when rescanning. * Revert change introduced in 0.0.2 causing Twitter protocol to not try to reconnect to a peer if it couldn't connect the first time the plugin From 196397f210edea4b36b8054fe5421f333deafaff Mon Sep 17 00:00:00 2001 From: Leo Franchi Date: Thu, 7 Apr 2011 09:16:21 -0400 Subject: [PATCH 278/329] fix build --- src/tomahawkapp.cpp | 17 ----------------- 1 file changed, 17 deletions(-) diff --git a/src/tomahawkapp.cpp b/src/tomahawkapp.cpp index d0c444164..e7ae74a45 100644 --- a/src/tomahawkapp.cpp +++ b/src/tomahawkapp.cpp @@ -162,23 +162,6 @@ TomahawkApp::TomahawkApp( int& argc, char *argv[] ) setupLogfile(); } - -TomahawkApp::~TomahawkApp() -{ - qDebug() << Q_FUNC_INFO; - - delete m_sipHandler; - delete m_servent; - -#ifndef TOMAHAWK_HEADLESS - delete m_mainwindow; - delete m_audioEngine; -#endif - - delete m_database; -} - - void TomahawkApp::init() { From db6a93e55c7d7cdbdfc77b5316a83e7e7f47a709 Mon Sep 17 00:00:00 2001 From: Jeff Mitchell Date: Thu, 7 Apr 2011 10:35:29 -0400 Subject: [PATCH 279/329] Now when inserting into the cache a max age time is specified. It can be refreshed by issuing a new max age time when doing a getCachedInfo call (optional). --- include/tomahawk/infosystem.h | 2 +- src/infosystem/infoplugins/lastfmplugin.cpp | 2 +- src/infosystem/infosystemcache.cpp | 53 ++++++++++++++++----- src/infosystem/infosystemcache.h | 7 +-- 4 files changed, 47 insertions(+), 17 deletions(-) diff --git a/include/tomahawk/infosystem.h b/include/tomahawk/infosystem.h index 1409dabf9..e36af20d3 100644 --- a/include/tomahawk/infosystem.h +++ b/include/tomahawk/infosystem.h @@ -112,7 +112,7 @@ public: virtual void getInfo( const QString &caller, const InfoType type, const QVariant &data, InfoCustomData customData ) = 0; signals: - void getCachedInfo( Tomahawk::InfoSystem::InfoCacheCriteria criteria, QString caller, Tomahawk::InfoSystem::InfoType type, QVariant input, Tomahawk::InfoSystem::InfoCustomData customData ); + void getCachedInfo( Tomahawk::InfoSystem::InfoCacheCriteria criteria, qint64 newMaxAge, QString caller, Tomahawk::InfoSystem::InfoType type, QVariant input, Tomahawk::InfoSystem::InfoCustomData customData ); void updateCache( Tomahawk::InfoSystem::InfoCacheCriteria criteria, Tomahawk::InfoSystem::InfoType type, QVariant output ); void info( QString caller, Tomahawk::InfoSystem::InfoType type, QVariant input, QVariant output, Tomahawk::InfoSystem::InfoCustomData customData ); void finished( QString, Tomahawk::InfoSystem::InfoType ); diff --git a/src/infosystem/infoplugins/lastfmplugin.cpp b/src/infosystem/infoplugins/lastfmplugin.cpp index 652d87414..fcff625bf 100644 --- a/src/infosystem/infoplugins/lastfmplugin.cpp +++ b/src/infosystem/infoplugins/lastfmplugin.cpp @@ -178,7 +178,7 @@ LastFmPlugin::fetchCoverArt( const QString &caller, const InfoType type, const Q criteria["artist"] = hash["artist"].toString(); criteria["album"] = hash["album"].toString(); - emit getCachedInfo( criteria, caller, type, data, customData ); + emit getCachedInfo( criteria, 2419200000, caller, type, data, customData ); } void diff --git a/src/infosystem/infosystemcache.cpp b/src/infosystem/infosystemcache.cpp index 7d73c6ade..e6e7b04f5 100644 --- a/src/infosystem/infosystemcache.cpp +++ b/src/infosystem/infosystemcache.cpp @@ -53,10 +53,10 @@ InfoSystemCache::~InfoSystemCache() qDebug() << Q_FUNC_INFO; qDebug() << "Saving infosystemcache to disk"; QString cacheBaseDir = QDesktopServices::storageLocation( QDesktopServices::CacheLocation ); - for( int i = 0; i <= InfoNoInfo; i++ ) + for ( int i = 0; i <= InfoNoInfo; i++ ) { InfoType type = (InfoType)(i); - if( m_dirtySet.contains( type ) && m_dataCache.contains( type ) ) + if ( m_dirtySet.contains( type ) && m_dataCache.contains( type ) ) { QString cacheDir = cacheBaseDir + "/InfoSystemCache/" + QString::number( i ); saveCache( type, cacheDir ); @@ -66,29 +66,54 @@ InfoSystemCache::~InfoSystemCache() void -InfoSystemCache::getCachedInfoSlot( Tomahawk::InfoSystem::InfoCacheCriteria criteria, QString caller, Tomahawk::InfoSystem::InfoType type, QVariant input, Tomahawk::InfoSystem::InfoCustomData customData ) +InfoSystemCache::getCachedInfoSlot( Tomahawk::InfoSystem::InfoCacheCriteria criteria, qint64 newMaxAge, QString caller, Tomahawk::InfoSystem::InfoType type, QVariant input, Tomahawk::InfoSystem::InfoCustomData customData ) { qDebug() << Q_FUNC_INFO; - if( !m_dataCache.contains( type ) || !m_dataCache[type].contains( criteria ) ) + if ( !m_dataCache.contains( type ) || !m_dataCache[type].contains( criteria ) ) { emit notInCache( criteria, caller, type, input, customData ); return; } + QHash< InfoCacheCriteria, QDateTime > typemaxtimecache = m_maxTimeCache[type]; + + if ( typemaxtimecache[criteria].toMSecsSinceEpoch() < QDateTime::currentMSecsSinceEpoch() ) + { + QHash< InfoCacheCriteria, QVariant > typedatacache = m_dataCache[type]; + QHash< InfoCacheCriteria, QDateTime > typeinserttimecache = m_insertTimeCache[type]; + typemaxtimecache.remove( criteria ); + typedatacache.remove( criteria ); + typeinserttimecache.remove( criteria ); + m_dirtySet.insert( type ); + emit notInCache( criteria, caller, type, input, customData ); + return; + } + + if ( newMaxAge > 0 ) + { + QHash< InfoCacheCriteria, QDateTime > typemaxtimecache = m_maxTimeCache[type]; + typemaxtimecache[criteria] = QDateTime::fromMSecsSinceEpoch( QDateTime::currentMSecsSinceEpoch() + newMaxAge ); + m_maxTimeCache[type] = typemaxtimecache; + m_dirtySet.insert( type ); + } + emit info( caller, type, input, m_dataCache[type][criteria], customData ); } void -InfoSystemCache::updateCacheSlot( Tomahawk::InfoSystem::InfoCacheCriteria criteria, Tomahawk::InfoSystem::InfoType type, QVariant output ) +InfoSystemCache::updateCacheSlot( Tomahawk::InfoSystem::InfoCacheCriteria criteria, qint64 maxAge, Tomahawk::InfoSystem::InfoType type, QVariant output ) { qDebug() << Q_FUNC_INFO; QHash< InfoCacheCriteria, QVariant > typedatacache = m_dataCache[type]; - QHash< InfoCacheCriteria, QDateTime > typetimecache = m_timeCache[type]; + QHash< InfoCacheCriteria, QDateTime > typeinserttimecache = m_insertTimeCache[type]; + QHash< InfoCacheCriteria, QDateTime > typemaxtimecache = m_maxTimeCache[type]; typedatacache[criteria] = output; - typetimecache[criteria] = QDateTime::currentDateTimeUtc(); + typeinserttimecache[criteria] = QDateTime::currentDateTimeUtc(); + typemaxtimecache[criteria] = QDateTime::fromMSecsSinceEpoch( QDateTime::currentMSecsSinceEpoch() + maxAge ); m_dataCache[type] = typedatacache; - m_timeCache[type] = typetimecache; + m_insertTimeCache[type] = typeinserttimecache; + m_maxTimeCache[type] = typemaxtimecache; m_dirtySet.insert( type ); } @@ -102,7 +127,8 @@ InfoSystemCache::loadCache( InfoType type, const QString &cacheFile ) foreach( QString group, cachedSettings.childGroups() ) { QHash< InfoCacheCriteria, QVariant > dataHash = m_dataCache[type]; - QHash< InfoCacheCriteria, QDateTime > dateHash = m_timeCache[type]; + QHash< InfoCacheCriteria, QDateTime > insertDateHash = m_insertTimeCache[type]; + QHash< InfoCacheCriteria, QDateTime > maxDateHash = m_maxTimeCache[type]; InfoCacheCriteria criteria; cachedSettings.beginGroup( group ); int numCriteria = cachedSettings.beginReadArray( "criteria" ); @@ -115,10 +141,12 @@ InfoSystemCache::loadCache( InfoType type, const QString &cacheFile ) } cachedSettings.endArray(); dataHash[criteria] = cachedSettings.value( "data" ); - dateHash[criteria] = cachedSettings.value( "time" ).toDateTime(); + insertDateHash[criteria] = cachedSettings.value( "inserttime" ).toDateTime(); + maxDateHash[criteria] = cachedSettings.value( "maxtime" ).toDateTime(); cachedSettings.endGroup(); m_dataCache[type] = dataHash; - m_timeCache[type] = dateHash; + m_insertTimeCache[type] = insertDateHash; + m_maxTimeCache[type] = maxDateHash; } } @@ -156,7 +184,8 @@ InfoSystemCache::saveCache( InfoType type, const QString &cacheDir ) } cachedSettings.endArray(); cachedSettings.setValue( "data", m_dataCache[type][criteria] ); - cachedSettings.setValue( "time", m_timeCache[type][criteria] ); + cachedSettings.setValue( "inserttime", m_insertTimeCache[type][criteria] ); + cachedSettings.setValue( "maxtime", m_maxTimeCache[type][criteria] ); cachedSettings.endGroup(); ++criteriaNumber; } diff --git a/src/infosystem/infosystemcache.h b/src/infosystem/infosystemcache.h index ca347f7bc..0ccad6d19 100644 --- a/src/infosystem/infosystemcache.h +++ b/src/infosystem/infosystemcache.h @@ -45,15 +45,16 @@ signals: void info( QString caller, Tomahawk::InfoSystem::InfoType type, QVariant input, QVariant output, Tomahawk::InfoSystem::InfoCustomData customData ); public slots: - void getCachedInfoSlot( Tomahawk::InfoSystem::InfoCacheCriteria criteria, QString caller, Tomahawk::InfoSystem::InfoType type, QVariant input, Tomahawk::InfoSystem::InfoCustomData customData ); - void updateCacheSlot( Tomahawk::InfoSystem::InfoCacheCriteria criteria, Tomahawk::InfoSystem::InfoType type, QVariant output ); + void getCachedInfoSlot( Tomahawk::InfoSystem::InfoCacheCriteria criteria, qint64 newMaxAge, QString caller, Tomahawk::InfoSystem::InfoType type, QVariant input, Tomahawk::InfoSystem::InfoCustomData customData ); + void updateCacheSlot( Tomahawk::InfoSystem::InfoCacheCriteria criteria, qint64 maxAge, Tomahawk::InfoSystem::InfoType type, QVariant output ); private: void loadCache( InfoType type, const QString &cacheFile ); void saveCache( InfoType type, const QString &cacheDir ); QHash< InfoType, QHash< InfoCacheCriteria, QVariant > > m_dataCache; - QHash< InfoType, QHash< InfoCacheCriteria, QDateTime > > m_timeCache; + QHash< InfoType, QHash< InfoCacheCriteria, QDateTime > > m_insertTimeCache; + QHash< InfoType, QHash< InfoCacheCriteria, QDateTime > > m_maxTimeCache; QSet< InfoType > m_dirtySet; }; From bf744ac8bb1a323103e16950fa3228cca033f6cc Mon Sep 17 00:00:00 2001 From: Jeff Mitchell Date: Thu, 7 Apr 2011 10:41:08 -0400 Subject: [PATCH 280/329] Ah-whoops, knew I forgot something in my previous commit --- src/infosystem/infosystemcache.cpp | 3 +++ 1 file changed, 3 insertions(+) diff --git a/src/infosystem/infosystemcache.cpp b/src/infosystem/infosystemcache.cpp index e6e7b04f5..67bdea896 100644 --- a/src/infosystem/infosystemcache.cpp +++ b/src/infosystem/infosystemcache.cpp @@ -82,8 +82,11 @@ InfoSystemCache::getCachedInfoSlot( Tomahawk::InfoSystem::InfoCacheCriteria crit QHash< InfoCacheCriteria, QVariant > typedatacache = m_dataCache[type]; QHash< InfoCacheCriteria, QDateTime > typeinserttimecache = m_insertTimeCache[type]; typemaxtimecache.remove( criteria ); + m_maxTimeCache[type] = typemaxtimecache; typedatacache.remove( criteria ); + m_dataCache[type] = typedatacache; typeinserttimecache.remove( criteria ); + m_insertTimeCache[type] = typeinserttimecache; m_dirtySet.insert( type ); emit notInCache( criteria, caller, type, input, customData ); return; From a13388f2c4635499a13854c32452bd34351006de Mon Sep 17 00:00:00 2001 From: Jeff Mitchell Date: Thu, 7 Apr 2011 11:19:20 -0400 Subject: [PATCH 281/329] Rejig Jreen/Gloox cmake stuff to report correct status --- CMakeLists.txt | 23 ++++++++++++----------- 1 file changed, 12 insertions(+), 11 deletions(-) diff --git a/CMakeLists.txt b/CMakeLists.txt index 2a4a65c53..c96d97ea9 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -53,31 +53,32 @@ include( CheckTagLibFileName ) check_taglib_filename( COMPLEX_TAGLIB_FILENAME ) # optional -macro_optional_find_package(Jreen) -IF( ENABLE_JREEN AND NOT LIBJREEN_FOUND ) +IF( ENABLE_JREEN ) + macro_optional_find_package(Jreen) + IF( LIBJREEN_FOUND ) + macro_log_feature(JREEN_FOUND "Jreen" "Qt XMPP library" "http://gitorious.org/jreen" FALSE "" "Jreen is needed for the alternative/new Jabber SIP plugin. Built automatically inside Tomahawk, if not installed systemwide and ENABLE_JREEN is true") + ELSE( LIBJREEN_FOUND ) ADD_SUBDIRECTORY( thirdparty/jreen ) SET( LIBJREEN_INCLUDE_DIR ${CMAKE_CURRENT_BINARY_DIR}/thirdparty/jreen/include ) IF( UNIX AND NOT APPLE ) SET( LIBJREEN_LIBRARY ${CMAKE_CURRENT_BINARY_DIR}/thirdparty/jreen/libjreen.so ) ENDIF( UNIX AND NOT APPLE ) IF( WIN32 ) - SET( LIBJREEN_LIBRARY ${CMAKE_CURRENT_BINARY_DIR}/thirdparty/jreen/libjreen.dll ) + SET( LIBJREEN_LIBRARY ${CMAKE_CURRENT_BINARY_DIR}/thirdparty/jreen/libjreen.dll ) ENDIF( WIN32 ) SET( LIBJREEN_FOUND true ) MESSAGE(STATUS "Internal libjreen: ${LIBJREEN_INCLUDE_DIR}, ${LIBJREEN_LIBRARY}") -ENDIF( ENABLE_JREEN AND NOT LIBJREEN_FOUND ) + ENDIF( LIBJREEN_FOUND ) +ELSE( LIBJREEN_FOUND ) + macro_optional_find_package(Gloox 1.0) + macro_log_feature(GLOOX_FOUND "Gloox" "A portable high-level Jabber/XMPP library for C++" "http://camaya.net/gloox" FALSE "" "Gloox is needed for the Jabber SIP plugin and the XMPP-Bot") + +ENDIF( ENABLE_JREEN ) IF( WIN32 ) find_library(QTSPARKLE_LIBRARIES qtsparkle) ENDIF( WIN32 ) -macro_log_feature(JREEN_FOUND "Jreen" "Qt XMPP library" "http://gitorious.org/jreen" FALSE "" "Jreen is needed for the alternative/new Jabber SIP plugin. Built automatically inside Tomahawk, if not installed systemwide and ENABLE_JREEN is true") - -macro_optional_find_package(Gloox 1.0) -IF( ENABLE_JREEN ) - set( GLOOX_FOUND false ) -ENDIF( ENABLE_JREEN) -macro_log_feature(GLOOX_FOUND "Gloox" "A portable high-level Jabber/XMPP library for C++" "http://camaya.net/gloox" FALSE "" "Gloox is needed for the Jabber SIP plugin and the XMPP-Bot") #show dep log macro_display_feature_log() MESSAGE("WARNING!") From 7639d8da9e4cc594337385f2d2beb1c6c8dcfd60 Mon Sep 17 00:00:00 2001 From: Jeff Mitchell Date: Thu, 7 Apr 2011 17:58:45 -0400 Subject: [PATCH 282/329] Happy Birthday, muesli --- src/infosystem/infosystemcache.cpp | 10 ++++++---- 1 file changed, 6 insertions(+), 4 deletions(-) diff --git a/src/infosystem/infosystemcache.cpp b/src/infosystem/infosystemcache.cpp index 67bdea896..9ecdc7435 100644 --- a/src/infosystem/infosystemcache.cpp +++ b/src/infosystem/infosystemcache.cpp @@ -127,19 +127,21 @@ InfoSystemCache::loadCache( InfoType type, const QString &cacheFile ) qDebug() << Q_FUNC_INFO; QSettings cachedSettings( cacheFile, QSettings::IniFormat ); - foreach( QString group, cachedSettings.childGroups() ) + foreach ( QString group, cachedSettings.childGroups() ) { + cachedSettings.beginGroup( group ); + if ( cachedSettings.value( "maxtime" ).toDateTime().toMSecsSinceEpoch() < QDateTime::currentMSecsSinceEpoch() ) + continue; QHash< InfoCacheCriteria, QVariant > dataHash = m_dataCache[type]; QHash< InfoCacheCriteria, QDateTime > insertDateHash = m_insertTimeCache[type]; QHash< InfoCacheCriteria, QDateTime > maxDateHash = m_maxTimeCache[type]; InfoCacheCriteria criteria; - cachedSettings.beginGroup( group ); int numCriteria = cachedSettings.beginReadArray( "criteria" ); - for( int i = 0; i < numCriteria; i++ ) + for ( int i = 0; i < numCriteria; i++ ) { cachedSettings.setArrayIndex( i ); QStringList criteriaValues = cachedSettings.value( QString::number( i ) ).toStringList(); - for( int j = 0; j < criteriaValues.length(); j += 2 ) + for ( int j = 0; j < criteriaValues.length(); j += 2 ) criteria[criteriaValues.at( j )] = criteriaValues.at( j + 1 ); } cachedSettings.endArray(); From c0994e3c248dcd80f9d7282b935501ad271785af Mon Sep 17 00:00:00 2001 From: Christian Muehlhaeuser Date: Sat, 9 Apr 2011 11:03:52 +0200 Subject: [PATCH 283/329] * Fixed crash bug caused by multiple sources going on- and offline at a time. --- ChangeLog | 3 +++ src/libtomahawk/query.cpp | 49 ++++++++++++++++++++++----------------- 2 files changed, 31 insertions(+), 21 deletions(-) diff --git a/ChangeLog b/ChangeLog index 2db4e9bd0..be69b1974 100644 --- a/ChangeLog +++ b/ChangeLog @@ -1,3 +1,6 @@ +Version 0.0.4: + * Fixed a crash situation caused by sources going on- or offline. + Version 0.0.3: * Show spinner while resolving playlists. * Go back to previous page visible when deleting a playlist. diff --git a/src/libtomahawk/query.cpp b/src/libtomahawk/query.cpp index f07025411..413ab4c96 100644 --- a/src/libtomahawk/query.cpp +++ b/src/libtomahawk/query.cpp @@ -1,5 +1,5 @@ /* === This file is part of Tomahawk Player - === - * + * * Copyright 2010-2011, Christian Muehlhaeuser * * Tomahawk is free software: you can redistribute it and/or modify @@ -89,10 +89,13 @@ Query::refreshResults() void Query::onResultStatusChanged() { - if ( m_results.count() ) - qStableSort( m_results.begin(), m_results.end(), Query::resultSorter ); - checkResults(); + { + QMutexLocker lock( &m_mutex ); + if ( m_results.count() ) + qStableSort( m_results.begin(), m_results.end(), Query::resultSorter ); + } + checkResults(); emit resultsChanged(); } @@ -180,27 +183,31 @@ Query::checkResults() { bool becameSolved = false; bool becameUnsolved = true; - m_playable = false; - - // hook up signals, and check solved status - foreach( const result_ptr& rp, m_results ) { - if ( rp->score() > 0.0 && rp->collection().isNull() ) - { - m_playable = true; - } - if ( !rp->collection().isNull() && rp->collection()->source()->isOnline() ) - { - m_playable = true; + QMutexLocker lock( &m_mutex ); - if ( rp->score() > 0.99 ) + m_playable = false; + + // hook up signals, and check solved status + foreach( const result_ptr& rp, m_results ) + { + if ( rp->score() > 0.0 && rp->collection().isNull() ) { - becameUnsolved = false; + m_playable = true; + } + if ( !rp->collection().isNull() && rp->collection()->source()->isOnline() ) + { + m_playable = true; - if ( !m_solved ) + if ( rp->score() > 0.99 ) { - m_solved = true; - becameSolved = true; + becameUnsolved = false; + + if ( !m_solved ) + { + m_solved = true; + becameSolved = true; + } } } } @@ -226,7 +233,7 @@ Query::toVariant() const m.insert( "track", track() ); m.insert( "duration", duration() ); m.insert( "qid", id() ); - + return m; } From 5ae0a7138c8eec388f76257fdd01d96c1b0f18ca Mon Sep 17 00:00:00 2001 From: Alejandro Wainzinger Date: Sun, 10 Apr 2011 02:24:32 -0700 Subject: [PATCH 284/329] Make compile on OS X. (liblastfm2) --- src/libtomahawk/CMakeLists.txt | 1 + 1 file changed, 1 insertion(+) diff --git a/src/libtomahawk/CMakeLists.txt b/src/libtomahawk/CMakeLists.txt index 188f6c1b3..6d2e7dc3b 100644 --- a/src/libtomahawk/CMakeLists.txt +++ b/src/libtomahawk/CMakeLists.txt @@ -329,6 +329,7 @@ include_directories( . ${CMAKE_CURRENT_BINARY_DIR} ${CMAKE_CURRENT_BINARY_DIR}/. ${THIRDPARTY_DIR}/jdns/jdns ${THIRDPARTY_DIR}/jdns/jdnsshared ${THIRDPARTY_DIR}/qtweetlib/qtweetlib/src + ${CMAKE_BINARY_DIR}/thirdparty/liblastfm2/src ) From 252a48196ca598faf9fac1b0804122249159923b Mon Sep 17 00:00:00 2001 From: Alejandro Wainzinger Date: Sun, 10 Apr 2011 16:46:35 -0700 Subject: [PATCH 285/329] Move signal connections of scrobbler into scrobbler, easier to read. --- src/scrobbler.cpp | 14 +++++++++++++- src/tomahawkapp.cpp | 11 ----------- 2 files changed, 13 insertions(+), 12 deletions(-) diff --git a/src/scrobbler.cpp b/src/scrobbler.cpp index ef599db76..bbdb0e26f 100644 --- a/src/scrobbler.cpp +++ b/src/scrobbler.cpp @@ -43,6 +43,18 @@ Scrobbler::Scrobbler( QObject* parent ) SLOT( infoSystemInfo( QString, Tomahawk::InfoSystem::InfoType, QVariant, QVariant, Tomahawk::InfoSystem::InfoCustomData ) ) ); connect( TomahawkApp::instance()->infoSystem(), SIGNAL( finished( QString ) ), SLOT( infoSystemFinished( QString ) ) ); + + connect( AudioEngine::instance(), SIGNAL( started( const Tomahawk::result_ptr& ) ), + SLOT( trackStarted( const Tomahawk::result_ptr& ) ), Qt::QueuedConnection ); + + connect( AudioEngine::instance(), SIGNAL( paused() ), + SLOT( trackPaused() ), Qt::QueuedConnection ); + + connect( AudioEngine::instance(), SIGNAL( resumed() ), + SLOT( trackResumed() ), Qt::QueuedConnection ); + + connect( AudioEngine::instance(), SIGNAL( stopped() ), + SLOT( trackStopped() ), Qt::QueuedConnection ); } @@ -143,4 +155,4 @@ Scrobbler::infoSystemFinished( QString target ) qDebug() << Q_FUNC_INFO; qDebug() << "Scrobbler received done signal from InfoSystem"; } -} \ No newline at end of file +} diff --git a/src/tomahawkapp.cpp b/src/tomahawkapp.cpp index e7ae74a45..11fa147c2 100644 --- a/src/tomahawkapp.cpp +++ b/src/tomahawkapp.cpp @@ -225,17 +225,6 @@ TomahawkApp::init() qDebug() << "Setting NAM."; TomahawkUtils::setNam( new lastfm::NetworkAccessManager( this ) ); - connect( m_audioEngine, SIGNAL( started( const Tomahawk::result_ptr& ) ), - m_scrobbler, SLOT( trackStarted( const Tomahawk::result_ptr& ) ), Qt::QueuedConnection ); - - connect( m_audioEngine, SIGNAL( paused() ), - m_scrobbler, SLOT( trackPaused() ), Qt::QueuedConnection ); - - connect( m_audioEngine, SIGNAL( resumed() ), - m_scrobbler, SLOT( trackResumed() ), Qt::QueuedConnection ); - - connect( m_audioEngine, SIGNAL( stopped() ), - m_scrobbler, SLOT( trackStopped() ), Qt::QueuedConnection ); #else qDebug() << "Setting NAM."; TomahawkUtils::setNam( new QNetworkAccessManager ); From a9a9ffa40fb9e9bb353572b959f17481dcc89d58 Mon Sep 17 00:00:00 2001 From: Leo Franchi Date: Sun, 10 Apr 2011 17:03:16 -0400 Subject: [PATCH 286/329] Changes to make Spotify resolver work --- CMakeLists.txt | 23 ++++--- include/tomahawk/infosystem.h | 2 +- src/audiocontrols.cpp | 22 +++---- src/infosystem/infoplugins/lastfmplugin.cpp | 2 +- src/infosystem/infosystemcache.cpp | 66 +++++--------------- src/infosystem/infosystemcache.h | 7 +-- src/libtomahawk/CMakeLists.txt | 12 ++-- src/libtomahawk/audio/audioengine.cpp | 38 +++++++---- src/libtomahawk/audio/dummytranscode.cpp | 62 ++++++++++++++++++ src/libtomahawk/audio/dummytranscode.h | 63 +++++++++++++++++++ src/libtomahawk/network/servent.cpp | 29 +++++---- src/libtomahawk/network/streamconnection.cpp | 6 +- src/resolvers/scriptresolver.cpp | 9 ++- src/resolvers/scriptresolver.h | 2 +- src/tomahawkapp.cpp | 9 ++- 15 files changed, 233 insertions(+), 119 deletions(-) create mode 100644 src/libtomahawk/audio/dummytranscode.cpp create mode 100644 src/libtomahawk/audio/dummytranscode.h diff --git a/CMakeLists.txt b/CMakeLists.txt index c96d97ea9..2a4a65c53 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -53,32 +53,31 @@ include( CheckTagLibFileName ) check_taglib_filename( COMPLEX_TAGLIB_FILENAME ) # optional -IF( ENABLE_JREEN ) - macro_optional_find_package(Jreen) - IF( LIBJREEN_FOUND ) - macro_log_feature(JREEN_FOUND "Jreen" "Qt XMPP library" "http://gitorious.org/jreen" FALSE "" "Jreen is needed for the alternative/new Jabber SIP plugin. Built automatically inside Tomahawk, if not installed systemwide and ENABLE_JREEN is true") - ELSE( LIBJREEN_FOUND ) +macro_optional_find_package(Jreen) +IF( ENABLE_JREEN AND NOT LIBJREEN_FOUND ) ADD_SUBDIRECTORY( thirdparty/jreen ) SET( LIBJREEN_INCLUDE_DIR ${CMAKE_CURRENT_BINARY_DIR}/thirdparty/jreen/include ) IF( UNIX AND NOT APPLE ) SET( LIBJREEN_LIBRARY ${CMAKE_CURRENT_BINARY_DIR}/thirdparty/jreen/libjreen.so ) ENDIF( UNIX AND NOT APPLE ) IF( WIN32 ) - SET( LIBJREEN_LIBRARY ${CMAKE_CURRENT_BINARY_DIR}/thirdparty/jreen/libjreen.dll ) + SET( LIBJREEN_LIBRARY ${CMAKE_CURRENT_BINARY_DIR}/thirdparty/jreen/libjreen.dll ) ENDIF( WIN32 ) SET( LIBJREEN_FOUND true ) MESSAGE(STATUS "Internal libjreen: ${LIBJREEN_INCLUDE_DIR}, ${LIBJREEN_LIBRARY}") - ENDIF( LIBJREEN_FOUND ) -ELSE( LIBJREEN_FOUND ) - macro_optional_find_package(Gloox 1.0) - macro_log_feature(GLOOX_FOUND "Gloox" "A portable high-level Jabber/XMPP library for C++" "http://camaya.net/gloox" FALSE "" "Gloox is needed for the Jabber SIP plugin and the XMPP-Bot") - -ENDIF( ENABLE_JREEN ) +ENDIF( ENABLE_JREEN AND NOT LIBJREEN_FOUND ) IF( WIN32 ) find_library(QTSPARKLE_LIBRARIES qtsparkle) ENDIF( WIN32 ) +macro_log_feature(JREEN_FOUND "Jreen" "Qt XMPP library" "http://gitorious.org/jreen" FALSE "" "Jreen is needed for the alternative/new Jabber SIP plugin. Built automatically inside Tomahawk, if not installed systemwide and ENABLE_JREEN is true") + +macro_optional_find_package(Gloox 1.0) +IF( ENABLE_JREEN ) + set( GLOOX_FOUND false ) +ENDIF( ENABLE_JREEN) +macro_log_feature(GLOOX_FOUND "Gloox" "A portable high-level Jabber/XMPP library for C++" "http://camaya.net/gloox" FALSE "" "Gloox is needed for the Jabber SIP plugin and the XMPP-Bot") #show dep log macro_display_feature_log() MESSAGE("WARNING!") diff --git a/include/tomahawk/infosystem.h b/include/tomahawk/infosystem.h index e36af20d3..1409dabf9 100644 --- a/include/tomahawk/infosystem.h +++ b/include/tomahawk/infosystem.h @@ -112,7 +112,7 @@ public: virtual void getInfo( const QString &caller, const InfoType type, const QVariant &data, InfoCustomData customData ) = 0; signals: - void getCachedInfo( Tomahawk::InfoSystem::InfoCacheCriteria criteria, qint64 newMaxAge, QString caller, Tomahawk::InfoSystem::InfoType type, QVariant input, Tomahawk::InfoSystem::InfoCustomData customData ); + void getCachedInfo( Tomahawk::InfoSystem::InfoCacheCriteria criteria, QString caller, Tomahawk::InfoSystem::InfoType type, QVariant input, Tomahawk::InfoSystem::InfoCustomData customData ); void updateCache( Tomahawk::InfoSystem::InfoCacheCriteria criteria, Tomahawk::InfoSystem::InfoType type, QVariant output ); void info( QString caller, Tomahawk::InfoSystem::InfoType type, QVariant input, QVariant output, Tomahawk::InfoSystem::InfoCustomData customData ); void finished( QString, Tomahawk::InfoSystem::InfoType ); diff --git a/src/audiocontrols.cpp b/src/audiocontrols.cpp index 8f4214ed8..1138826ee 100644 --- a/src/audiocontrols.cpp +++ b/src/audiocontrols.cpp @@ -45,11 +45,11 @@ AudioControls::AudioControls( QWidget* parent ) QFont font( ui->artistTrackLabel->font() ); font.setPixelSize( 12 ); - + #ifdef Q_WS_MAC font.setPointSize( font.pointSize() - 2 ); #endif - + ui->artistTrackLabel->setFont( font ); ui->artistTrackLabel->setElideMode( Qt::ElideMiddle ); ui->artistTrackLabel->setType( QueryLabel::ArtistAndTrack ); @@ -144,10 +144,10 @@ AudioControls::AudioControls( QWidget* parent ) connect( ui->volumeLowButton, SIGNAL( clicked() ), AudioEngine::instance(), SLOT( lowerVolume() ) ); connect( ui->volumeHighButton, SIGNAL( clicked() ), AudioEngine::instance(), SLOT( raiseVolume() ) ); - + connect( ui->playPauseButton, SIGNAL( clicked() ), this, SIGNAL( playPressed() ) ); connect( ui->pauseButton, SIGNAL( clicked() ), this, SIGNAL( pausePressed() ) ); - + connect( ui->repeatButton, SIGNAL( clicked() ), SLOT( onRepeatClicked() ) ); connect( ui->shuffleButton, SIGNAL( clicked() ), SLOT( onShuffleClicked() ) ); @@ -170,7 +170,7 @@ AudioControls::AudioControls( QWidget* parent ) connect( TomahawkApp::instance()->infoSystem(), SIGNAL( info( QString, Tomahawk::InfoSystem::InfoType, QVariant, QVariant, Tomahawk::InfoSystem::InfoCustomData ) ), SLOT( infoSystemInfo( QString, Tomahawk::InfoSystem::InfoType, QVariant, QVariant, Tomahawk::InfoSystem::InfoCustomData ) ) ); - + connect( TomahawkApp::instance()->infoSystem(), SIGNAL( finished( QString ) ), SLOT( infoSystemFinished( QString ) ) ); onPlaybackStopped(); // initial state @@ -251,9 +251,9 @@ AudioControls::onPlaybackStarted( const Tomahawk::result_ptr& result ) QString artistName = result->artist()->name(); QString albumName = result->album()->name(); - + Tomahawk::InfoSystem::InfoCustomData trackInfo; - + trackInfo["artist"] = QVariant::fromValue< QString >( result->artist()->name() ); trackInfo["album"] = QVariant::fromValue< QString >( result->album()->name() ); TomahawkApp::instance()->infoSystem()->getInfo( @@ -270,7 +270,7 @@ AudioControls::infoSystemInfo( QString caller, Tomahawk::InfoSystem::InfoType ty qDebug() << "info of wrong type or not with our identifier"; return; } - + if ( m_currentTrack.isNull() ) { qDebug() << "Current track is null when trying to apply fetched cover art"; @@ -281,7 +281,7 @@ AudioControls::infoSystemInfo( QString caller, Tomahawk::InfoSystem::InfoType ty { qDebug() << "Cannot convert fetched art from a QByteArray"; return; - } + } Tomahawk::InfoSystem::InfoCustomData returnedData = output.value< Tomahawk::InfoSystem::InfoCustomData >(); const QByteArray ba = returnedData["imgbytes"].toByteArray(); @@ -296,7 +296,7 @@ AudioControls::infoSystemInfo( QString caller, Tomahawk::InfoSystem::InfoType ty ui->coverImage->setPixmap( pm.scaled( ui->coverImage->size(), Qt::IgnoreAspectRatio, Qt::SmoothTransformation ) ); } } - + void AudioControls::infoSystemFinished( QString target ) { @@ -356,7 +356,7 @@ AudioControls::onPlaybackResumed() ui->pauseButton->setVisible( true ); ui->pauseButton->setEnabled( true ); ui->playPauseButton->setVisible( false ); - ui->playPauseButton->setEnabled( false ); + ui->playPauseButton->setEnabled( false ); } diff --git a/src/infosystem/infoplugins/lastfmplugin.cpp b/src/infosystem/infoplugins/lastfmplugin.cpp index fcff625bf..652d87414 100644 --- a/src/infosystem/infoplugins/lastfmplugin.cpp +++ b/src/infosystem/infoplugins/lastfmplugin.cpp @@ -178,7 +178,7 @@ LastFmPlugin::fetchCoverArt( const QString &caller, const InfoType type, const Q criteria["artist"] = hash["artist"].toString(); criteria["album"] = hash["album"].toString(); - emit getCachedInfo( criteria, 2419200000, caller, type, data, customData ); + emit getCachedInfo( criteria, caller, type, data, customData ); } void diff --git a/src/infosystem/infosystemcache.cpp b/src/infosystem/infosystemcache.cpp index 9ecdc7435..7d73c6ade 100644 --- a/src/infosystem/infosystemcache.cpp +++ b/src/infosystem/infosystemcache.cpp @@ -53,10 +53,10 @@ InfoSystemCache::~InfoSystemCache() qDebug() << Q_FUNC_INFO; qDebug() << "Saving infosystemcache to disk"; QString cacheBaseDir = QDesktopServices::storageLocation( QDesktopServices::CacheLocation ); - for ( int i = 0; i <= InfoNoInfo; i++ ) + for( int i = 0; i <= InfoNoInfo; i++ ) { InfoType type = (InfoType)(i); - if ( m_dirtySet.contains( type ) && m_dataCache.contains( type ) ) + if( m_dirtySet.contains( type ) && m_dataCache.contains( type ) ) { QString cacheDir = cacheBaseDir + "/InfoSystemCache/" + QString::number( i ); saveCache( type, cacheDir ); @@ -66,57 +66,29 @@ InfoSystemCache::~InfoSystemCache() void -InfoSystemCache::getCachedInfoSlot( Tomahawk::InfoSystem::InfoCacheCriteria criteria, qint64 newMaxAge, QString caller, Tomahawk::InfoSystem::InfoType type, QVariant input, Tomahawk::InfoSystem::InfoCustomData customData ) +InfoSystemCache::getCachedInfoSlot( Tomahawk::InfoSystem::InfoCacheCriteria criteria, QString caller, Tomahawk::InfoSystem::InfoType type, QVariant input, Tomahawk::InfoSystem::InfoCustomData customData ) { qDebug() << Q_FUNC_INFO; - if ( !m_dataCache.contains( type ) || !m_dataCache[type].contains( criteria ) ) + if( !m_dataCache.contains( type ) || !m_dataCache[type].contains( criteria ) ) { emit notInCache( criteria, caller, type, input, customData ); return; } - QHash< InfoCacheCriteria, QDateTime > typemaxtimecache = m_maxTimeCache[type]; - - if ( typemaxtimecache[criteria].toMSecsSinceEpoch() < QDateTime::currentMSecsSinceEpoch() ) - { - QHash< InfoCacheCriteria, QVariant > typedatacache = m_dataCache[type]; - QHash< InfoCacheCriteria, QDateTime > typeinserttimecache = m_insertTimeCache[type]; - typemaxtimecache.remove( criteria ); - m_maxTimeCache[type] = typemaxtimecache; - typedatacache.remove( criteria ); - m_dataCache[type] = typedatacache; - typeinserttimecache.remove( criteria ); - m_insertTimeCache[type] = typeinserttimecache; - m_dirtySet.insert( type ); - emit notInCache( criteria, caller, type, input, customData ); - return; - } - - if ( newMaxAge > 0 ) - { - QHash< InfoCacheCriteria, QDateTime > typemaxtimecache = m_maxTimeCache[type]; - typemaxtimecache[criteria] = QDateTime::fromMSecsSinceEpoch( QDateTime::currentMSecsSinceEpoch() + newMaxAge ); - m_maxTimeCache[type] = typemaxtimecache; - m_dirtySet.insert( type ); - } - emit info( caller, type, input, m_dataCache[type][criteria], customData ); } void -InfoSystemCache::updateCacheSlot( Tomahawk::InfoSystem::InfoCacheCriteria criteria, qint64 maxAge, Tomahawk::InfoSystem::InfoType type, QVariant output ) +InfoSystemCache::updateCacheSlot( Tomahawk::InfoSystem::InfoCacheCriteria criteria, Tomahawk::InfoSystem::InfoType type, QVariant output ) { qDebug() << Q_FUNC_INFO; QHash< InfoCacheCriteria, QVariant > typedatacache = m_dataCache[type]; - QHash< InfoCacheCriteria, QDateTime > typeinserttimecache = m_insertTimeCache[type]; - QHash< InfoCacheCriteria, QDateTime > typemaxtimecache = m_maxTimeCache[type]; + QHash< InfoCacheCriteria, QDateTime > typetimecache = m_timeCache[type]; typedatacache[criteria] = output; - typeinserttimecache[criteria] = QDateTime::currentDateTimeUtc(); - typemaxtimecache[criteria] = QDateTime::fromMSecsSinceEpoch( QDateTime::currentMSecsSinceEpoch() + maxAge ); + typetimecache[criteria] = QDateTime::currentDateTimeUtc(); m_dataCache[type] = typedatacache; - m_insertTimeCache[type] = typeinserttimecache; - m_maxTimeCache[type] = typemaxtimecache; + m_timeCache[type] = typetimecache; m_dirtySet.insert( type ); } @@ -127,31 +99,26 @@ InfoSystemCache::loadCache( InfoType type, const QString &cacheFile ) qDebug() << Q_FUNC_INFO; QSettings cachedSettings( cacheFile, QSettings::IniFormat ); - foreach ( QString group, cachedSettings.childGroups() ) + foreach( QString group, cachedSettings.childGroups() ) { - cachedSettings.beginGroup( group ); - if ( cachedSettings.value( "maxtime" ).toDateTime().toMSecsSinceEpoch() < QDateTime::currentMSecsSinceEpoch() ) - continue; QHash< InfoCacheCriteria, QVariant > dataHash = m_dataCache[type]; - QHash< InfoCacheCriteria, QDateTime > insertDateHash = m_insertTimeCache[type]; - QHash< InfoCacheCriteria, QDateTime > maxDateHash = m_maxTimeCache[type]; + QHash< InfoCacheCriteria, QDateTime > dateHash = m_timeCache[type]; InfoCacheCriteria criteria; + cachedSettings.beginGroup( group ); int numCriteria = cachedSettings.beginReadArray( "criteria" ); - for ( int i = 0; i < numCriteria; i++ ) + for( int i = 0; i < numCriteria; i++ ) { cachedSettings.setArrayIndex( i ); QStringList criteriaValues = cachedSettings.value( QString::number( i ) ).toStringList(); - for ( int j = 0; j < criteriaValues.length(); j += 2 ) + for( int j = 0; j < criteriaValues.length(); j += 2 ) criteria[criteriaValues.at( j )] = criteriaValues.at( j + 1 ); } cachedSettings.endArray(); dataHash[criteria] = cachedSettings.value( "data" ); - insertDateHash[criteria] = cachedSettings.value( "inserttime" ).toDateTime(); - maxDateHash[criteria] = cachedSettings.value( "maxtime" ).toDateTime(); + dateHash[criteria] = cachedSettings.value( "time" ).toDateTime(); cachedSettings.endGroup(); m_dataCache[type] = dataHash; - m_insertTimeCache[type] = insertDateHash; - m_maxTimeCache[type] = maxDateHash; + m_timeCache[type] = dateHash; } } @@ -189,8 +156,7 @@ InfoSystemCache::saveCache( InfoType type, const QString &cacheDir ) } cachedSettings.endArray(); cachedSettings.setValue( "data", m_dataCache[type][criteria] ); - cachedSettings.setValue( "inserttime", m_insertTimeCache[type][criteria] ); - cachedSettings.setValue( "maxtime", m_maxTimeCache[type][criteria] ); + cachedSettings.setValue( "time", m_timeCache[type][criteria] ); cachedSettings.endGroup(); ++criteriaNumber; } diff --git a/src/infosystem/infosystemcache.h b/src/infosystem/infosystemcache.h index 0ccad6d19..ca347f7bc 100644 --- a/src/infosystem/infosystemcache.h +++ b/src/infosystem/infosystemcache.h @@ -45,16 +45,15 @@ signals: void info( QString caller, Tomahawk::InfoSystem::InfoType type, QVariant input, QVariant output, Tomahawk::InfoSystem::InfoCustomData customData ); public slots: - void getCachedInfoSlot( Tomahawk::InfoSystem::InfoCacheCriteria criteria, qint64 newMaxAge, QString caller, Tomahawk::InfoSystem::InfoType type, QVariant input, Tomahawk::InfoSystem::InfoCustomData customData ); - void updateCacheSlot( Tomahawk::InfoSystem::InfoCacheCriteria criteria, qint64 maxAge, Tomahawk::InfoSystem::InfoType type, QVariant output ); + void getCachedInfoSlot( Tomahawk::InfoSystem::InfoCacheCriteria criteria, QString caller, Tomahawk::InfoSystem::InfoType type, QVariant input, Tomahawk::InfoSystem::InfoCustomData customData ); + void updateCacheSlot( Tomahawk::InfoSystem::InfoCacheCriteria criteria, Tomahawk::InfoSystem::InfoType type, QVariant output ); private: void loadCache( InfoType type, const QString &cacheFile ); void saveCache( InfoType type, const QString &cacheDir ); QHash< InfoType, QHash< InfoCacheCriteria, QVariant > > m_dataCache; - QHash< InfoType, QHash< InfoCacheCriteria, QDateTime > > m_insertTimeCache; - QHash< InfoType, QHash< InfoCacheCriteria, QDateTime > > m_maxTimeCache; + QHash< InfoType, QHash< InfoCacheCriteria, QDateTime > > m_timeCache; QSet< InfoType > m_dirtySet; }; diff --git a/src/libtomahawk/CMakeLists.txt b/src/libtomahawk/CMakeLists.txt index 6d2e7dc3b..cdc92f878 100644 --- a/src/libtomahawk/CMakeLists.txt +++ b/src/libtomahawk/CMakeLists.txt @@ -29,6 +29,7 @@ set( libSources sip/SipPlugin.cpp audio/madtranscode.cpp + audio/dummytranscode.cpp audio/vorbistranscode.cpp audio/flactranscode.cpp audio/audioengine.cpp @@ -96,8 +97,8 @@ set( libSources playlist/albumview.cpp playlist/topbar/topbar.cpp - playlist/topbar/clearbutton.cpp - playlist/topbar/searchlineedit.cpp + playlist/topbar/clearbutton.cpp + playlist/topbar/searchlineedit.cpp playlist/topbar/lineedit.cpp playlist/topbar/searchbutton.cpp @@ -175,6 +176,7 @@ set( libHeaders audio/transcodeinterface.h audio/madtranscode.h + audio/dummytranscode.h audio/vorbistranscode.h audio/flactranscode.h audio/audioengine.h @@ -251,7 +253,7 @@ set( libHeaders playlist/topbar/topbar.h playlist/topbar/clearbutton.h - playlist/topbar/searchlineedit.h + playlist/topbar/searchlineedit.h playlist/topbar/lineedit.h playlist/topbar/lineedit_p.h playlist/topbar/searchbutton.h @@ -297,8 +299,8 @@ set( libHeaders kdsingleapplicationguard/kdlockedsharedmemorypointer.h ) -set( libHeaders_NoMOC - playlist/dynamic/GeneratorInterface.h +set( libHeaders_NoMOC + playlist/dynamic/GeneratorInterface.h ) set( libUI ${libUI} widgets/newplaylistwidget.ui diff --git a/src/libtomahawk/audio/audioengine.cpp b/src/libtomahawk/audio/audioengine.cpp index 07c5c3cc1..e7e931cf7 100644 --- a/src/libtomahawk/audio/audioengine.cpp +++ b/src/libtomahawk/audio/audioengine.cpp @@ -1,5 +1,5 @@ /* === This file is part of Tomahawk Player - === - * + * * Copyright 2010-2011, Christian Muehlhaeuser * * Tomahawk is free software: you can redistribute it and/or modify @@ -28,6 +28,7 @@ #include "network/servent.h" #include "madtranscode.h" +#include "dummytranscode.h" #ifndef NO_OGG #include "vorbistranscode.h" #endif @@ -205,7 +206,9 @@ AudioEngine::loadTrack( const Tomahawk::result_ptr& result ) else { setCurrentTrack( result ); + qDebug() << "Getting IODEVICE, on thread:" << QThread::currentThread() << QThread::currentThreadId(); io = Servent::instance()->getIODeviceForUrl( m_currentTrack ); + qDebug() << "GOT IODEVICE:" << io.data(); if ( m_currentTrack->url().startsWith( "http://" ) ) { m_readReady = false; @@ -228,12 +231,15 @@ AudioEngine::loadTrack( const Tomahawk::result_ptr& result ) qDebug() << "Starting new song from url:" << m_currentTrack->url(); emit loading( m_currentTrack ); + qDebug() << "input is:" << m_input.isNull(); if ( !m_input.isNull() ) { m_input->close(); m_input.clear(); } + if( !m_lastTrack.isNull() ) qDebug() << "LAST TRACK:" << m_lastTrack->mimetype(); + qDebug() << "LOADING SONG:" << m_currentTrack->mimetype(); if ( m_lastTrack.isNull() || ( m_currentTrack->mimetype() != m_lastTrack->mimetype() ) ) { if ( !m_transcode.isNull() ) @@ -241,7 +247,10 @@ AudioEngine::loadTrack( const Tomahawk::result_ptr& result ) m_transcode.clear(); } - if ( m_currentTrack->mimetype() == "audio/mpeg" ) + if ( m_currentTrack->mimetype() == "audio/basic" ) + { + m_transcode = QSharedPointer(new DummyTranscode()); + } else if ( m_currentTrack->mimetype() == "audio/mpeg" ) { m_transcode = QSharedPointer(new MADTranscode()); } @@ -428,8 +437,10 @@ AudioEngine::setCurrentTrack( const Tomahawk::result_ptr& result ) void AudioEngine::onDownloadProgress( qint64 recv, qint64 total ) { - if ( ( recv > 1024 * 32 ) || recv > total ) + if ( ( recv > 1024 * 32 ) || recv > total ) m_readReady = true; + +// qDebug() << "Got onDownloadProgress from reading http stream, received enough?" << m_readReady << "(" << recv << "> 1024 * 32 and" << recv << "<" << total << ")"; } @@ -454,28 +465,30 @@ void AudioEngine::loop() { m_i++; - //if( m_i % 500 == 0 ) qDebug() << Q_FUNC_INFO << thread(); +// if( m_i % 500 == 0 ) qDebug() << Q_FUNC_INFO << thread(); { QMutexLocker lock( &m_mutex ); -/* if ( m_i % 200 == 0 ) - { - if ( !m_input.isNull() ) - qDebug() << "Outer audio loop" << m_input->bytesAvailable() << m_audio->needData(); - }*/ +// if ( m_i % 200 == 0 ) +// { +// if ( !m_input.isNull() ) +// qDebug() << "Outer audio loop" << m_input->bytesAvailable() << m_audio->needData(); +// } if ( m_i % 10 == 0 && m_audio->isPlaying() ) m_audio->triggerTimers(); +// qDebug() << !m_transcode.isNull() << !m_input.isNull() << m_audio->needData() << !m_audio->isPaused(); +// if( !m_input.isNull() ) qDebug() << "INPUT has bytes:" << m_input->bytesAvailable(); if( !m_transcode.isNull() && !m_input.isNull() && m_input->bytesAvailable() && m_audio->needData() && !m_audio->isPaused() ) { - //if ( m_i % 50 == 0 ) - // qDebug() << "Inner audio loop"; +// if ( m_i % 50 == 0 ) +// qDebug() << "Inner audio loop"; if ( m_transcode->needData() > 0 ) { @@ -498,11 +511,12 @@ AudioEngine::loop() // are we cleanly at the end of a track, and ready for the next one? if ( !m_input.isNull() && m_input->atEnd() && - m_readReady && + m_readReady && !m_input->bytesAvailable() && !m_audio->haveData() && !m_audio->isPaused() ) { + qDebug() << !m_input.isNull() << m_input->atEnd() << m_readReady << !m_input->bytesAvailable() << !m_audio->haveData() << !m_audio->isPaused(); qDebug() << "Starting next track then"; loadNextTrack(); // will need data immediately: diff --git a/src/libtomahawk/audio/dummytranscode.cpp b/src/libtomahawk/audio/dummytranscode.cpp new file mode 100644 index 000000000..1ab245d86 --- /dev/null +++ b/src/libtomahawk/audio/dummytranscode.cpp @@ -0,0 +1,62 @@ +/* === This file is part of Tomahawk Player - === + * + * Copyright 2010-2011, Christian Muehlhaeuser + * + * Tomahawk is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * Tomahawk is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with Tomahawk. If not, see . + */ + +#include "dummytranscode.h" + +#include + +DummyTranscode::DummyTranscode() + : m_init( false ) +{ + qDebug() << Q_FUNC_INFO; +} + + +DummyTranscode::~DummyTranscode() +{ + qDebug() << Q_FUNC_INFO; +} + + +void +DummyTranscode::processData( const QByteArray &buffer, bool finish ) +{ + m_buffer.append( buffer ); +// qDebug() << "DUMMYTRANSCODING:" << buffer.size(); + + if( !m_init && m_buffer.size() >= 16364 ) { + m_init = true; + emit streamInitialized( 44100, 2 ); + } +} + + +void +DummyTranscode::onSeek( int seconds ) +{ + m_buffer.clear(); +} + + +void +DummyTranscode::clearBuffers() +{ + m_buffer.clear(); + m_init = false; +} + diff --git a/src/libtomahawk/audio/dummytranscode.h b/src/libtomahawk/audio/dummytranscode.h new file mode 100644 index 000000000..d38774ffd --- /dev/null +++ b/src/libtomahawk/audio/dummytranscode.h @@ -0,0 +1,63 @@ +/* + + Copyright (C) 2011 Leo Franchi + + 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 3 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, see . +*/ + + +#ifndef DUMMYTRANSCODE_H +#define DUMMYTRANSCODE_H + +#include "audio/transcodeinterface.h" +#include "dllmacro.h" + +#define _BUFFER_PREFERRED 32768 + +class DLLEXPORT DummyTranscode : public TranscodeInterface +{ + Q_OBJECT + +public: + DummyTranscode(); + virtual ~DummyTranscode(); + + const QStringList supportedTypes() const { QStringList l; l << "audio/basic"; return l; } + + int needData() { return true; } // always eats data + bool haveData() { return !m_buffer.isEmpty(); } + + unsigned int preferredDataSize() { return _BUFFER_PREFERRED; } + + QByteArray data() { QByteArray b = m_buffer; m_buffer.clear(); return b; } + + virtual void setBufferCapacity( int bytes ) { m_bufferCapacity = bytes; } + int bufferSize() { return m_buffer.size(); } + +public slots: + virtual void clearBuffers(); + virtual void onSeek( int seconds ); + virtual void processData( const QByteArray& data, bool finish ); + +signals: + void streamInitialized( long sampleRate, int channels ); + void timeChanged( int seconds ); + +private: + QByteArray m_buffer; + int m_bufferCapacity; + bool m_init; +}; + +#endif // DUMMYTRANSCODE_H diff --git a/src/libtomahawk/network/servent.cpp b/src/libtomahawk/network/servent.cpp index 083e67d92..4b9a88358 100644 --- a/src/libtomahawk/network/servent.cpp +++ b/src/libtomahawk/network/servent.cpp @@ -1,5 +1,5 @@ /* === This file is part of Tomahawk Player - === - * + * * Copyright 2010-2011, Christian Muehlhaeuser * * Tomahawk is free software: you can redistribute it and/or modify @@ -62,7 +62,7 @@ Servent::Servent( QObject* parent ) , m_portfwd( 0 ) { s_instance = this; - + new ACLSystem( this ); setProxy( QNetworkProxy::NoProxy ); @@ -126,7 +126,7 @@ Servent::startListening( QHostAddress ha, bool upnp, int port ) // --lanhack means to advertise your LAN IP over jabber as if it were externallyVisible qDebug() << "Address mode = " << (int)(TomahawkSettings::instance()->externalAddressMode()); qDebug() << "Static host/port preferred ? = " << ( TomahawkSettings::instance()->preferStaticHostPort() ? "true" : "false" ); - + if( TomahawkSettings::instance()->preferStaticHostPort() ) { qDebug() << "Forcing static preferred host and port"; @@ -136,7 +136,7 @@ Servent::startListening( QHostAddress ha, bool upnp, int port ) emit ready(); return true; } - + switch( TomahawkSettings::instance()->externalAddressMode() ) { case TomahawkSettings::Lan: @@ -318,7 +318,7 @@ Servent::readyRead() pport = m.value( "port" ).toInt(); nodeid = m.value( "nodeid" ).toString(); controlid = m.value( "controlid" ).toString(); - + qDebug() << "Incoming connection details: " << m; if( !nodeid.isEmpty() ) // only control connections send nodeid @@ -367,7 +367,7 @@ Servent::readyRead() { qDebug() << "Invalid or unhandled conntype"; } - + // fallthru to cleanup: closeconnection: qDebug() << "Closing incoming connection, something was wrong."; @@ -562,7 +562,7 @@ Connection* Servent::claimOffer( ControlConnection* cc, const QString &nodeid, const QString &key, const QHostAddress peer ) { qDebug() << Q_FUNC_INFO; - + bool noauth = qApp->arguments().contains( "--noauth" ); // magic key for stream connections: @@ -629,9 +629,9 @@ Servent::claimOffer( ControlConnection* cc, const QString &nodeid, const QString return NULL; } } - + qDebug() << "ACL has allowed the connection"; - + if( conn->onceOnly() ) { m_offers.remove( key ); @@ -672,7 +672,7 @@ Servent::checkACL( const Connection* conn, const QString &nodeid, bool showDialo { if( !showDialog ) return false; - + qDebug() << "ACL for this node not found"; QMessageBox msgBox; msgBox.setIcon( QMessageBox::Question ); @@ -682,7 +682,7 @@ Servent::checkACL( const Connection* conn, const QString &nodeid, bool showDialo QPushButton *alwaysDenyButton = msgBox.addButton( tr( "Always Deny" ), QMessageBox::YesRole ); QPushButton *allowButton = msgBox.addButton( tr( "Allow" ), QMessageBox::NoRole ); QPushButton *alwaysAllowButton = msgBox.addButton( tr( "Always Allow" ), QMessageBox::ActionRole ); - + msgBox.setDefaultButton( denyButton ); msgBox.setEscapeButton( denyButton ); @@ -701,7 +701,7 @@ Servent::checkACL( const Connection* conn, const QString &nodeid, bool showDialo #endif return true; -} +} QSharedPointer Servent::remoteIODeviceFactory( const result_ptr& result ) @@ -832,7 +832,6 @@ Servent::registerIODeviceFactory( const QString &proto, boost::function Servent::getIODeviceForUrl( const Tomahawk::result_ptr& result ) { @@ -845,9 +844,9 @@ Servent::getIODeviceForUrl( const Tomahawk::result_ptr& result ) const QString proto = rx.cap( 1 ); //const QString urlpart = rx.cap( 2 ); + qDebug() << "Getting IODevice for URL:" << proto << m_iofactories.contains( proto ); if ( !m_iofactories.contains( proto ) ) return sp; - return m_iofactories.value( proto )( result ); } @@ -870,5 +869,5 @@ Servent::httpIODeviceFactory( const Tomahawk::result_ptr& result ) qDebug() << Q_FUNC_INFO << result->url(); QNetworkRequest req( result->url() ); QNetworkReply* reply = TomahawkUtils::nam()->get( req ); - return QSharedPointer( reply ); + return QSharedPointer( reply, &QObject::deleteLater ); } diff --git a/src/libtomahawk/network/streamconnection.cpp b/src/libtomahawk/network/streamconnection.cpp index 224ab7d7f..e33ed6196 100644 --- a/src/libtomahawk/network/streamconnection.cpp +++ b/src/libtomahawk/network/streamconnection.cpp @@ -1,5 +1,5 @@ /* === This file is part of Tomahawk Player - === - * + * * Copyright 2010-2011, Christian Muehlhaeuser * * Tomahawk is free software: you can redistribute it and/or modify @@ -46,7 +46,7 @@ StreamConnection::StreamConnection( Servent* s, ControlConnection* cc, QString f qDebug() << Q_FUNC_INFO; BufferIODevice* bio = new BufferIODevice( result->size() ); - m_iodev = QSharedPointer( bio ); // device audio data gets written to + m_iodev = QSharedPointer( bio, &QObject::deleteLater ); // device audio data gets written to m_iodev->open( QIODevice::ReadWrite ); Servent::instance()->registerStreamConnection( this ); @@ -108,7 +108,7 @@ StreamConnection::id() const } -Tomahawk::source_ptr +Tomahawk::source_ptr StreamConnection::source() const { return m_source; diff --git a/src/resolvers/scriptresolver.cpp b/src/resolvers/scriptresolver.cpp index 913588663..f84acf492 100644 --- a/src/resolvers/scriptresolver.cpp +++ b/src/resolvers/scriptresolver.cpp @@ -1,5 +1,5 @@ /* === This file is part of Tomahawk Player - === - * + * * Copyright 2010-2011, Christian Muehlhaeuser * * Tomahawk is free software: you can redistribute it and/or modify @@ -46,6 +46,8 @@ ScriptResolver::ScriptResolver( const QString& exe ) ScriptResolver::~ScriptResolver() { + stop(); + Tomahawk::Pipeline::instance()->removeResolver( this ); } @@ -175,7 +177,7 @@ ScriptResolver::cmdExited( int code, QProcess::ExitStatus status ) qDebug() << Q_FUNC_INFO << "SCRIPT EXITED, code" << code << "status" << status << filePath(); Tomahawk::Pipeline::instance()->removeResolver( this ); - if( m_stopped ) + if( m_stopped ) { qDebug() << "*** Script resolver stopped "; emit finished(); @@ -233,10 +235,11 @@ ScriptResolver::doSetup( const QVariantMap& m ) } -void +void ScriptResolver::stop() { m_stopped = true; + qDebug() << "KILLING PROCESS!"; m_proc.kill(); } diff --git a/src/resolvers/scriptresolver.h b/src/resolvers/scriptresolver.h index dcd5eeed6..60d636352 100644 --- a/src/resolvers/scriptresolver.h +++ b/src/resolvers/scriptresolver.h @@ -1,5 +1,5 @@ /* === This file is part of Tomahawk Player - === - * + * * Copyright 2010-2011, Christian Muehlhaeuser * * Tomahawk is free software: you can redistribute it and/or modify diff --git a/src/tomahawkapp.cpp b/src/tomahawkapp.cpp index 11fa147c2..82e614800 100644 --- a/src/tomahawkapp.cpp +++ b/src/tomahawkapp.cpp @@ -223,7 +223,7 @@ TomahawkApp::init() qDebug() << "Init Scrobbler."; m_scrobbler = new Scrobbler( this ); qDebug() << "Setting NAM."; - TomahawkUtils::setNam( new lastfm::NetworkAccessManager( this ) ); + TomahawkUtils::setNam( lastfm::nam() ); #else qDebug() << "Setting NAM."; @@ -290,6 +290,13 @@ TomahawkApp::~TomahawkApp() { qDebug() << Q_FUNC_INFO; + // stop script resolvers + foreach( Tomahawk::ExternalResolver* r, m_scriptResolvers ) + { + delete r; + } + m_scriptResolvers.clear(); + delete m_sipHandler; delete m_servent; delete m_scanManager; From 41c553fac7ba09ec1f778030f6075bbbefc1c574 Mon Sep 17 00:00:00 2001 From: Leo Franchi Date: Sun, 10 Apr 2011 21:03:21 -0400 Subject: [PATCH 287/329] undo oops --- CMakeLists.txt | 23 +++---- include/tomahawk/infosystem.h | 2 +- src/infosystem/infoplugins/lastfmplugin.cpp | 2 +- src/infosystem/infosystemcache.cpp | 68 +++++++++++++++------ src/infosystem/infosystemcache.h | 7 ++- 5 files changed, 69 insertions(+), 33 deletions(-) diff --git a/CMakeLists.txt b/CMakeLists.txt index 2a4a65c53..c96d97ea9 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -53,31 +53,32 @@ include( CheckTagLibFileName ) check_taglib_filename( COMPLEX_TAGLIB_FILENAME ) # optional -macro_optional_find_package(Jreen) -IF( ENABLE_JREEN AND NOT LIBJREEN_FOUND ) +IF( ENABLE_JREEN ) + macro_optional_find_package(Jreen) + IF( LIBJREEN_FOUND ) + macro_log_feature(JREEN_FOUND "Jreen" "Qt XMPP library" "http://gitorious.org/jreen" FALSE "" "Jreen is needed for the alternative/new Jabber SIP plugin. Built automatically inside Tomahawk, if not installed systemwide and ENABLE_JREEN is true") + ELSE( LIBJREEN_FOUND ) ADD_SUBDIRECTORY( thirdparty/jreen ) SET( LIBJREEN_INCLUDE_DIR ${CMAKE_CURRENT_BINARY_DIR}/thirdparty/jreen/include ) IF( UNIX AND NOT APPLE ) SET( LIBJREEN_LIBRARY ${CMAKE_CURRENT_BINARY_DIR}/thirdparty/jreen/libjreen.so ) ENDIF( UNIX AND NOT APPLE ) IF( WIN32 ) - SET( LIBJREEN_LIBRARY ${CMAKE_CURRENT_BINARY_DIR}/thirdparty/jreen/libjreen.dll ) + SET( LIBJREEN_LIBRARY ${CMAKE_CURRENT_BINARY_DIR}/thirdparty/jreen/libjreen.dll ) ENDIF( WIN32 ) SET( LIBJREEN_FOUND true ) MESSAGE(STATUS "Internal libjreen: ${LIBJREEN_INCLUDE_DIR}, ${LIBJREEN_LIBRARY}") -ENDIF( ENABLE_JREEN AND NOT LIBJREEN_FOUND ) + ENDIF( LIBJREEN_FOUND ) +ELSE( LIBJREEN_FOUND ) + macro_optional_find_package(Gloox 1.0) + macro_log_feature(GLOOX_FOUND "Gloox" "A portable high-level Jabber/XMPP library for C++" "http://camaya.net/gloox" FALSE "" "Gloox is needed for the Jabber SIP plugin and the XMPP-Bot") + +ENDIF( ENABLE_JREEN ) IF( WIN32 ) find_library(QTSPARKLE_LIBRARIES qtsparkle) ENDIF( WIN32 ) -macro_log_feature(JREEN_FOUND "Jreen" "Qt XMPP library" "http://gitorious.org/jreen" FALSE "" "Jreen is needed for the alternative/new Jabber SIP plugin. Built automatically inside Tomahawk, if not installed systemwide and ENABLE_JREEN is true") - -macro_optional_find_package(Gloox 1.0) -IF( ENABLE_JREEN ) - set( GLOOX_FOUND false ) -ENDIF( ENABLE_JREEN) -macro_log_feature(GLOOX_FOUND "Gloox" "A portable high-level Jabber/XMPP library for C++" "http://camaya.net/gloox" FALSE "" "Gloox is needed for the Jabber SIP plugin and the XMPP-Bot") #show dep log macro_display_feature_log() MESSAGE("WARNING!") diff --git a/include/tomahawk/infosystem.h b/include/tomahawk/infosystem.h index 1409dabf9..e36af20d3 100644 --- a/include/tomahawk/infosystem.h +++ b/include/tomahawk/infosystem.h @@ -112,7 +112,7 @@ public: virtual void getInfo( const QString &caller, const InfoType type, const QVariant &data, InfoCustomData customData ) = 0; signals: - void getCachedInfo( Tomahawk::InfoSystem::InfoCacheCriteria criteria, QString caller, Tomahawk::InfoSystem::InfoType type, QVariant input, Tomahawk::InfoSystem::InfoCustomData customData ); + void getCachedInfo( Tomahawk::InfoSystem::InfoCacheCriteria criteria, qint64 newMaxAge, QString caller, Tomahawk::InfoSystem::InfoType type, QVariant input, Tomahawk::InfoSystem::InfoCustomData customData ); void updateCache( Tomahawk::InfoSystem::InfoCacheCriteria criteria, Tomahawk::InfoSystem::InfoType type, QVariant output ); void info( QString caller, Tomahawk::InfoSystem::InfoType type, QVariant input, QVariant output, Tomahawk::InfoSystem::InfoCustomData customData ); void finished( QString, Tomahawk::InfoSystem::InfoType ); diff --git a/src/infosystem/infoplugins/lastfmplugin.cpp b/src/infosystem/infoplugins/lastfmplugin.cpp index 652d87414..fcff625bf 100644 --- a/src/infosystem/infoplugins/lastfmplugin.cpp +++ b/src/infosystem/infoplugins/lastfmplugin.cpp @@ -178,7 +178,7 @@ LastFmPlugin::fetchCoverArt( const QString &caller, const InfoType type, const Q criteria["artist"] = hash["artist"].toString(); criteria["album"] = hash["album"].toString(); - emit getCachedInfo( criteria, caller, type, data, customData ); + emit getCachedInfo( criteria, 2419200000, caller, type, data, customData ); } void diff --git a/src/infosystem/infosystemcache.cpp b/src/infosystem/infosystemcache.cpp index 7d73c6ade..9ecdc7435 100644 --- a/src/infosystem/infosystemcache.cpp +++ b/src/infosystem/infosystemcache.cpp @@ -53,10 +53,10 @@ InfoSystemCache::~InfoSystemCache() qDebug() << Q_FUNC_INFO; qDebug() << "Saving infosystemcache to disk"; QString cacheBaseDir = QDesktopServices::storageLocation( QDesktopServices::CacheLocation ); - for( int i = 0; i <= InfoNoInfo; i++ ) + for ( int i = 0; i <= InfoNoInfo; i++ ) { InfoType type = (InfoType)(i); - if( m_dirtySet.contains( type ) && m_dataCache.contains( type ) ) + if ( m_dirtySet.contains( type ) && m_dataCache.contains( type ) ) { QString cacheDir = cacheBaseDir + "/InfoSystemCache/" + QString::number( i ); saveCache( type, cacheDir ); @@ -66,29 +66,57 @@ InfoSystemCache::~InfoSystemCache() void -InfoSystemCache::getCachedInfoSlot( Tomahawk::InfoSystem::InfoCacheCriteria criteria, QString caller, Tomahawk::InfoSystem::InfoType type, QVariant input, Tomahawk::InfoSystem::InfoCustomData customData ) +InfoSystemCache::getCachedInfoSlot( Tomahawk::InfoSystem::InfoCacheCriteria criteria, qint64 newMaxAge, QString caller, Tomahawk::InfoSystem::InfoType type, QVariant input, Tomahawk::InfoSystem::InfoCustomData customData ) { qDebug() << Q_FUNC_INFO; - if( !m_dataCache.contains( type ) || !m_dataCache[type].contains( criteria ) ) + if ( !m_dataCache.contains( type ) || !m_dataCache[type].contains( criteria ) ) { emit notInCache( criteria, caller, type, input, customData ); return; } + QHash< InfoCacheCriteria, QDateTime > typemaxtimecache = m_maxTimeCache[type]; + + if ( typemaxtimecache[criteria].toMSecsSinceEpoch() < QDateTime::currentMSecsSinceEpoch() ) + { + QHash< InfoCacheCriteria, QVariant > typedatacache = m_dataCache[type]; + QHash< InfoCacheCriteria, QDateTime > typeinserttimecache = m_insertTimeCache[type]; + typemaxtimecache.remove( criteria ); + m_maxTimeCache[type] = typemaxtimecache; + typedatacache.remove( criteria ); + m_dataCache[type] = typedatacache; + typeinserttimecache.remove( criteria ); + m_insertTimeCache[type] = typeinserttimecache; + m_dirtySet.insert( type ); + emit notInCache( criteria, caller, type, input, customData ); + return; + } + + if ( newMaxAge > 0 ) + { + QHash< InfoCacheCriteria, QDateTime > typemaxtimecache = m_maxTimeCache[type]; + typemaxtimecache[criteria] = QDateTime::fromMSecsSinceEpoch( QDateTime::currentMSecsSinceEpoch() + newMaxAge ); + m_maxTimeCache[type] = typemaxtimecache; + m_dirtySet.insert( type ); + } + emit info( caller, type, input, m_dataCache[type][criteria], customData ); } void -InfoSystemCache::updateCacheSlot( Tomahawk::InfoSystem::InfoCacheCriteria criteria, Tomahawk::InfoSystem::InfoType type, QVariant output ) +InfoSystemCache::updateCacheSlot( Tomahawk::InfoSystem::InfoCacheCriteria criteria, qint64 maxAge, Tomahawk::InfoSystem::InfoType type, QVariant output ) { qDebug() << Q_FUNC_INFO; QHash< InfoCacheCriteria, QVariant > typedatacache = m_dataCache[type]; - QHash< InfoCacheCriteria, QDateTime > typetimecache = m_timeCache[type]; + QHash< InfoCacheCriteria, QDateTime > typeinserttimecache = m_insertTimeCache[type]; + QHash< InfoCacheCriteria, QDateTime > typemaxtimecache = m_maxTimeCache[type]; typedatacache[criteria] = output; - typetimecache[criteria] = QDateTime::currentDateTimeUtc(); + typeinserttimecache[criteria] = QDateTime::currentDateTimeUtc(); + typemaxtimecache[criteria] = QDateTime::fromMSecsSinceEpoch( QDateTime::currentMSecsSinceEpoch() + maxAge ); m_dataCache[type] = typedatacache; - m_timeCache[type] = typetimecache; + m_insertTimeCache[type] = typeinserttimecache; + m_maxTimeCache[type] = typemaxtimecache; m_dirtySet.insert( type ); } @@ -99,26 +127,31 @@ InfoSystemCache::loadCache( InfoType type, const QString &cacheFile ) qDebug() << Q_FUNC_INFO; QSettings cachedSettings( cacheFile, QSettings::IniFormat ); - foreach( QString group, cachedSettings.childGroups() ) + foreach ( QString group, cachedSettings.childGroups() ) { - QHash< InfoCacheCriteria, QVariant > dataHash = m_dataCache[type]; - QHash< InfoCacheCriteria, QDateTime > dateHash = m_timeCache[type]; - InfoCacheCriteria criteria; cachedSettings.beginGroup( group ); + if ( cachedSettings.value( "maxtime" ).toDateTime().toMSecsSinceEpoch() < QDateTime::currentMSecsSinceEpoch() ) + continue; + QHash< InfoCacheCriteria, QVariant > dataHash = m_dataCache[type]; + QHash< InfoCacheCriteria, QDateTime > insertDateHash = m_insertTimeCache[type]; + QHash< InfoCacheCriteria, QDateTime > maxDateHash = m_maxTimeCache[type]; + InfoCacheCriteria criteria; int numCriteria = cachedSettings.beginReadArray( "criteria" ); - for( int i = 0; i < numCriteria; i++ ) + for ( int i = 0; i < numCriteria; i++ ) { cachedSettings.setArrayIndex( i ); QStringList criteriaValues = cachedSettings.value( QString::number( i ) ).toStringList(); - for( int j = 0; j < criteriaValues.length(); j += 2 ) + for ( int j = 0; j < criteriaValues.length(); j += 2 ) criteria[criteriaValues.at( j )] = criteriaValues.at( j + 1 ); } cachedSettings.endArray(); dataHash[criteria] = cachedSettings.value( "data" ); - dateHash[criteria] = cachedSettings.value( "time" ).toDateTime(); + insertDateHash[criteria] = cachedSettings.value( "inserttime" ).toDateTime(); + maxDateHash[criteria] = cachedSettings.value( "maxtime" ).toDateTime(); cachedSettings.endGroup(); m_dataCache[type] = dataHash; - m_timeCache[type] = dateHash; + m_insertTimeCache[type] = insertDateHash; + m_maxTimeCache[type] = maxDateHash; } } @@ -156,7 +189,8 @@ InfoSystemCache::saveCache( InfoType type, const QString &cacheDir ) } cachedSettings.endArray(); cachedSettings.setValue( "data", m_dataCache[type][criteria] ); - cachedSettings.setValue( "time", m_timeCache[type][criteria] ); + cachedSettings.setValue( "inserttime", m_insertTimeCache[type][criteria] ); + cachedSettings.setValue( "maxtime", m_maxTimeCache[type][criteria] ); cachedSettings.endGroup(); ++criteriaNumber; } diff --git a/src/infosystem/infosystemcache.h b/src/infosystem/infosystemcache.h index ca347f7bc..0ccad6d19 100644 --- a/src/infosystem/infosystemcache.h +++ b/src/infosystem/infosystemcache.h @@ -45,15 +45,16 @@ signals: void info( QString caller, Tomahawk::InfoSystem::InfoType type, QVariant input, QVariant output, Tomahawk::InfoSystem::InfoCustomData customData ); public slots: - void getCachedInfoSlot( Tomahawk::InfoSystem::InfoCacheCriteria criteria, QString caller, Tomahawk::InfoSystem::InfoType type, QVariant input, Tomahawk::InfoSystem::InfoCustomData customData ); - void updateCacheSlot( Tomahawk::InfoSystem::InfoCacheCriteria criteria, Tomahawk::InfoSystem::InfoType type, QVariant output ); + void getCachedInfoSlot( Tomahawk::InfoSystem::InfoCacheCriteria criteria, qint64 newMaxAge, QString caller, Tomahawk::InfoSystem::InfoType type, QVariant input, Tomahawk::InfoSystem::InfoCustomData customData ); + void updateCacheSlot( Tomahawk::InfoSystem::InfoCacheCriteria criteria, qint64 maxAge, Tomahawk::InfoSystem::InfoType type, QVariant output ); private: void loadCache( InfoType type, const QString &cacheFile ); void saveCache( InfoType type, const QString &cacheDir ); QHash< InfoType, QHash< InfoCacheCriteria, QVariant > > m_dataCache; - QHash< InfoType, QHash< InfoCacheCriteria, QDateTime > > m_timeCache; + QHash< InfoType, QHash< InfoCacheCriteria, QDateTime > > m_insertTimeCache; + QHash< InfoType, QHash< InfoCacheCriteria, QDateTime > > m_maxTimeCache; QSet< InfoType > m_dirtySet; }; From 5235cc9093c6557b02a82b55c43e88dc59b11a5e Mon Sep 17 00:00:00 2001 From: Leo Franchi Date: Sun, 10 Apr 2011 21:04:18 -0400 Subject: [PATCH 288/329] less debug --- src/libtomahawk/audio/audioengine.cpp | 2 -- 1 file changed, 2 deletions(-) diff --git a/src/libtomahawk/audio/audioengine.cpp b/src/libtomahawk/audio/audioengine.cpp index e7e931cf7..8b2987149 100644 --- a/src/libtomahawk/audio/audioengine.cpp +++ b/src/libtomahawk/audio/audioengine.cpp @@ -206,9 +206,7 @@ AudioEngine::loadTrack( const Tomahawk::result_ptr& result ) else { setCurrentTrack( result ); - qDebug() << "Getting IODEVICE, on thread:" << QThread::currentThread() << QThread::currentThreadId(); io = Servent::instance()->getIODeviceForUrl( m_currentTrack ); - qDebug() << "GOT IODEVICE:" << io.data(); if ( m_currentTrack->url().startsWith( "http://" ) ) { m_readReady = false; From 4095f4b5aac8a1f292788d617458ae7cd49177fe Mon Sep 17 00:00:00 2001 From: Leo Franchi Date: Mon, 11 Apr 2011 10:11:40 -0400 Subject: [PATCH 289/329] support loading local .xspfs from files --- src/libtomahawk/utils/xspfloader.cpp | 4 ++-- src/libtomahawk/utils/xspfloader.h | 2 +- src/tomahawkwindow.cpp | 30 +++++++++++++++++----------- 3 files changed, 21 insertions(+), 15 deletions(-) diff --git a/src/libtomahawk/utils/xspfloader.cpp b/src/libtomahawk/utils/xspfloader.cpp index 5b1899300..d8b31d40d 100644 --- a/src/libtomahawk/utils/xspfloader.cpp +++ b/src/libtomahawk/utils/xspfloader.cpp @@ -1,5 +1,5 @@ /* === This file is part of Tomahawk Player - === - * + * * Copyright 2010-2011, Christian Muehlhaeuser * * Tomahawk is free software: you can redistribute it and/or modify @@ -137,7 +137,7 @@ XSPFLoader::gotBody() track = n.text(); } } - + 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 ); diff --git a/src/libtomahawk/utils/xspfloader.h b/src/libtomahawk/utils/xspfloader.h index d541293cf..39e5088f7 100644 --- a/src/libtomahawk/utils/xspfloader.h +++ b/src/libtomahawk/utils/xspfloader.h @@ -1,5 +1,5 @@ /* === This file is part of Tomahawk Player - === - * + * * Copyright 2010-2011, Christian Muehlhaeuser * * Tomahawk is free software: you can redistribute it and/or modify diff --git a/src/tomahawkwindow.cpp b/src/tomahawkwindow.cpp index 15b355d28..08d7dd504 100644 --- a/src/tomahawkwindow.cpp +++ b/src/tomahawkwindow.cpp @@ -1,5 +1,5 @@ /* === This file is part of Tomahawk Player - === - * + * * Copyright 2010-2011, Christian Muehlhaeuser * * Tomahawk is free software: you can redistribute it and/or modify @@ -108,7 +108,7 @@ TomahawkWindow::TomahawkWindow( QWidget* parent ) connect( ui->actionHideOfflineSources, SIGNAL( triggered() ), stv, SLOT( hideOfflineSources() ) ); connect( ui->actionShowOfflineSources, SIGNAL( triggered() ), stv, SLOT( showOfflineSources() ) ); - + sidebar->addWidget( stv ); sidebar->addWidget( transferView ); sidebar->hide( 1, false ); @@ -145,7 +145,7 @@ TomahawkWindow::TomahawkWindow( QWidget* parent ) toolbar->setIconSize( QSize( 32, 32 ) ); toolbar->setToolButtonStyle( Qt::ToolButtonFollowStyle ); toolbar->installEventFilter( new WidgetDragFilter( toolbar ) ); - + #if defined( Q_OS_DARWIN ) && defined( HAVE_SPARKLE ) QAction* checkForUpdates = ui->menu_Help->addAction( tr( "Check For Updates...") ); checkForUpdates->setMenuRole( QAction::ApplicationSpecificRole ); @@ -161,7 +161,7 @@ TomahawkWindow::TomahawkWindow( QWidget* parent ) qtsparkle::Updater* updater = new qtsparkle::Updater( updaterUrl, this ); updater->SetNetworkAccessManager( TomahawkUtils::nam() ); updater->SetVersion( VERSION ); - + ui->menu_Help->addSeparator(); QAction* checkForUpdates = ui->menu_Help->addAction( tr( "Check For Updates...") ); connect( checkForUpdates, SIGNAL( triggered() ), updater, SLOT( CheckNow() ) ); @@ -355,14 +355,14 @@ TomahawkWindow::addPeerManually() } -void +void TomahawkWindow::pluginMenuAdded( QMenu* menu ) { ui->menuNetwork->addMenu( menu ); } -void +void TomahawkWindow::pluginMenuRemoved( QMenu* menu ) { foreach( QAction* action, ui->menuNetwork->actions() ) @@ -384,21 +384,27 @@ TomahawkWindow::loadSpiff() if ( !ok || urlstr.isEmpty() ) return; - QUrl url( urlstr ); - XSPFLoader* loader = new XSPFLoader; - loader->load( url ); + QFileInfo info( urlstr ); + if( info.isFile() ) + { + QFile f( urlstr ); + loader->load( f ); + } else + { + loader->load( QUrl( urlstr ) ); + } } -void +void TomahawkWindow::createAutomaticPlaylist() { bool ok; QString name = QInputDialog::getText( this, tr( "Create New Automatic Playlist" ), tr( "Name:" ), QLineEdit::Normal, tr( "New Automatic Playlist" ), &ok ); if ( !ok || name.isEmpty() ) return; - + source_ptr author = SourceList::instance()->getLocal(); QString id = uuid(); QString info = ""; // FIXME @@ -417,7 +423,7 @@ TomahawkWindow::createStation() QString name = QInputDialog::getText( this, tr( "Create New Station" ), tr( "Name:" ), QLineEdit::Normal, tr( "New Station" ), &ok ); if ( !ok || name.isEmpty() ) return; - + source_ptr author = SourceList::instance()->getLocal(); QString id = uuid(); QString info = ""; // FIXME From c24a95e49f4138802e93aa12c2d0ffbdc71a9376 Mon Sep 17 00:00:00 2001 From: Leo Franchi Date: Mon, 11 Apr 2011 10:11:40 -0400 Subject: [PATCH 290/329] support loading local .xspfs from files (cherry picked from commit 4095f4b5aac8a1f292788d617458ae7cd49177fe) --- src/libtomahawk/utils/xspfloader.cpp | 4 ++-- src/libtomahawk/utils/xspfloader.h | 2 +- src/tomahawkwindow.cpp | 30 +++++++++++++++++----------- 3 files changed, 21 insertions(+), 15 deletions(-) diff --git a/src/libtomahawk/utils/xspfloader.cpp b/src/libtomahawk/utils/xspfloader.cpp index 5b1899300..d8b31d40d 100644 --- a/src/libtomahawk/utils/xspfloader.cpp +++ b/src/libtomahawk/utils/xspfloader.cpp @@ -1,5 +1,5 @@ /* === This file is part of Tomahawk Player - === - * + * * Copyright 2010-2011, Christian Muehlhaeuser * * Tomahawk is free software: you can redistribute it and/or modify @@ -137,7 +137,7 @@ XSPFLoader::gotBody() track = n.text(); } } - + 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 ); diff --git a/src/libtomahawk/utils/xspfloader.h b/src/libtomahawk/utils/xspfloader.h index d541293cf..39e5088f7 100644 --- a/src/libtomahawk/utils/xspfloader.h +++ b/src/libtomahawk/utils/xspfloader.h @@ -1,5 +1,5 @@ /* === This file is part of Tomahawk Player - === - * + * * Copyright 2010-2011, Christian Muehlhaeuser * * Tomahawk is free software: you can redistribute it and/or modify diff --git a/src/tomahawkwindow.cpp b/src/tomahawkwindow.cpp index 868f1609b..97adfb7ca 100644 --- a/src/tomahawkwindow.cpp +++ b/src/tomahawkwindow.cpp @@ -1,5 +1,5 @@ /* === This file is part of Tomahawk Player - === - * + * * Copyright 2010-2011, Christian Muehlhaeuser * * Tomahawk is free software: you can redistribute it and/or modify @@ -108,7 +108,7 @@ TomahawkWindow::TomahawkWindow( QWidget* parent ) connect( ui->actionHideOfflineSources, SIGNAL( triggered() ), stv, SLOT( hideOfflineSources() ) ); connect( ui->actionShowOfflineSources, SIGNAL( triggered() ), stv, SLOT( showOfflineSources() ) ); - + sidebar->addWidget( stv ); sidebar->addWidget( transferView ); sidebar->hide( 1, false ); @@ -145,7 +145,7 @@ TomahawkWindow::TomahawkWindow( QWidget* parent ) toolbar->setIconSize( QSize( 32, 32 ) ); toolbar->setToolButtonStyle( Qt::ToolButtonFollowStyle ); toolbar->installEventFilter( new WidgetDragFilter( toolbar ) ); - + #if defined( Q_OS_DARWIN ) && defined( HAVE_SPARKLE ) QAction* checkForUpdates = ui->menu_Help->addAction( tr( "Check for updates...") ); checkForUpdates->setMenuRole( QAction::ApplicationSpecificRole ); @@ -161,7 +161,7 @@ TomahawkWindow::TomahawkWindow( QWidget* parent ) qtsparkle::Updater* updater = new qtsparkle::Updater( updaterUrl, this ); updater->SetNetworkAccessManager( TomahawkUtils::nam() ); updater->SetVersion( VERSION ); - + ui->menu_Help->addSeparator(); QAction* checkForUpdates = ui->menu_Help->addAction( tr( "Check for updates...") ); connect( checkForUpdates, SIGNAL( triggered() ), updater, SLOT( CheckNow() ) ); @@ -355,14 +355,14 @@ TomahawkWindow::addPeerManually() } -void +void TomahawkWindow::pluginMenuAdded( QMenu* menu ) { ui->menuNetwork->addMenu( menu ); } -void +void TomahawkWindow::pluginMenuRemoved( QMenu* menu ) { foreach( QAction* action, ui->menuNetwork->actions() ) @@ -384,21 +384,27 @@ TomahawkWindow::loadSpiff() if ( !ok || urlstr.isEmpty() ) return; - QUrl url( urlstr ); - XSPFLoader* loader = new XSPFLoader; - loader->load( url ); + QFileInfo info( urlstr ); + if( info.isFile() ) + { + QFile f( urlstr ); + loader->load( f ); + } else + { + loader->load( QUrl( urlstr ) ); + } } -void +void TomahawkWindow::createAutomaticPlaylist() { bool ok; QString name = QInputDialog::getText( this, "Create New Automatic Playlist", "Name:", QLineEdit::Normal, "New Automatic Playlist", &ok ); if ( !ok || name.isEmpty() ) return; - + source_ptr author = SourceList::instance()->getLocal(); QString id = uuid(); QString info = ""; // FIXME @@ -417,7 +423,7 @@ TomahawkWindow::createStation() QString name = QInputDialog::getText( this, "Create New Station", "Name:", QLineEdit::Normal, "New Station", &ok ); if ( !ok || name.isEmpty() ) return; - + source_ptr author = SourceList::instance()->getLocal(); QString id = uuid(); QString info = ""; // FIXME From 1cb3516dc69bd3c9ade3d08751635ba7b4a39950 Mon Sep 17 00:00:00 2001 From: Leo Franchi Date: Mon, 11 Apr 2011 11:01:26 -0400 Subject: [PATCH 291/329] As per domme's suggestion, use ::fromUserInput to make loading xspfs work everywhere --- src/tomahawkapp.cpp | 2 +- src/tomahawkwindow.cpp | 10 +--------- 2 files changed, 2 insertions(+), 10 deletions(-) diff --git a/src/tomahawkapp.cpp b/src/tomahawkapp.cpp index 826887587..4c10d987d 100644 --- a/src/tomahawkapp.cpp +++ b/src/tomahawkapp.cpp @@ -524,7 +524,7 @@ TomahawkApp::loadUrl( const QString& url ) if( f.exists() && info.suffix() == "xspf" ) { XSPFLoader* l = new XSPFLoader( true, this ); qDebug() << "Loading spiff:" << url; - l->load( QUrl( url ) ); + l->load( QUrl::fromUserInput( url ) ); } } return true; diff --git a/src/tomahawkwindow.cpp b/src/tomahawkwindow.cpp index 97adfb7ca..20de04fa1 100644 --- a/src/tomahawkwindow.cpp +++ b/src/tomahawkwindow.cpp @@ -385,15 +385,7 @@ TomahawkWindow::loadSpiff() return; XSPFLoader* loader = new XSPFLoader; - QFileInfo info( urlstr ); - if( info.isFile() ) - { - QFile f( urlstr ); - loader->load( f ); - } else - { - loader->load( QUrl( urlstr ) ); - } + loader->load( QUrl::fromUserInput( urlstr ) ); } From 24d9cce3a8562d694b9cb08da19c19a347a380d5 Mon Sep 17 00:00:00 2001 From: Leo Franchi Date: Mon, 11 Apr 2011 15:36:29 -0400 Subject: [PATCH 292/329] Install a .protocol file on KDE so tomahawk:// links work there too --- CMakeLists.txt | 17 ++++++++++++++--- admin/unix/tomahawk.protocol | 2 +- src/CMakeLists.txt | 12 +++++++++++- thirdparty/alsa-playback/CMakeLists.txt | 2 ++ thirdparty/jdns/CMakeLists.txt | 2 ++ thirdparty/libportfwd/CMakeLists.txt | 2 ++ 6 files changed, 32 insertions(+), 5 deletions(-) diff --git a/CMakeLists.txt b/CMakeLists.txt index dde40af13..b69c77148 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -47,12 +47,22 @@ macro_log_feature(TAGLIB_FOUND "TagLib" "Audio Meta-Data Library" "http://develo # we need pthreads too find_package(Threads) +find_package(KDE4) +IF(KDE4_FOUND) + #KDE4 adds and removes some compiler flags that we don't like + STRING( REPLACE "-std=iso9899:1990" "" CLEAN_C_FLAGS ${CMAKE_C_FLAGS} ) + SET( CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -fexceptions" ) +ELSE() + SET( CLEAN_C_FLAGS ${CMAKE_C_FLAGS} ) +ENDIF() + include( CheckTagLibFileName ) check_taglib_filename( COMPLEX_TAGLIB_FILENAME ) # optional macro_optional_find_package(Jreen) IF( ENABLE_JREEN AND NOT LIBJREEN_FOUND ) + ADD_SUBDIRECTORY( thirdparty/jreen ) SET( LIBJREEN_INCLUDE_DIR ${CMAKE_CURRENT_BINARY_DIR}/thirdparty/jreen/include ) IF( UNIX AND NOT APPLE ) @@ -92,9 +102,10 @@ CONFIGURE_FILE( "${CMAKE_CURRENT_BINARY_DIR}/cmake_uninstall.cmake" IMMEDIATE @ONLY) -ADD_CUSTOM_TARGET(uninstall - "${CMAKE_COMMAND}" -P "${CMAKE_CURRENT_BINARY_DIR}/cmake_uninstall.cmake") - +# KDE4 defines an uninstall target for us automatically +IF( NOT KDE4_FOUND ) + ADD_CUSTOM_TARGET(uninstall "${CMAKE_COMMAND}" -P "${CMAKE_CURRENT_BINARY_DIR}/cmake_uninstall.cmake") +ENDIF() IF( NOT APPLE ) # Make linking as strict on linux as it is on osx. Then we don't break linking on mac so often diff --git a/admin/unix/tomahawk.protocol b/admin/unix/tomahawk.protocol index 3a393aa61..f342fb39c 100644 --- a/admin/unix/tomahawk.protocol +++ b/admin/unix/tomahawk.protocol @@ -1,5 +1,5 @@ [Protocol] -exec=/home/leo/kde/tomahawk/build/tomahawk "%u" +exec=/path/to/binary "%u" protocol=tomahawk input=none output=none diff --git a/src/CMakeLists.txt b/src/CMakeLists.txt index a28f4d97b..8bc02ece4 100644 --- a/src/CMakeLists.txt +++ b/src/CMakeLists.txt @@ -120,7 +120,7 @@ SET( tomahawkUI ${tomahawkUI} ) INCLUDE_DIRECTORIES( - . + . ${TOMAHAWK_INC_DIR} ${CMAKE_CURRENT_BINARY_DIR} @@ -237,4 +237,14 @@ ENDIF( APPLE ) INSTALL( TARGETS tomahawk BUNDLE DESTINATION . RUNTIME DESTINATION bin ) +IF(KDE4_FOUND) #install protocol file + FILE(READ ${CMAKE_SOURCE_DIR}/admin/unix/tomahawk.protocol protocol) + STRING( REPLACE "/path/to/binary" # match this + "${CMAKE_INSTALL_PREFIX}/bin/tomahawk" # this is linux (kde) so pretty safe I think + edited_protocol # save in this variable + "${protocol}" # from the contents of this var + ) + FILE( WRITE ${CMAKE_BINARY_DIR}/tomahawk.protocol "${edited_protocol}" ) + INSTALL( FILES ${CMAKE_BINARY_DIR}/tomahawk.protocol DESTINATION ${SERVICES_INSTALL_DIR} ) +ENDIF() #INCLUDE( "CPack.txt" ) diff --git a/thirdparty/alsa-playback/CMakeLists.txt b/thirdparty/alsa-playback/CMakeLists.txt index 4a4e98d99..b8fe19f7f 100644 --- a/thirdparty/alsa-playback/CMakeLists.txt +++ b/thirdparty/alsa-playback/CMakeLists.txt @@ -12,6 +12,8 @@ SET(CMAKE_RUNTIME_OUTPUT_DIRECTORY "${CMAKE_CURRENT_SOURCE_DIR}") SET(CMAKE_LIBRARY_OUTPUT_DIRECTORY "${CMAKE_CURRENT_SOURCE_DIR}") SET(CMAKE_ARCHIVE_OUTPUT_DIRECTORY "${CMAKE_CURRENT_SOURCE_DIR}") +SET( CMAKE_C_FLAGS ${CLEAN_C_FLAGS} ) + #ADD_DEFINITIONS(-Wall -O2 -DNDEBUG) ADD_DEFINITIONS(-fPIC) diff --git a/thirdparty/jdns/CMakeLists.txt b/thirdparty/jdns/CMakeLists.txt index 85667cb7e..70df58ed4 100644 --- a/thirdparty/jdns/CMakeLists.txt +++ b/thirdparty/jdns/CMakeLists.txt @@ -9,6 +9,8 @@ INCLUDE( ${QT_USE_FILE} ) add_definitions( ${QT_DEFINITIONS} ) add_definitions( -DQT_SHARED ) +SET( CMAKE_C_FLAGS ${CLEAN_C_FLAGS} ) + if(WIN32) set(PLATFORM_SPECIFIC_LIBS "ws2_32.dll" "advapi32.dll" ) endif(WIN32) diff --git a/thirdparty/libportfwd/CMakeLists.txt b/thirdparty/libportfwd/CMakeLists.txt index c301bf95b..e19485414 100644 --- a/thirdparty/libportfwd/CMakeLists.txt +++ b/thirdparty/libportfwd/CMakeLists.txt @@ -17,6 +17,8 @@ ELSE() ENDIF() INCLUDE_DIRECTORIES(${MINIUPNP_DIR} include) +SET( CMAKE_C_FLAGS ${CLEAN_C_FLAGS} ) + ADD_LIBRARY(portfwd STATIC # the needed bits of miniupnpc (no python module, no tests, no cli) From f5f16b6b2693186a79adf141e139362879a570cc Mon Sep 17 00:00:00 2001 From: Leo Franchi Date: Mon, 11 Apr 2011 17:10:31 -0400 Subject: [PATCH 293/329] update the Info.plist version as a part of cmake so we don't forget next time :) --- admin/mac/Info.plist | 4 ++-- src/CMakeLists.osx.txt | 17 ++++++++++------- 2 files changed, 12 insertions(+), 9 deletions(-) diff --git a/admin/mac/Info.plist b/admin/mac/Info.plist index 9acc67a0c..f4fd83c88 100644 --- a/admin/mac/Info.plist +++ b/admin/mac/Info.plist @@ -13,9 +13,9 @@ CFBundlePackageType APPL CFBundleVersion - 0.0.2.0 + TOMAHAWK_VERSION CFBundleShortVersionString - 0.0.2 + TOMAHAWK_VERSION CFBundleSignature tomahawk CFBundleIconFile diff --git a/src/CMakeLists.osx.txt b/src/CMakeLists.osx.txt index 6d3ded108..0488b2f71 100644 --- a/src/CMakeLists.osx.txt +++ b/src/CMakeLists.osx.txt @@ -35,15 +35,18 @@ if (APPLE) # Use two different sparkle update tracks for debug and release # We have to change the URL in the Info.plist file :-/ - IF( NOT CMAKE_BUILD_TYPE STREQUAL "Release" ) - FILE(READ ${CMAKE_SOURCE_DIR}/admin/mac/Info.plist plist) - STRING( REPLACE "http://download.tomahawk-player.org/sparkle" # match this - "http://download.tomahawk-player.org/sparkle-debug" #replace with debug url + FILE(READ ${CMAKE_SOURCE_DIR}/admin/mac/Info.plist plist) + STRING( REPLACE "TOMAHAWK_VERSION" + ${VERSION} edited_plist # save in this variable "${plist}" # from the contents of this var ) - FILE( WRITE ${CMAKE_BINARY_DIR}/Info.plist "${edited_plist}" ) - ELSE() # Just copy the release one - FILE( COPY ${CMAKE_SOURCE_DIR}/admin/mac/Info.plist DESTINATION ${CMAKE_BINARY_DIR} ) + IF( NOT CMAKE_BUILD_TYPE STREQUAL "Release" ) + STRING( REPLACE "http://download.tomahawk-player.org/sparkle" # match this + "http://download.tomahawk-player.org/sparkle-debug" #replace with debug url + edited_plist # save in this variable + "${edited_plist}" # from the contents of this var + ) ENDIF() + FILE( WRITE ${CMAKE_BINARY_DIR}/Info.plist "${edited_plist}" ) endif (APPLE) From 0d6c335e45e3d4b11918656bd8f05fed48631360 Mon Sep 17 00:00:00 2001 From: Jeff Mitchell Date: Tue, 12 Apr 2011 10:53:32 -0400 Subject: [PATCH 294/329] Suppress warning with newer cmake --- CMakeLists.txt | 2 ++ thirdparty/qxt/qxtweb-standalone/CMakeLists.txt | 1 + 2 files changed, 3 insertions(+) diff --git a/CMakeLists.txt b/CMakeLists.txt index 97fa506d8..22af95c72 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -1,6 +1,8 @@ PROJECT( tomahawk ) CMAKE_MINIMUM_REQUIRED( VERSION 2.8 ) +CMAKE_POLICY(SET CMP0017 NEW) + ### ### Tomahawk application info ### diff --git a/thirdparty/qxt/qxtweb-standalone/CMakeLists.txt b/thirdparty/qxt/qxtweb-standalone/CMakeLists.txt index ec81d04cb..c0a453e40 100644 --- a/thirdparty/qxt/qxtweb-standalone/CMakeLists.txt +++ b/thirdparty/qxt/qxtweb-standalone/CMakeLists.txt @@ -2,6 +2,7 @@ PROJECT(libqxtweb-standalone) CMAKE_MINIMUM_REQUIRED(VERSION 2.8 FATAL_ERROR) SET(CMAKE_VERBOSE_MAKEFILE ON) SET(CMAKE_INSTALL_PREFIX ".") +CMAKE_POLICY(SET CMP0017 NEW) FIND_PACKAGE( Qt4 4.6.0 COMPONENTS QtCore QtNetwork REQUIRED ) set(QT_USE_QTNETWORK TRUE) From 3b873dfedac2c36652f9e78c6105d33b608d3ced Mon Sep 17 00:00:00 2001 From: Jeff Mitchell Date: Tue, 12 Apr 2011 11:00:07 -0400 Subject: [PATCH 295/329] Limit policy setting to 2.8.4 or higher --- CMakeLists.txt | 4 +++- thirdparty/qxt/qxtweb-standalone/CMakeLists.txt | 5 ++++- 2 files changed, 7 insertions(+), 2 deletions(-) diff --git a/CMakeLists.txt b/CMakeLists.txt index 22af95c72..e08ee0e3b 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -1,7 +1,9 @@ PROJECT( tomahawk ) CMAKE_MINIMUM_REQUIRED( VERSION 2.8 ) -CMAKE_POLICY(SET CMP0017 NEW) +IF( ${CMAKE_VERSION} VERSION_GREATER 2.8.3 ) + CMAKE_POLICY(SET CMP0017 NEW) +ENDIF( ${CMAKE_VERSION} VERSION_GREATER 2.8.3 ) ### ### Tomahawk application info diff --git a/thirdparty/qxt/qxtweb-standalone/CMakeLists.txt b/thirdparty/qxt/qxtweb-standalone/CMakeLists.txt index c0a453e40..d29e7b989 100644 --- a/thirdparty/qxt/qxtweb-standalone/CMakeLists.txt +++ b/thirdparty/qxt/qxtweb-standalone/CMakeLists.txt @@ -2,7 +2,10 @@ PROJECT(libqxtweb-standalone) CMAKE_MINIMUM_REQUIRED(VERSION 2.8 FATAL_ERROR) SET(CMAKE_VERBOSE_MAKEFILE ON) SET(CMAKE_INSTALL_PREFIX ".") -CMAKE_POLICY(SET CMP0017 NEW) + +IF( ${CMAKE_VERSION} VERSION_GREATER 2.8.3 ) + CMAKE_POLICY(SET CMP0017 NEW) +ENDIF( ${CMAKE_VERSION} VERSION_GREATER 2.8.3 ) FIND_PACKAGE( Qt4 4.6.0 COMPONENTS QtCore QtNetwork REQUIRED ) set(QT_USE_QTNETWORK TRUE) From 0c3ec4a6cf7ec5e3ec450db80089b603449da087 Mon Sep 17 00:00:00 2001 From: Jeff Mitchell Date: Tue, 12 Apr 2011 11:24:54 -0400 Subject: [PATCH 296/329] Fix Jreen compilation when KDE4 is found --- CMakeLists.txt | 3 +++ 1 file changed, 3 insertions(+) diff --git a/CMakeLists.txt b/CMakeLists.txt index e08ee0e3b..71c62fdea 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -71,6 +71,8 @@ IF( ENABLE_JREEN ) IF( LIBJREEN_FOUND ) macro_log_feature(JREEN_FOUND "Jreen" "Qt XMPP library" "http://gitorious.org/jreen" FALSE "" "Jreen is needed for the alternative/new Jabber SIP plugin. Built automatically inside Tomahawk, if not installed systemwide and ENABLE_JREEN is true") ELSE( LIBJREEN_FOUND ) + SET( OLD_C_FLAGS ${CMAKE_C_FLAGS} ) + SET( CMAKE_C_FLAGS ${CLEAN_C_FLAGS} ) ADD_SUBDIRECTORY( thirdparty/jreen ) SET( LIBJREEN_INCLUDE_DIR ${CMAKE_CURRENT_BINARY_DIR}/thirdparty/jreen/include ) IF( UNIX AND NOT APPLE ) @@ -81,6 +83,7 @@ IF( ENABLE_JREEN ) ENDIF( WIN32 ) SET( LIBJREEN_FOUND true ) MESSAGE(STATUS "Internal libjreen: ${LIBJREEN_INCLUDE_DIR}, ${LIBJREEN_LIBRARY}") + SET( CMAKE_C_FLAGS ${OLD_C_FLAGS} ) ENDIF( LIBJREEN_FOUND ) ELSE( LIBJREEN_FOUND ) macro_optional_find_package(Gloox 1.0) From 4a6bc942fa4df746f29438389c1615ecd44df584 Mon Sep 17 00:00:00 2001 From: Jeff Mitchell Date: Tue, 12 Apr 2011 12:21:03 -0400 Subject: [PATCH 297/329] First round of warning cleanups. Leo, please look at the changes in the playlist stuff, make sure I didn't screw anything up --- include/tomahawk/infosystem.h | 5 +++++ src/audiocontrols.cpp | 4 ++++ src/libtomahawk/album.h | 2 +- src/libtomahawk/artist.h | 2 +- src/libtomahawk/database/database.h | 2 +- src/libtomahawk/database/databasecommand.h | 3 ++- .../database/databasecommand_alltracks.cpp | 1 - .../database/databasecommand_loadops.h | 4 +++- .../databasecommand_loadplaylistentries.h | 2 +- .../database/databasecommand_modifyplaylist.cpp | 1 + src/libtomahawk/database/databaseimpl.cpp | 1 + src/libtomahawk/database/databaseworker.cpp | 3 +++ src/libtomahawk/network/remotecollection.cpp | 2 ++ src/libtomahawk/network/servent.cpp | 15 ++++++++++++++- src/libtomahawk/playlist.cpp | 1 + src/libtomahawk/playlist.h | 4 ++-- src/libtomahawk/playlist/albummodel.cpp | 2 ++ src/libtomahawk/playlist/albummodel.h | 4 ++-- src/libtomahawk/playlist/albumproxymodel.cpp | 9 +++++++-- src/libtomahawk/playlist/albumproxymodel.h | 2 +- src/libtomahawk/playlist/albumview.cpp | 9 +++++++-- src/libtomahawk/playlist/albumview.h | 2 +- src/libtomahawk/playlist/collectionflatmodel.h | 2 +- src/libtomahawk/playlist/collectionmodel.h | 4 ++-- src/libtomahawk/playlist/collectionview.cpp | 10 +++++++--- src/libtomahawk/playlist/collectionview.h | 2 +- .../playlist/dynamic/DynamicControl.h | 4 ++-- .../playlist/dynamic/DynamicModel.cpp | 6 +++++- src/libtomahawk/playlist/dynamic/DynamicModel.h | 2 +- .../playlist/dynamic/DynamicPlaylist.h | 4 ++-- .../playlist/dynamic/GeneratorFactory.h | 2 ++ .../playlist/dynamic/GeneratorInterface.h | 4 ++-- .../playlist/dynamic/widgets/DynamicWidget.cpp | 2 ++ .../playlist/playlistitemdelegate.cpp | 3 +++ src/libtomahawk/playlist/playlistmodel.cpp | 1 + src/libtomahawk/playlist/playlistview.cpp | 17 +++++++++++------ src/libtomahawk/playlist/playlistview.h | 2 +- src/libtomahawk/playlist/trackmodel.cpp | 2 ++ src/libtomahawk/playlist/trackmodel.h | 4 ++-- src/libtomahawk/playlist/trackproxymodel.cpp | 14 +++++++++----- src/libtomahawk/playlist/trackproxymodel.h | 2 +- src/libtomahawk/playlist/trackview.cpp | 9 +++++++-- src/libtomahawk/playlist/trackview.h | 4 ++-- src/libtomahawk/source.cpp | 2 ++ src/libtomahawk/tomahawksettings.cpp | 2 +- src/libtomahawk/viewpage.h | 2 ++ src/libtomahawk/widgets/welcomewidget.cpp | 2 ++ src/scrobbler.cpp | 3 +++ src/sourcetree/sourcesmodel.cpp | 1 + src/sourcetree/sourcetreeview.cpp | 1 + src/sourcetree/sourcetreeview.h | 2 +- 51 files changed, 141 insertions(+), 54 deletions(-) diff --git a/include/tomahawk/infosystem.h b/include/tomahawk/infosystem.h index e36af20d3..e1f0017c5 100644 --- a/include/tomahawk/infosystem.h +++ b/include/tomahawk/infosystem.h @@ -121,6 +121,11 @@ public slots: //FIXME: Make pure virtual when everything supports it virtual void notInCacheSlot( Tomahawk::InfoSystem::InfoCacheCriteria criteria, QString caller, Tomahawk::InfoSystem::InfoType type, QVariant input, Tomahawk::InfoSystem::InfoCustomData customData ) { + Q_UNUSED( criteria ); + Q_UNUSED( caller ); + Q_UNUSED( type ); + Q_UNUSED( input ); + Q_UNUSED( customData ); } protected: diff --git a/src/audiocontrols.cpp b/src/audiocontrols.cpp index 1138826ee..e7b0e8739 100644 --- a/src/audiocontrols.cpp +++ b/src/audiocontrols.cpp @@ -264,6 +264,9 @@ AudioControls::onPlaybackStarted( const Tomahawk::result_ptr& result ) void AudioControls::infoSystemInfo( QString caller, Tomahawk::InfoSystem::InfoType type, QVariant input, QVariant output, Tomahawk::InfoSystem::InfoCustomData customData ) { + Q_UNUSED( input ); + Q_UNUSED( customData ); + qDebug() << Q_FUNC_INFO; if ( caller != s_infoIdentifier || type != Tomahawk::InfoSystem::InfoAlbumCoverArt ) { @@ -300,6 +303,7 @@ AudioControls::infoSystemInfo( QString caller, Tomahawk::InfoSystem::InfoType ty void AudioControls::infoSystemFinished( QString target ) { + Q_UNUSED( target ); qDebug() << Q_FUNC_INFO; } diff --git a/src/libtomahawk/album.h b/src/libtomahawk/album.h index df8009b27..a1497f490 100644 --- a/src/libtomahawk/album.h +++ b/src/libtomahawk/album.h @@ -58,7 +58,7 @@ public: virtual void setRepeatMode( PlaylistInterface::RepeatMode ) {} virtual void setShuffled( bool ) {} - virtual void setFilter( const QString& pattern ) {} + virtual void setFilter( const QString& /*pattern*/ ) {} signals: void repeatModeChanged( PlaylistInterface::RepeatMode mode ); diff --git a/src/libtomahawk/artist.h b/src/libtomahawk/artist.h index d7f5ac7ef..200e29c48 100644 --- a/src/libtomahawk/artist.h +++ b/src/libtomahawk/artist.h @@ -58,7 +58,7 @@ public: virtual void setRepeatMode( PlaylistInterface::RepeatMode ) {} virtual void setShuffled( bool ) {} - virtual void setFilter( const QString& pattern ) {} + virtual void setFilter( const QString& /*pattern*/ ) {} signals: void repeatModeChanged( PlaylistInterface::RepeatMode mode ); diff --git a/src/libtomahawk/database/database.h b/src/libtomahawk/database/database.h index a6890fbb6..df02ce2a0 100644 --- a/src/libtomahawk/database/database.h +++ b/src/libtomahawk/database/database.h @@ -49,7 +49,7 @@ public: ~Database(); QString dbid() const; - const bool indexReady() const { return m_indexReady; } + bool indexReady() const { return m_indexReady; } void loadIndex(); diff --git a/src/libtomahawk/database/databasecommand.h b/src/libtomahawk/database/databasecommand.h index f18440ca7..35fff3dfc 100644 --- a/src/libtomahawk/database/databasecommand.h +++ b/src/libtomahawk/database/databasecommand.h @@ -48,6 +48,7 @@ public: explicit DatabaseCommand( const Tomahawk::source_ptr& src, QObject* parent = 0 ); DatabaseCommand( const DatabaseCommand &other ) + : QObject( other.parent() ) { } @@ -59,7 +60,7 @@ public: // if i make this pure virtual, i get compile errors in qmetatype.h. // we need Q_DECLARE_METATYPE to use in queued sig/slot connections. - virtual void exec( DatabaseImpl* lib ) { Q_ASSERT( false ); } + virtual void exec( DatabaseImpl* /*lib*/ ) { Q_ASSERT( false ); } void _exec( DatabaseImpl* lib ); diff --git a/src/libtomahawk/database/databasecommand_alltracks.cpp b/src/libtomahawk/database/databasecommand_alltracks.cpp index 07072db4b..8e2548a3c 100644 --- a/src/libtomahawk/database/databasecommand_alltracks.cpp +++ b/src/libtomahawk/database/databasecommand_alltracks.cpp @@ -77,7 +77,6 @@ DatabaseCommand_AllTracks::exec( DatabaseImpl* dbi ) query.prepare( sql ); query.exec(); - int i = 0; while( query.next() ) { Tomahawk::result_ptr result = Tomahawk::result_ptr( new Tomahawk::Result() ); diff --git a/src/libtomahawk/database/databasecommand_loadops.h b/src/libtomahawk/database/databasecommand_loadops.h index 6a3580f42..06c8cb56f 100644 --- a/src/libtomahawk/database/databasecommand_loadops.h +++ b/src/libtomahawk/database/databasecommand_loadops.h @@ -32,7 +32,9 @@ Q_OBJECT public: explicit DatabaseCommand_loadOps( const Tomahawk::source_ptr& src, QString since, QObject* parent = 0 ) : DatabaseCommand( src ), m_since( since ) - {} + { + Q_UNUSED( parent ); + } virtual void exec( DatabaseImpl* db ); virtual bool doesMutates() const { return false; } diff --git a/src/libtomahawk/database/databasecommand_loadplaylistentries.h b/src/libtomahawk/database/databasecommand_loadplaylistentries.h index ec597ef67..255c9a017 100644 --- a/src/libtomahawk/database/databasecommand_loadplaylistentries.h +++ b/src/libtomahawk/database/databasecommand_loadplaylistentries.h @@ -33,7 +33,7 @@ Q_OBJECT public: explicit DatabaseCommand_LoadPlaylistEntries( QString revision_guid, QObject* parent = 0 ) - : DatabaseCommand( parent ), m_revguid( revision_guid ), m_islatest( true ) + : DatabaseCommand( parent ), m_islatest( true ), m_revguid( revision_guid ) {} virtual void exec( DatabaseImpl* ); diff --git a/src/libtomahawk/database/databasecommand_modifyplaylist.cpp b/src/libtomahawk/database/databasecommand_modifyplaylist.cpp index 640fe8a3c..1d60da687 100644 --- a/src/libtomahawk/database/databasecommand_modifyplaylist.cpp +++ b/src/libtomahawk/database/databasecommand_modifyplaylist.cpp @@ -32,4 +32,5 @@ DatabaseCommand_ModifyPlaylist::DatabaseCommand_ModifyPlaylist( Playlist* playli void DatabaseCommand_ModifyPlaylist::exec( DatabaseImpl* lib ) { + Q_UNUSED( lib ); } diff --git a/src/libtomahawk/database/databaseimpl.cpp b/src/libtomahawk/database/databaseimpl.cpp index ff96c3d7d..85615f00e 100644 --- a/src/libtomahawk/database/databaseimpl.cpp +++ b/src/libtomahawk/database/databaseimpl.cpp @@ -369,6 +369,7 @@ DatabaseImpl::albumId( int artistid, const QString& name_orig, bool& isnew ) QList< int > DatabaseImpl::searchTable( const QString& table, const QString& name, uint limit ) { + Q_UNUSED( limit ); QList< int > results; if( table != "artist" && table != "track" && table != "album" ) return results; diff --git a/src/libtomahawk/database/databaseworker.cpp b/src/libtomahawk/database/databaseworker.cpp index 791888e80..e0d1f7be7 100644 --- a/src/libtomahawk/database/databaseworker.cpp +++ b/src/libtomahawk/database/databaseworker.cpp @@ -32,6 +32,9 @@ DatabaseWorker::DatabaseWorker( DatabaseImpl* lib, Database* db, bool mutates ) , m_abort( false ) , m_outstanding( 0 ) { + Q_UNUSED( db ); + Q_UNUSED( mutates ); + moveToThread( this ); qDebug() << "CTOR DatabaseWorker" << this->thread(); diff --git a/src/libtomahawk/network/remotecollection.cpp b/src/libtomahawk/network/remotecollection.cpp index 8bfdfd3a2..e73966cac 100644 --- a/src/libtomahawk/network/remotecollection.cpp +++ b/src/libtomahawk/network/remotecollection.cpp @@ -31,6 +31,7 @@ RemoteCollection::RemoteCollection( source_ptr source, QObject* parent ) // the database will make us emit the appropriate signals (tracksAdded etc.) void RemoteCollection::addTracks( const QList& newitems ) { + Q_UNUSED( newitems ); qDebug() << Q_FUNC_INFO; Q_ASSERT( false ); } @@ -38,6 +39,7 @@ void RemoteCollection::addTracks( const QList& newitems ) void RemoteCollection::removeTracks( const QDir& dir ) { + Q_UNUSED( dir ); qDebug() << Q_FUNC_INFO; Q_ASSERT( false ); } diff --git a/src/libtomahawk/network/servent.cpp b/src/libtomahawk/network/servent.cpp index 4b9a88358..16f46d428 100644 --- a/src/libtomahawk/network/servent.cpp +++ b/src/libtomahawk/network/servent.cpp @@ -137,7 +137,11 @@ Servent::startListening( QHostAddress ha, bool upnp, int port ) return true; } - switch( TomahawkSettings::instance()->externalAddressMode() ) + TomahawkSettings::ExternalAddressMode mode = TomahawkSettings::instance()->externalAddressMode(); + if ( mode == TomahawkSettings::Upnp && !upnp ) + mode = TomahawkSettings::Lan; + + switch( mode ) { case TomahawkSettings::Lan: foreach( QHostAddress ha, QNetworkInterface::allAddresses() ) @@ -696,7 +700,16 @@ Servent::checkACL( const Connection* conn, const QString &nodeid, bool showDialo return false; } else if( msgBox.clickedButton() == alwaysAllowButton ) + { aclSystem->authorizeUser( nodeid, ACLSystem::Allow ); + return true; + } + else if( msgBox.clickedButton() == allowButton ) + return true; + + //How could we get here? + qDebug() << "Somehow no button matched"; + return false; } #endif diff --git a/src/libtomahawk/playlist.cpp b/src/libtomahawk/playlist.cpp index 9d35c0fe6..bf65556da 100644 --- a/src/libtomahawk/playlist.cpp +++ b/src/libtomahawk/playlist.cpp @@ -431,6 +431,7 @@ Playlist::resolve() void Playlist::onResultsFound( const QList& results ) { + Q_UNUSED( results ); m_locallyChanged = true; } diff --git a/src/libtomahawk/playlist.h b/src/libtomahawk/playlist.h index d0a4ee490..5147a3d9b 100644 --- a/src/libtomahawk/playlist.h +++ b/src/libtomahawk/playlist.h @@ -162,7 +162,7 @@ public: virtual int unfilteredTrackCount() const { return m_entries.count(); } virtual int trackCount() const { return m_entries.count(); } - virtual Tomahawk::result_ptr siblingItem( int itemsAway ) { return result_ptr(); } + virtual Tomahawk::result_ptr siblingItem( int /*itemsAway*/ ) { return result_ptr(); } virtual PlaylistInterface::RepeatMode repeatMode() const { return PlaylistInterface::NoRepeat; } virtual bool shuffled() const { return false; } @@ -170,7 +170,7 @@ public: virtual void setRepeatMode( PlaylistInterface::RepeatMode ) {} virtual void setShuffled( bool ) {} - virtual void setFilter( const QString& pattern ) {} + virtual void setFilter( const QString& /*pattern*/ ) {} signals: /// emitted when the playlist revision changes (whenever the playlist changes) diff --git a/src/libtomahawk/playlist/albummodel.cpp b/src/libtomahawk/playlist/albummodel.cpp index a60610e36..6be94ee1a 100644 --- a/src/libtomahawk/playlist/albummodel.cpp +++ b/src/libtomahawk/playlist/albummodel.cpp @@ -81,6 +81,7 @@ AlbumModel::rowCount( const QModelIndex& parent ) const int AlbumModel::columnCount( const QModelIndex& parent ) const { + Q_UNUSED( parent ); return 1; } @@ -273,6 +274,7 @@ AlbumModel::addFilteredCollection( const collection_ptr& collection, unsigned in void AlbumModel::onAlbumsAdded( const QList& albums, const Tomahawk::collection_ptr& collection ) { + Q_UNUSED( collection ); if ( !albums.count() ) return; diff --git a/src/libtomahawk/playlist/albummodel.h b/src/libtomahawk/playlist/albummodel.h index dc080a84c..fd9361b60 100644 --- a/src/libtomahawk/playlist/albummodel.h +++ b/src/libtomahawk/playlist/albummodel.h @@ -81,8 +81,8 @@ public: } public slots: - virtual void setRepeatMode( PlaylistInterface::RepeatMode mode ) {} - virtual void setShuffled( bool shuffled ) {} + virtual void setRepeatMode( PlaylistInterface::RepeatMode /*mode*/ ) {} + virtual void setShuffled( bool /*shuffled*/ ) {} signals: void repeatModeChanged( PlaylistInterface::RepeatMode mode ); diff --git a/src/libtomahawk/playlist/albumproxymodel.cpp b/src/libtomahawk/playlist/albumproxymodel.cpp index 8a9f66493..97f2130c7 100644 --- a/src/libtomahawk/playlist/albumproxymodel.cpp +++ b/src/libtomahawk/playlist/albumproxymodel.cpp @@ -43,9 +43,13 @@ AlbumProxyModel::AlbumProxyModel( QObject* parent ) void -AlbumProxyModel::setSourceModel( AlbumModel* sourceModel ) +AlbumProxyModel::setSourceModel( QAbstractItemModel* sourceModel ) { - m_model = sourceModel; + AlbumModel* amodel = static_cast< AlbumModel* >( sourceModel ); + if( !amodel ) + return; + else + m_model = amodel; connect( m_model, SIGNAL( trackCountChanged( unsigned int ) ), SIGNAL( sourceTrackCountChanged( unsigned int ) ) ); @@ -141,6 +145,7 @@ AlbumProxyModel::removeIndexes( const QList& indexes ) Tomahawk::result_ptr AlbumProxyModel::siblingItem( int itemsAway ) { + Q_UNUSED( itemsAway ); qDebug() << Q_FUNC_INFO; return Tomahawk::result_ptr( 0 ); } diff --git a/src/libtomahawk/playlist/albumproxymodel.h b/src/libtomahawk/playlist/albumproxymodel.h index 151ba51c8..87535bbb1 100644 --- a/src/libtomahawk/playlist/albumproxymodel.h +++ b/src/libtomahawk/playlist/albumproxymodel.h @@ -34,7 +34,7 @@ public: explicit AlbumProxyModel( QObject* parent = 0 ); virtual AlbumModel* sourceModel() const { return m_model; } - virtual void setSourceModel( AlbumModel* sourceModel ); + virtual void setSourceModel( QAbstractItemModel* sourceModel ); virtual QList tracks() { Q_ASSERT( FALSE ); QList queries; return queries; } diff --git a/src/libtomahawk/playlist/albumview.cpp b/src/libtomahawk/playlist/albumview.cpp index 1f8affea7..4d652d1f4 100644 --- a/src/libtomahawk/playlist/albumview.cpp +++ b/src/libtomahawk/playlist/albumview.cpp @@ -72,9 +72,14 @@ AlbumView::setProxyModel( AlbumProxyModel* model ) void -AlbumView::setModel( AlbumModel* model ) +AlbumView::setModel( QAbstractItemModel* model ) { - m_model = model; + AlbumModel *amodel = static_cast< AlbumModel* >( model ); + + if ( !amodel ) + return; + else + m_model = amodel; if ( m_proxyModel ) { diff --git a/src/libtomahawk/playlist/albumview.h b/src/libtomahawk/playlist/albumview.h index 953863ab2..013cb349a 100644 --- a/src/libtomahawk/playlist/albumview.h +++ b/src/libtomahawk/playlist/albumview.h @@ -42,7 +42,7 @@ public: AlbumProxyModel* proxyModel() const { return m_proxyModel; } // PlaylistItemDelegate* delegate() { return m_delegate; } - void setModel( AlbumModel* model ); + void setModel( QAbstractItemModel* model ); virtual QWidget* widget() { return this; } virtual PlaylistInterface* playlistInterface() const { return proxyModel(); } diff --git a/src/libtomahawk/playlist/collectionflatmodel.h b/src/libtomahawk/playlist/collectionflatmodel.h index fee195376..65e1489a0 100644 --- a/src/libtomahawk/playlist/collectionflatmodel.h +++ b/src/libtomahawk/playlist/collectionflatmodel.h @@ -54,7 +54,7 @@ public: void addFilteredCollection( const Tomahawk::collection_ptr& collection, unsigned int amount, DatabaseCommand_AllTracks::SortOrder order ); - virtual void append( const Tomahawk::query_ptr& query ) {} + virtual void append( const Tomahawk::query_ptr& /*query*/ ) {} signals: void repeatModeChanged( PlaylistInterface::RepeatMode mode ); diff --git a/src/libtomahawk/playlist/collectionmodel.h b/src/libtomahawk/playlist/collectionmodel.h index d6563922f..2ba760743 100644 --- a/src/libtomahawk/playlist/collectionmodel.h +++ b/src/libtomahawk/playlist/collectionmodel.h @@ -59,8 +59,8 @@ public: virtual PlaylistInterface::RepeatMode repeatMode() const { return PlaylistInterface::NoRepeat; } virtual bool shuffled() const { return false; } - virtual void setRepeatMode( PlaylistInterface::RepeatMode mode ) {} - virtual void setShuffled( bool shuffled ) {} + virtual void setRepeatMode( PlaylistInterface::RepeatMode /*mode*/ ) {} + virtual void setShuffled( bool /*shuffled*/ ) {} TrackModelItem* itemFromIndex( const QModelIndex& index ) const; diff --git a/src/libtomahawk/playlist/collectionview.cpp b/src/libtomahawk/playlist/collectionview.cpp index d67b02535..e5a72cd19 100644 --- a/src/libtomahawk/playlist/collectionview.cpp +++ b/src/libtomahawk/playlist/collectionview.cpp @@ -51,12 +51,16 @@ CollectionView::~CollectionView() void -CollectionView::setModel( TrackModel* model ) +CollectionView::setModel( QAbstractItemModel* model ) { - TrackView::setModel( model ); + TrackModel *tmodel = static_cast< TrackModel* >( model ); + if ( !tmodel ) + return; + + TrackView::setModel( tmodel ); setGuid( "collectionview" ); - connect( model, SIGNAL( trackCountChanged( unsigned int ) ), SLOT( onTrackCountChanged( unsigned int ) ) ); + connect( tmodel, SIGNAL( trackCountChanged( unsigned int ) ), SLOT( onTrackCountChanged( unsigned int ) ) ); } diff --git a/src/libtomahawk/playlist/collectionview.h b/src/libtomahawk/playlist/collectionview.h index ea26fc5af..54fa0b47a 100644 --- a/src/libtomahawk/playlist/collectionview.h +++ b/src/libtomahawk/playlist/collectionview.h @@ -36,7 +36,7 @@ public: explicit CollectionView( QWidget* parent = 0 ); ~CollectionView(); - virtual void setModel( TrackModel* model ); + virtual void setModel( QAbstractItemModel* model ); virtual QWidget* widget() { return this; } virtual PlaylistInterface* playlistInterface() const { return proxyModel(); } diff --git a/src/libtomahawk/playlist/dynamic/DynamicControl.h b/src/libtomahawk/playlist/dynamic/DynamicControl.h index 42b62d07d..c03d386fd 100644 --- a/src/libtomahawk/playlist/dynamic/DynamicControl.h +++ b/src/libtomahawk/playlist/dynamic/DynamicControl.h @@ -79,8 +79,8 @@ public: virtual QString summary() const { Q_ASSERT( false ); return QString(); } // used by JSON serialization - virtual void setMatch( const QString& match ) { Q_ASSERT( false ); } - virtual void setInput( const QString& input ) { Q_ASSERT( false ); } + virtual void setMatch( const QString& /*match*/ ) { Q_ASSERT( false ); } + virtual void setInput( const QString& /*input*/ ) { Q_ASSERT( false ); } /// All the potential type selectors for this control QStringList typeSelectors() const { return m_typeSelectors; } diff --git a/src/libtomahawk/playlist/dynamic/DynamicModel.cpp b/src/libtomahawk/playlist/dynamic/DynamicModel.cpp index 43fceeaac..689af3bca 100644 --- a/src/libtomahawk/playlist/dynamic/DynamicModel.cpp +++ b/src/libtomahawk/playlist/dynamic/DynamicModel.cpp @@ -42,8 +42,10 @@ DynamicModel::~DynamicModel() } void -DynamicModel::loadPlaylist( const Tomahawk::dynplaylist_ptr& playlist ) +DynamicModel::loadPlaylist( const Tomahawk::dynplaylist_ptr& playlist, bool loadEntries ) { + Q_UNUSED( loadEntries ); + if( !m_playlist.isNull() ) { disconnect( m_playlist->generator().data(), SIGNAL( nextTrackGenerated( Tomahawk::query_ptr ) ), this, SLOT( newTrackGenerated( Tomahawk::query_ptr ) ) ); } @@ -108,6 +110,8 @@ DynamicModel::changeStation() void DynamicModel::trackResolveFinished( bool success ) { + Q_UNUSED( success ); + Query* q = qobject_cast(sender()); if( !q->playable() ) { diff --git a/src/libtomahawk/playlist/dynamic/DynamicModel.h b/src/libtomahawk/playlist/dynamic/DynamicModel.h index a3cdc5f65..23aa93e68 100644 --- a/src/libtomahawk/playlist/dynamic/DynamicModel.h +++ b/src/libtomahawk/playlist/dynamic/DynamicModel.h @@ -44,7 +44,7 @@ public: virtual QString description() const; - void loadPlaylist( const dynplaylist_ptr& playlist ); + void loadPlaylist( const dynplaylist_ptr& playlist, bool loadEntries = true ); virtual void removeIndex( const QModelIndex& index, bool moreToCome = false ); diff --git a/src/libtomahawk/playlist/dynamic/DynamicPlaylist.h b/src/libtomahawk/playlist/dynamic/DynamicPlaylist.h index bee37900a..04cd2119e 100644 --- a/src/libtomahawk/playlist/dynamic/DynamicPlaylist.h +++ b/src/libtomahawk/playlist/dynamic/DynamicPlaylist.h @@ -105,7 +105,7 @@ public: // maybe friend QObjectHelper and make them private? explicit DynamicPlaylist( const source_ptr& author, const QString& type ); void setMode( int mode ); - void setType( const QString& type ) { /** TODO */; } + void setType( const QString& /*type*/ ) { /** TODO */; } void setGenerator( const geninterface_ptr& gen_ptr ); // @@ -189,4 +189,4 @@ private: }; // namespace -#endif \ No newline at end of file +#endif diff --git a/src/libtomahawk/playlist/dynamic/GeneratorFactory.h b/src/libtomahawk/playlist/dynamic/GeneratorFactory.h index 5687dbb91..56a131cd1 100644 --- a/src/libtomahawk/playlist/dynamic/GeneratorFactory.h +++ b/src/libtomahawk/playlist/dynamic/GeneratorFactory.h @@ -36,6 +36,8 @@ class DLLEXPORT GeneratorFactoryInterface { public: GeneratorFactoryInterface() {} + + virtual ~GeneratorFactoryInterface() {} virtual GeneratorInterface* create() = 0; /** diff --git a/src/libtomahawk/playlist/dynamic/GeneratorInterface.h b/src/libtomahawk/playlist/dynamic/GeneratorInterface.h index b08634104..238c14ff6 100644 --- a/src/libtomahawk/playlist/dynamic/GeneratorInterface.h +++ b/src/libtomahawk/playlist/dynamic/GeneratorInterface.h @@ -70,7 +70,7 @@ public: * Connect to the generated() signal for the results. * */ - virtual void generate( int number = -1 ) {} + virtual void generate( int number = -1 ) { Q_UNUSED( number ); } /** * Starts an on demand session for this generator. Listen to the nextTrack() signal to get @@ -82,7 +82,7 @@ public: * Get the next on demand track. * \param rating Rating from 1-5, -1 for none */ - virtual void fetchNext( int rating = -1 ) {} + virtual void fetchNext( int rating = -1 ) { Q_UNUSED( rating ) } /** * Return a sentence that describes this generator's controls. TODO english only ATM diff --git a/src/libtomahawk/playlist/dynamic/widgets/DynamicWidget.cpp b/src/libtomahawk/playlist/dynamic/widgets/DynamicWidget.cpp index bd76b5683..0aa65d3bc 100644 --- a/src/libtomahawk/playlist/dynamic/widgets/DynamicWidget.cpp +++ b/src/libtomahawk/playlist/dynamic/widgets/DynamicWidget.cpp @@ -171,6 +171,7 @@ DynamicWidget::loadDynamicPlaylist( const Tomahawk::dynplaylist_ptr& playlist ) void DynamicWidget::onRevisionLoaded( const Tomahawk::DynamicPlaylistRevision& rev ) { + Q_UNUSED( rev ); qDebug() << "DynamicWidget::onRevisionLoaded"; loadDynamicPlaylist( m_playlist ); if( m_resolveOnNextLoad || !m_playlist->author()->isLocal() ) @@ -354,6 +355,7 @@ DynamicWidget::controlsChanged() void DynamicWidget::controlChanged( const Tomahawk::dyncontrol_ptr& control ) { + Q_UNUSED( control ); if( !m_playlist->author()->isLocal() ) return; m_playlist->createNewRevision(); diff --git a/src/libtomahawk/playlist/playlistitemdelegate.cpp b/src/libtomahawk/playlist/playlistitemdelegate.cpp index 8e9adb3a7..174001fc7 100644 --- a/src/libtomahawk/playlist/playlistitemdelegate.cpp +++ b/src/libtomahawk/playlist/playlistitemdelegate.cpp @@ -61,6 +61,9 @@ PlaylistItemDelegate::sizeHint( const QStyleOptionViewItem& option, const QModel QWidget* PlaylistItemDelegate::createEditor( QWidget* parent, const QStyleOptionViewItem& option, const QModelIndex& index ) const { + Q_UNUSED( parent ); + Q_UNUSED( option ); + Q_UNUSED( index ); return 0; } diff --git a/src/libtomahawk/playlist/playlistmodel.cpp b/src/libtomahawk/playlist/playlistmodel.cpp index 22e863457..c171f2cb1 100644 --- a/src/libtomahawk/playlist/playlistmodel.cpp +++ b/src/libtomahawk/playlist/playlistmodel.cpp @@ -298,6 +298,7 @@ PlaylistModel::onRevisionLoaded( Tomahawk::PlaylistRevision revision ) bool PlaylistModel::dropMimeData( const QMimeData* data, Qt::DropAction action, int row, int column, const QModelIndex& parent ) { + Q_UNUSED( column ); if ( action == Qt::IgnoreAction || isReadOnly() ) return true; diff --git a/src/libtomahawk/playlist/playlistview.cpp b/src/libtomahawk/playlist/playlistview.cpp index 79026e872..e6a06ea0c 100644 --- a/src/libtomahawk/playlist/playlistview.cpp +++ b/src/libtomahawk/playlist/playlistview.cpp @@ -45,20 +45,25 @@ PlaylistView::~PlaylistView() void -PlaylistView::setModel( PlaylistModel* model ) +PlaylistView::setModel( QAbstractItemModel* model ) { - m_model = model; + PlaylistModel* pmodel = static_cast< PlaylistModel* >( model ); + + if ( !pmodel ) + return; + else + m_model = pmodel; TrackView::setModel( model ); setColumnHidden( 5, true ); // Hide age column per default - if ( !model->playlist().isNull() ) - setGuid( QString( "playlistview/%1" ).arg( model->playlist()->guid() ) ); + if ( !m_model->playlist().isNull() ) + setGuid( QString( "playlistview/%1" ).arg( m_model->playlist()->guid() ) ); else setGuid( "playlistview" ); - connect( model, SIGNAL( trackCountChanged( unsigned int ) ), SLOT( onTrackCountChanged( unsigned int ) ) ); - connect( model, SIGNAL( playlistDeleted() ), SLOT( onDeleted() ) ); + connect( m_model, SIGNAL( trackCountChanged( unsigned int ) ), SLOT( onTrackCountChanged( unsigned int ) ) ); + connect( m_model, SIGNAL( playlistDeleted() ), SLOT( onDeleted() ) ); } diff --git a/src/libtomahawk/playlist/playlistview.h b/src/libtomahawk/playlist/playlistview.h index 5fa0d3a51..dbf653625 100644 --- a/src/libtomahawk/playlist/playlistview.h +++ b/src/libtomahawk/playlist/playlistview.h @@ -39,7 +39,7 @@ public: ~PlaylistView(); PlaylistModel* playlistModel() const { return m_model; } - virtual void setModel( PlaylistModel* model ); + virtual void setModel( QAbstractItemModel* model ); virtual QWidget* widget() { return this; } virtual PlaylistInterface* playlistInterface() const { return proxyModel(); } diff --git a/src/libtomahawk/playlist/trackmodel.cpp b/src/libtomahawk/playlist/trackmodel.cpp index 6bcb438a8..0249aa1d1 100644 --- a/src/libtomahawk/playlist/trackmodel.cpp +++ b/src/libtomahawk/playlist/trackmodel.cpp @@ -81,6 +81,7 @@ TrackModel::rowCount( const QModelIndex& parent ) const int TrackModel::columnCount( const QModelIndex& parent ) const { + Q_UNUSED( parent ); return 9; } @@ -207,6 +208,7 @@ TrackModel::data( const QModelIndex& index, int role ) const QVariant TrackModel::headerData( int section, Qt::Orientation orientation, int role ) const { + Q_UNUSED( orientation ); QStringList headers; headers << tr( "Artist" ) << tr( "Track" ) << tr( "Album" ) << tr( "Duration" ) << tr( "Bitrate" ) << tr( "Age" ) << tr( "Year" ) << tr( "Size" ) << tr( "Origin" ); if ( role == Qt::DisplayRole && section >= 0 ) diff --git a/src/libtomahawk/playlist/trackmodel.h b/src/libtomahawk/playlist/trackmodel.h index 3fbb1e868..843e8f72a 100644 --- a/src/libtomahawk/playlist/trackmodel.h +++ b/src/libtomahawk/playlist/trackmodel.h @@ -99,8 +99,8 @@ public slots: virtual void removeIndex( const QModelIndex& index, bool moreToCome = false ); virtual void removeIndexes( const QList& indexes ); - virtual void setRepeatMode( PlaylistInterface::RepeatMode mode ) {} - virtual void setShuffled( bool shuffled ) {} + virtual void setRepeatMode( PlaylistInterface::RepeatMode /*mode*/ ) {} + virtual void setShuffled( bool /*shuffled*/ ) {} protected: virtual void setReadOnly( bool b ) { m_readOnly = b; } diff --git a/src/libtomahawk/playlist/trackproxymodel.cpp b/src/libtomahawk/playlist/trackproxymodel.cpp index 6fa2ac3f6..f69670057 100644 --- a/src/libtomahawk/playlist/trackproxymodel.cpp +++ b/src/libtomahawk/playlist/trackproxymodel.cpp @@ -45,13 +45,17 @@ TrackProxyModel::TrackProxyModel( QObject* parent ) void -TrackProxyModel::setSourceModel( TrackModel* sourceModel ) +TrackProxyModel::setSourceModel( QAbstractItemModel* sourceModel ) { - m_model = sourceModel; + TrackModel* tmodel = static_cast< TrackModel* >( sourceModel ); - if ( m_model ) - connect( m_model, SIGNAL( trackCountChanged( unsigned int ) ), - SIGNAL( sourceTrackCountChanged( unsigned int ) ) ); + if ( !tmodel ) + return; + else + m_model = tmodel; + + connect( m_model, SIGNAL( trackCountChanged( unsigned int ) ), + SIGNAL( sourceTrackCountChanged( unsigned int ) ) ); QSortFilterProxyModel::setSourceModel( sourceModel ); } diff --git a/src/libtomahawk/playlist/trackproxymodel.h b/src/libtomahawk/playlist/trackproxymodel.h index fa68b8c4b..ac626f0a5 100644 --- a/src/libtomahawk/playlist/trackproxymodel.h +++ b/src/libtomahawk/playlist/trackproxymodel.h @@ -34,7 +34,7 @@ public: explicit TrackProxyModel ( QObject* parent = 0 ); virtual TrackModel* sourceModel() const { return m_model; } - virtual void setSourceModel( TrackModel* sourceModel ); + virtual void setSourceModel( QAbstractItemModel* sourceModel ); virtual QPersistentModelIndex currentItem() const { return mapFromSource( m_model->currentItem() ); } virtual void setCurrentItem( const QModelIndex& index ) { m_model->setCurrentItem( mapToSource( index ) ); } diff --git a/src/libtomahawk/playlist/trackview.cpp b/src/libtomahawk/playlist/trackview.cpp index 3ec4ce4c7..8b851ba33 100644 --- a/src/libtomahawk/playlist/trackview.cpp +++ b/src/libtomahawk/playlist/trackview.cpp @@ -107,9 +107,14 @@ TrackView::setProxyModel( TrackProxyModel* model ) void -TrackView::setModel( TrackModel* model ) +TrackView::setModel( QAbstractItemModel* model ) { - m_model = model; + TrackModel* tmodel = static_cast< TrackModel* >( model ); + + if ( !tmodel ) + return; + else + m_model = tmodel; if ( m_proxyModel ) { diff --git a/src/libtomahawk/playlist/trackview.h b/src/libtomahawk/playlist/trackview.h index 7777e24a3..2d9707f26 100644 --- a/src/libtomahawk/playlist/trackview.h +++ b/src/libtomahawk/playlist/trackview.h @@ -44,7 +44,7 @@ public: virtual QString guid() const { return m_guid; } virtual void setGuid( const QString& guid ); - virtual void setModel( TrackModel* model ); + virtual void setModel( QAbstractItemModel* model ); void setProxyModel( TrackProxyModel* model ); virtual TrackModel* model() const { return m_model; } @@ -67,7 +67,7 @@ protected: virtual void startDrag( Qt::DropActions supportedActions ); virtual void dragEnterEvent( QDragEnterEvent* event ); - virtual void dragLeaveEvent( QDragLeaveEvent* event ) { m_dragging = false; setDirtyRegion( m_dropRect ); } + virtual void dragLeaveEvent( QDragLeaveEvent* /*event*/ ) { m_dragging = false; setDirtyRegion( m_dropRect ); } virtual void dragMoveEvent( QDragMoveEvent* event ); virtual void dropEvent( QDropEvent* event ); diff --git a/src/libtomahawk/source.cpp b/src/libtomahawk/source.cpp index 54c4394b3..29715c966 100644 --- a/src/libtomahawk/source.cpp +++ b/src/libtomahawk/source.cpp @@ -181,6 +181,7 @@ Source::scanningProgress( unsigned int files ) void Source::scanningFinished( unsigned int files ) { + Q_UNUSED( files ); m_textStatus = QString(); emit stateChanged(); } @@ -189,6 +190,7 @@ Source::scanningFinished( unsigned int files ) void Source::onStateChanged( DBSyncConnection::State newstate, DBSyncConnection::State oldstate, const QString& info ) { + Q_UNUSED( oldstate ); QString msg; switch( newstate ) { diff --git a/src/libtomahawk/tomahawksettings.cpp b/src/libtomahawk/tomahawksettings.cpp index bdbe3c171..37a905cad 100644 --- a/src/libtomahawk/tomahawksettings.cpp +++ b/src/libtomahawk/tomahawksettings.cpp @@ -624,7 +624,7 @@ TomahawkSettings::xmppBotPort() const void TomahawkSettings::setXmppBotPort( const int port ) { - setValue( "xmppBot/port", -1 ); + setValue( "xmppBot/port", port ); } void diff --git a/src/libtomahawk/viewpage.h b/src/libtomahawk/viewpage.h index a515c47e6..356fabcf8 100644 --- a/src/libtomahawk/viewpage.h +++ b/src/libtomahawk/viewpage.h @@ -35,6 +35,8 @@ class DLLEXPORT ViewPage public: ViewPage() {} + virtual ~ViewPage() {} + virtual QWidget* widget() = 0; virtual PlaylistInterface* playlistInterface() const = 0; diff --git a/src/libtomahawk/widgets/welcomewidget.cpp b/src/libtomahawk/widgets/welcomewidget.cpp index d7ad62938..55154e32f 100644 --- a/src/libtomahawk/widgets/welcomewidget.cpp +++ b/src/libtomahawk/widgets/welcomewidget.cpp @@ -190,6 +190,8 @@ PlaylistWidgetItem::data( int role ) const QSize PlaylistDelegate::sizeHint( const QStyleOptionViewItem& option, const QModelIndex& index ) const { + Q_UNUSED( option ); + Q_UNUSED( index ); return QSize( 0, 64 ); } diff --git a/src/scrobbler.cpp b/src/scrobbler.cpp index bbdb0e26f..2968d5196 100644 --- a/src/scrobbler.cpp +++ b/src/scrobbler.cpp @@ -137,6 +137,9 @@ Scrobbler::scrobble() void Scrobbler::infoSystemInfo( QString caller, Tomahawk::InfoSystem::InfoType type, QVariant input, QVariant output, Tomahawk::InfoSystem::InfoCustomData customData ) { + Q_UNUSED( input ); + Q_UNUSED( output ); + Q_UNUSED( customData ); if ( caller == s_infoIdentifier ) { qDebug() << Q_FUNC_INFO; diff --git a/src/sourcetree/sourcesmodel.cpp b/src/sourcetree/sourcesmodel.cpp index bee554830..76f1d233d 100644 --- a/src/sourcetree/sourcesmodel.cpp +++ b/src/sourcetree/sourcesmodel.cpp @@ -352,6 +352,7 @@ SourcesModel::collectionToIndex( const Tomahawk::collection_ptr& collection ) bool SourcesModel::setData( const QModelIndex& index, const QVariant& value, int role ) { + Q_UNUSED( role ); qDebug() << Q_FUNC_INFO; if ( !index.isValid() ) diff --git a/src/sourcetree/sourcetreeview.cpp b/src/sourcetree/sourcetreeview.cpp index 050f895db..4ceccd2c1 100644 --- a/src/sourcetree/sourcetreeview.cpp +++ b/src/sourcetree/sourcetreeview.cpp @@ -168,6 +168,7 @@ SourceTreeView::hideOfflineSources() void SourceTreeView::onSourceOffline( Tomahawk::source_ptr src ) { + Q_UNUSED( src ); qDebug() << Q_FUNC_INFO; } diff --git a/src/sourcetree/sourcetreeview.h b/src/sourcetree/sourcetreeview.h index 710c609cb..e9cbe51b0 100644 --- a/src/sourcetree/sourcetreeview.h +++ b/src/sourcetree/sourcetreeview.h @@ -68,7 +68,7 @@ protected: virtual void paintEvent( QPaintEvent* event ); virtual void dragEnterEvent( QDragEnterEvent* event ); - virtual void dragLeaveEvent( QDragLeaveEvent* event ) { m_dragging = false; setDirtyRegion( m_dropRect ); } + virtual void dragLeaveEvent( QDragLeaveEvent* event ) { Q_UNUSED( event ); m_dragging = false; setDirtyRegion( m_dropRect ); } virtual void dragMoveEvent( QDragMoveEvent* event ); virtual void dropEvent( QDropEvent* event ); From e2a3c48e6e318f6fa00b3e999b89ce786891d1c8 Mon Sep 17 00:00:00 2001 From: Jeff Mitchell Date: Tue, 12 Apr 2011 15:21:05 -0400 Subject: [PATCH 298/329] Rejig the parameters and add QASSERTs. Chris, I decided to take your advice about the asserts -- it will help in the future to make sure people don't mistakenly use the wrong function in new code either. --- src/libtomahawk/playlist/albumproxymodel.cpp | 15 +++++++++------ src/libtomahawk/playlist/albumproxymodel.h | 1 + src/libtomahawk/playlist/albumview.cpp | 18 ++++++++++++------ src/libtomahawk/playlist/albumview.h | 1 + src/libtomahawk/playlist/collectionview.cpp | 14 +++++++++----- src/libtomahawk/playlist/collectionview.h | 1 + .../playlist/dynamic/DynamicView.cpp | 8 ++++---- src/libtomahawk/playlist/dynamic/DynamicView.h | 2 +- src/libtomahawk/playlist/playlistview.cpp | 16 ++++++++++------ src/libtomahawk/playlist/playlistview.h | 1 + src/libtomahawk/playlist/trackproxymodel.cpp | 18 +++++++++++------- src/libtomahawk/playlist/trackproxymodel.h | 3 ++- src/libtomahawk/playlist/trackview.cpp | 17 +++++++++++------ src/libtomahawk/playlist/trackview.h | 1 + 14 files changed, 74 insertions(+), 42 deletions(-) diff --git a/src/libtomahawk/playlist/albumproxymodel.cpp b/src/libtomahawk/playlist/albumproxymodel.cpp index 97f2130c7..c3b3fe29c 100644 --- a/src/libtomahawk/playlist/albumproxymodel.cpp +++ b/src/libtomahawk/playlist/albumproxymodel.cpp @@ -41,15 +41,18 @@ AlbumProxyModel::AlbumProxyModel( QObject* parent ) setSourceModel( 0 ); } - void AlbumProxyModel::setSourceModel( QAbstractItemModel* sourceModel ) { - AlbumModel* amodel = static_cast< AlbumModel* >( sourceModel ); - if( !amodel ) - return; - else - m_model = amodel; + Q_UNUSED( sourceModel ); + qDebug() << "Explicitly use setSourceAlbumModel instead"; + Q_ASSERT( false ); +} + +void +AlbumProxyModel::setSourceAlbumModel( AlbumModel* sourceModel ) +{ + m_model = sourceModel; connect( m_model, SIGNAL( trackCountChanged( unsigned int ) ), SIGNAL( sourceTrackCountChanged( unsigned int ) ) ); diff --git a/src/libtomahawk/playlist/albumproxymodel.h b/src/libtomahawk/playlist/albumproxymodel.h index 87535bbb1..7427cc85f 100644 --- a/src/libtomahawk/playlist/albumproxymodel.h +++ b/src/libtomahawk/playlist/albumproxymodel.h @@ -34,6 +34,7 @@ public: explicit AlbumProxyModel( QObject* parent = 0 ); virtual AlbumModel* sourceModel() const { return m_model; } + virtual void setSourceAlbumModel( AlbumModel* sourceModel ); virtual void setSourceModel( QAbstractItemModel* sourceModel ); virtual QList tracks() { Q_ASSERT( FALSE ); QList queries; return queries; } diff --git a/src/libtomahawk/playlist/albumview.cpp b/src/libtomahawk/playlist/albumview.cpp index 4d652d1f4..9f6ad1a57 100644 --- a/src/libtomahawk/playlist/albumview.cpp +++ b/src/libtomahawk/playlist/albumview.cpp @@ -74,16 +74,20 @@ AlbumView::setProxyModel( AlbumProxyModel* model ) void AlbumView::setModel( QAbstractItemModel* model ) { - AlbumModel *amodel = static_cast< AlbumModel* >( model ); + Q_UNUSED( model ); + qDebug() << "Explicitly use setAlbumModel instead"; + Q_ASSERT( false ); +} - if ( !amodel ) - return; - else - m_model = amodel; + +void +AlbumView::setAlbumModel( AlbumModel* model ) +{ + m_model = model; if ( m_proxyModel ) { - m_proxyModel->setSourceModel( model ); + m_proxyModel->setSourceModel( m_model ); m_proxyModel->sort( 0 ); } @@ -147,6 +151,7 @@ AlbumView::onFilterChanged( const QString& ) void AlbumView::startDrag( Qt::DropActions supportedActions ) { + Q_UNUSED( supportedActions ); } @@ -154,5 +159,6 @@ AlbumView::startDrag( Qt::DropActions supportedActions ) QPixmap AlbumView::createDragPixmap( int itemCount ) const { + Q_UNUSED( itemCount ); return QPixmap(); } diff --git a/src/libtomahawk/playlist/albumview.h b/src/libtomahawk/playlist/albumview.h index 013cb349a..194eab8d7 100644 --- a/src/libtomahawk/playlist/albumview.h +++ b/src/libtomahawk/playlist/albumview.h @@ -42,6 +42,7 @@ public: AlbumProxyModel* proxyModel() const { return m_proxyModel; } // PlaylistItemDelegate* delegate() { return m_delegate; } + void setAlbumModel( AlbumModel* model ); void setModel( QAbstractItemModel* model ); virtual QWidget* widget() { return this; } diff --git a/src/libtomahawk/playlist/collectionview.cpp b/src/libtomahawk/playlist/collectionview.cpp index e5a72cd19..9f3b439ef 100644 --- a/src/libtomahawk/playlist/collectionview.cpp +++ b/src/libtomahawk/playlist/collectionview.cpp @@ -53,14 +53,18 @@ CollectionView::~CollectionView() void CollectionView::setModel( QAbstractItemModel* model ) { - TrackModel *tmodel = static_cast< TrackModel* >( model ); - if ( !tmodel ) - return; + qDebug() << "Explicitly use setTrackModel instead"; + Q_ASSERT( false ); +} - TrackView::setModel( tmodel ); + +void +CollectionView::setTrackModel( TrackModel* model ) +{ + TrackView::setModel( model ); setGuid( "collectionview" ); - connect( tmodel, SIGNAL( trackCountChanged( unsigned int ) ), SLOT( onTrackCountChanged( unsigned int ) ) ); + connect( model, SIGNAL( trackCountChanged( unsigned int ) ), SLOT( onTrackCountChanged( unsigned int ) ) ); } diff --git a/src/libtomahawk/playlist/collectionview.h b/src/libtomahawk/playlist/collectionview.h index 54fa0b47a..1fe261646 100644 --- a/src/libtomahawk/playlist/collectionview.h +++ b/src/libtomahawk/playlist/collectionview.h @@ -36,6 +36,7 @@ public: explicit CollectionView( QWidget* parent = 0 ); ~CollectionView(); + virtual void setTrackModel( TrackModel* model ); virtual void setModel( QAbstractItemModel* model ); virtual QWidget* widget() { return this; } diff --git a/src/libtomahawk/playlist/dynamic/DynamicView.cpp b/src/libtomahawk/playlist/dynamic/DynamicView.cpp index 05b2c9c9d..85b87aa53 100644 --- a/src/libtomahawk/playlist/dynamic/DynamicView.cpp +++ b/src/libtomahawk/playlist/dynamic/DynamicView.cpp @@ -71,13 +71,13 @@ DynamicView::~DynamicView() } void -DynamicView::setModel( DynamicModel* model) +DynamicView::setDynamicModel( DynamicModel* model) { m_model = model; - PlaylistView::setModel( model ); + PlaylistView::setModel( m_model ); - connect( model, SIGNAL( trackCountChanged( unsigned int ) ), SLOT( onTrackCountChanged( unsigned int ) ) ); - connect( model, SIGNAL( checkForOverflow() ), this, SLOT( checkForOverflow() ) ); + connect( m_model, SIGNAL( trackCountChanged( unsigned int ) ), SLOT( onTrackCountChanged( unsigned int ) ) ); + connect( m_model, SIGNAL( checkForOverflow() ), this, SLOT( checkForOverflow() ) ); } void diff --git a/src/libtomahawk/playlist/dynamic/DynamicView.h b/src/libtomahawk/playlist/dynamic/DynamicView.h index 3ea371d3a..39826fe97 100644 --- a/src/libtomahawk/playlist/dynamic/DynamicView.h +++ b/src/libtomahawk/playlist/dynamic/DynamicView.h @@ -40,7 +40,7 @@ public: explicit DynamicView( QWidget* parent = 0 ); virtual ~DynamicView(); - virtual void setModel( DynamicModel* model ); + virtual void setDynamicModel( DynamicModel* model ); void setOnDemand( bool onDemand ); void setReadOnly( bool readOnly ); diff --git a/src/libtomahawk/playlist/playlistview.cpp b/src/libtomahawk/playlist/playlistview.cpp index e6a06ea0c..0ba3373db 100644 --- a/src/libtomahawk/playlist/playlistview.cpp +++ b/src/libtomahawk/playlist/playlistview.cpp @@ -47,14 +47,18 @@ PlaylistView::~PlaylistView() void PlaylistView::setModel( QAbstractItemModel* model ) { - PlaylistModel* pmodel = static_cast< PlaylistModel* >( model ); + Q_UNUSED( model ); + qDebug() << "Explicitly use setPlaylistModel instead"; + Q_ASSERT( false ); +} - if ( !pmodel ) - return; - else - m_model = pmodel; - TrackView::setModel( model ); +void +PlaylistView::setPlaylistModel( PlaylistModel* model ) +{ + m_model = model; + + TrackView::setModel( m_model ); setColumnHidden( 5, true ); // Hide age column per default if ( !m_model->playlist().isNull() ) diff --git a/src/libtomahawk/playlist/playlistview.h b/src/libtomahawk/playlist/playlistview.h index dbf653625..84df64805 100644 --- a/src/libtomahawk/playlist/playlistview.h +++ b/src/libtomahawk/playlist/playlistview.h @@ -39,6 +39,7 @@ public: ~PlaylistView(); PlaylistModel* playlistModel() const { return m_model; } + virtual void setPlaylistModel( PlaylistModel* model ); virtual void setModel( QAbstractItemModel* model ); virtual QWidget* widget() { return this; } diff --git a/src/libtomahawk/playlist/trackproxymodel.cpp b/src/libtomahawk/playlist/trackproxymodel.cpp index f69670057..2c11bcd6d 100644 --- a/src/libtomahawk/playlist/trackproxymodel.cpp +++ b/src/libtomahawk/playlist/trackproxymodel.cpp @@ -45,19 +45,23 @@ TrackProxyModel::TrackProxyModel( QObject* parent ) void -TrackProxyModel::setSourceModel( QAbstractItemModel* sourceModel ) +TrackProxyModel::setSourceModel( QAbstractItemModel* model ) { - TrackModel* tmodel = static_cast< TrackModel* >( sourceModel ); + Q_UNUSED( model ); + qDebug() << "Explicitly use setSourceTrackModel instead"; + Q_ASSERT( false ); +} - if ( !tmodel ) - return; - else - m_model = tmodel; + +void +TrackProxyModel::setSourceTrackModel( TrackModel* sourceModel ) +{ + m_model = sourceModel; connect( m_model, SIGNAL( trackCountChanged( unsigned int ) ), SIGNAL( sourceTrackCountChanged( unsigned int ) ) ); - QSortFilterProxyModel::setSourceModel( sourceModel ); + QSortFilterProxyModel::setSourceModel( m_model ); } diff --git a/src/libtomahawk/playlist/trackproxymodel.h b/src/libtomahawk/playlist/trackproxymodel.h index ac626f0a5..853a512d8 100644 --- a/src/libtomahawk/playlist/trackproxymodel.h +++ b/src/libtomahawk/playlist/trackproxymodel.h @@ -34,7 +34,8 @@ public: explicit TrackProxyModel ( QObject* parent = 0 ); virtual TrackModel* sourceModel() const { return m_model; } - virtual void setSourceModel( QAbstractItemModel* sourceModel ); + virtual void setSourceTrackModel( TrackModel* sourceModel ); + virtual void setSourceModel( QAbstractItemModel* model ); virtual QPersistentModelIndex currentItem() const { return mapFromSource( m_model->currentItem() ); } virtual void setCurrentItem( const QModelIndex& index ) { m_model->setCurrentItem( mapToSource( index ) ); } diff --git a/src/libtomahawk/playlist/trackview.cpp b/src/libtomahawk/playlist/trackview.cpp index 8b851ba33..3e7b11bef 100644 --- a/src/libtomahawk/playlist/trackview.cpp +++ b/src/libtomahawk/playlist/trackview.cpp @@ -33,6 +33,7 @@ #include "queueview.h" #include "trackmodel.h" #include "trackproxymodel.h" +#include using namespace Tomahawk; @@ -109,16 +110,20 @@ TrackView::setProxyModel( TrackProxyModel* model ) void TrackView::setModel( QAbstractItemModel* model ) { - TrackModel* tmodel = static_cast< TrackModel* >( model ); + Q_UNUSED( model ); + qDebug() << "Explicitly use setTrackModel instead"; + Q_ASSERT( false ); +} - if ( !tmodel ) - return; - else - m_model = tmodel; + +void +TrackView::setTrackModel( TrackModel* model ) +{ + m_model = model; if ( m_proxyModel ) { - m_proxyModel->setSourceModel( model ); + m_proxyModel->setSourceModel( m_model ); } connect( m_model, SIGNAL( itemSizeChanged( QModelIndex ) ), SLOT( onItemResized( QModelIndex ) ) ); diff --git a/src/libtomahawk/playlist/trackview.h b/src/libtomahawk/playlist/trackview.h index 2d9707f26..aac8b2a11 100644 --- a/src/libtomahawk/playlist/trackview.h +++ b/src/libtomahawk/playlist/trackview.h @@ -44,6 +44,7 @@ public: virtual QString guid() const { return m_guid; } virtual void setGuid( const QString& guid ); + virtual void setTrackModel( TrackModel* model ); virtual void setModel( QAbstractItemModel* model ); void setProxyModel( TrackProxyModel* model ); From 5455ee0f214a2ca7b4ade27b564ab5394fbec0a1 Mon Sep 17 00:00:00 2001 From: Jeff Mitchell Date: Tue, 12 Apr 2011 16:52:25 -0400 Subject: [PATCH 299/329] Unused warning cleanups --- src/infosystem/infoplugins/echonestplugin.cpp | 2 ++ src/libtomahawk/audio/dummytranscode.cpp | 2 ++ src/libtomahawk/audio/flactranscode.cpp | 2 ++ src/libtomahawk/audio/vorbistranscode.cpp | 6 ++++++ src/libtomahawk/network/bufferiodevice.cpp | 3 +++ src/libtomahawk/playlist/collectionmodel.cpp | 1 + src/libtomahawk/playlist/collectionview.cpp | 1 + .../playlist/dynamic/GeneratorInterface.cpp | 1 + .../dynamic/widgets/DynamicSetupWidget.cpp | 2 +- .../playlist/dynamic/widgets/LoadingSpinner.cpp | 1 + src/libtomahawk/utils/querylabel.cpp | 1 + src/libtomahawk/widgets/overlaywidget.cpp | 1 + src/musicscanner.cpp | 1 + src/scanmanager.cpp | 1 + src/sip/jabber/jabber_p.cpp | 14 ++++++++++++-- src/sip/twitter/twitter.cpp | 2 ++ src/sip/twitter/twitter.h | 5 +++++ src/sip/twitter/twitterconfigwidget.cpp | 1 + src/sip/zeroconf/zeroconf.h | 5 +++++ src/web/api_v1.cpp | 3 +++ 20 files changed, 52 insertions(+), 3 deletions(-) diff --git a/src/infosystem/infoplugins/echonestplugin.cpp b/src/infosystem/infoplugins/echonestplugin.cpp index 38f64ed4c..7bec6c653 100644 --- a/src/infosystem/infoplugins/echonestplugin.cpp +++ b/src/infosystem/infoplugins/echonestplugin.cpp @@ -68,6 +68,7 @@ void EchoNestPlugin::getInfo(const QString &caller, const InfoType type, const Q void EchoNestPlugin::getSongProfile(const QString &caller, const QVariant& data, InfoCustomData &customData, const QString &item) { //WARNING: Totally not implemented yet + Q_UNUSED( item ); if( !isValidTrackData( caller, data, customData ) ) return; @@ -139,6 +140,7 @@ void EchoNestPlugin::getArtistTerms(const QString &caller, const QVariant& data, void EchoNestPlugin::getMiscTopTerms(const QString &caller, const QVariant& data, InfoCustomData& customData) { + Q_UNUSED( data ); QNetworkReply* reply = Echonest::Artist::topTerms( 20 ); m_replyMap[reply] = customData; m_callerMap[reply] = caller; diff --git a/src/libtomahawk/audio/dummytranscode.cpp b/src/libtomahawk/audio/dummytranscode.cpp index 1ab245d86..92b5efc6e 100644 --- a/src/libtomahawk/audio/dummytranscode.cpp +++ b/src/libtomahawk/audio/dummytranscode.cpp @@ -36,6 +36,7 @@ DummyTranscode::~DummyTranscode() void DummyTranscode::processData( const QByteArray &buffer, bool finish ) { + Q_UNUSED( finish ); m_buffer.append( buffer ); // qDebug() << "DUMMYTRANSCODING:" << buffer.size(); @@ -49,6 +50,7 @@ DummyTranscode::processData( const QByteArray &buffer, bool finish ) void DummyTranscode::onSeek( int seconds ) { + Q_UNUSED( seconds ); m_buffer.clear(); } diff --git a/src/libtomahawk/audio/flactranscode.cpp b/src/libtomahawk/audio/flactranscode.cpp index aee7299d1..c66c5a557 100644 --- a/src/libtomahawk/audio/flactranscode.cpp +++ b/src/libtomahawk/audio/flactranscode.cpp @@ -39,6 +39,7 @@ FLACTranscode::~FLACTranscode() void FLACTranscode::onSeek( int seconds ) { + Q_UNUSED( seconds ); QMutexLocker locker( &m_mutex ); m_buffer.clear(); @@ -120,6 +121,7 @@ FLACTranscode::write_callback( const ::FLAC__Frame *frame, const FLAC__int32 *co ::FLAC__StreamDecoderSeekStatus FLACTranscode::seek_callback(FLAC__uint64 absolute_byte_offset) { + Q_UNUSED( absolute_byte_offset ); return FLAC__STREAM_DECODER_SEEK_STATUS_UNSUPPORTED; } diff --git a/src/libtomahawk/audio/vorbistranscode.cpp b/src/libtomahawk/audio/vorbistranscode.cpp index 826c0f272..20403684c 100644 --- a/src/libtomahawk/audio/vorbistranscode.cpp +++ b/src/libtomahawk/audio/vorbistranscode.cpp @@ -39,6 +39,9 @@ vorbis_read( void* data_ptr, size_t byteSize, size_t sizeToRead, void* data_src int vorbis_seek( void* data_src, ogg_int64_t offset, int origin ) { + Q_UNUSED( data_src ); + Q_UNUSED( offset ); + Q_UNUSED( origin ); return -1; } @@ -46,6 +49,7 @@ vorbis_seek( void* data_src, ogg_int64_t offset, int origin ) int vorbis_close( void* data_src ) { + Q_UNUSED( data_src ); // done ;-) return 0; } @@ -54,6 +58,7 @@ vorbis_close( void* data_src ) long vorbis_tell( void* data_src ) { + Q_UNUSED( data_src ); return -1; } @@ -74,6 +79,7 @@ VorbisTranscode::~VorbisTranscode() void VorbisTranscode::onSeek( int seconds ) { + Q_UNUSED( seconds ); QMutexLocker locker( &m_mutex ); m_buffer.clear(); diff --git a/src/libtomahawk/network/bufferiodevice.cpp b/src/libtomahawk/network/bufferiodevice.cpp index 435a2b2cd..bceca0745 100644 --- a/src/libtomahawk/network/bufferiodevice.cpp +++ b/src/libtomahawk/network/bufferiodevice.cpp @@ -38,6 +38,7 @@ BufferIODevice::BufferIODevice( unsigned int size, QObject* parent ) bool BufferIODevice::open( OpenMode mode ) { + Q_UNUSED( mode ); QMutexLocker lock( &m_mut ); qDebug() << Q_FUNC_INFO; @@ -148,6 +149,8 @@ BufferIODevice::readData( char* data, qint64 maxSize ) qint64 BufferIODevice::writeData( const char* data, qint64 maxSize ) { + Q_UNUSED( data ); + Q_UNUSED( maxSize ); // call addData instead Q_ASSERT( false ); return 0; diff --git a/src/libtomahawk/playlist/collectionmodel.cpp b/src/libtomahawk/playlist/collectionmodel.cpp index ac0a5dc18..ec8808764 100644 --- a/src/libtomahawk/playlist/collectionmodel.cpp +++ b/src/libtomahawk/playlist/collectionmodel.cpp @@ -75,6 +75,7 @@ CollectionModel::rowCount( const QModelIndex& parent ) const int CollectionModel::columnCount( const QModelIndex& parent ) const { + Q_UNUSED( parent ); return 4; } diff --git a/src/libtomahawk/playlist/collectionview.cpp b/src/libtomahawk/playlist/collectionview.cpp index 9f3b439ef..ff538e3c1 100644 --- a/src/libtomahawk/playlist/collectionview.cpp +++ b/src/libtomahawk/playlist/collectionview.cpp @@ -53,6 +53,7 @@ CollectionView::~CollectionView() void CollectionView::setModel( QAbstractItemModel* model ) { + Q_UNUSED( model ); qDebug() << "Explicitly use setTrackModel instead"; Q_ASSERT( false ); } diff --git a/src/libtomahawk/playlist/dynamic/GeneratorInterface.cpp b/src/libtomahawk/playlist/dynamic/GeneratorInterface.cpp index 999d8aa45..0ccf5888a 100644 --- a/src/libtomahawk/playlist/dynamic/GeneratorInterface.cpp +++ b/src/libtomahawk/playlist/dynamic/GeneratorInterface.cpp @@ -71,6 +71,7 @@ void Tomahawk::GeneratorInterface::removeControl(const Tomahawk::dyncontrol_ptr& Tomahawk::dyncontrol_ptr Tomahawk::GeneratorInterface::createControl(const QString& type) { + Q_UNUSED( type ); Q_ASSERT( false ); return dyncontrol_ptr(); } diff --git a/src/libtomahawk/playlist/dynamic/widgets/DynamicSetupWidget.cpp b/src/libtomahawk/playlist/dynamic/widgets/DynamicSetupWidget.cpp index 062fa6485..8eec49f0a 100644 --- a/src/libtomahawk/playlist/dynamic/widgets/DynamicSetupWidget.cpp +++ b/src/libtomahawk/playlist/dynamic/widgets/DynamicSetupWidget.cpp @@ -104,7 +104,7 @@ DynamicSetupWidget::~DynamicSetupWidget() void DynamicSetupWidget::setPlaylist( const Tomahawk::dynplaylist_ptr& playlist ) { - + Q_UNUSED( playlist ); } void diff --git a/src/libtomahawk/playlist/dynamic/widgets/LoadingSpinner.cpp b/src/libtomahawk/playlist/dynamic/widgets/LoadingSpinner.cpp index a532d51da..b1bd4efc2 100644 --- a/src/libtomahawk/playlist/dynamic/widgets/LoadingSpinner.cpp +++ b/src/libtomahawk/playlist/dynamic/widgets/LoadingSpinner.cpp @@ -106,6 +106,7 @@ LoadingSpinner::reposition() void LoadingSpinner::paintEvent( QPaintEvent* ev ) { + Q_UNUSED( ev ); QPainter p( this ); // qDebug() << "FADING" << ( m_showHide->state() == QTimeLine::Running ) << "at frame:" << m_showHide->currentValue(); diff --git a/src/libtomahawk/utils/querylabel.cpp b/src/libtomahawk/utils/querylabel.cpp index 8a5d77ce0..d765f65dd 100644 --- a/src/libtomahawk/utils/querylabel.cpp +++ b/src/libtomahawk/utils/querylabel.cpp @@ -544,6 +544,7 @@ QueryLabel::mouseMoveEvent( QMouseEvent* event ) void QueryLabel::leaveEvent( QEvent* event ) { + Q_UNUSED( event ); m_hoverArea = QRect(); m_hoverType = None; repaint(); diff --git a/src/libtomahawk/widgets/overlaywidget.cpp b/src/libtomahawk/widgets/overlaywidget.cpp index 3f46f5e11..64471b272 100644 --- a/src/libtomahawk/widgets/overlaywidget.cpp +++ b/src/libtomahawk/widgets/overlaywidget.cpp @@ -121,6 +121,7 @@ OverlayWidget::shown() const void OverlayWidget::paintEvent( QPaintEvent* event ) { + Q_UNUSED( event ); QPoint center( ( m_parent->width() - width() ) / 2, ( m_parent->height() - height() ) / 2 ); move( center ); diff --git a/src/musicscanner.cpp b/src/musicscanner.cpp index 3cc36305e..601d89288 100644 --- a/src/musicscanner.cpp +++ b/src/musicscanner.cpp @@ -263,6 +263,7 @@ MusicScanner::listerQuit() void MusicScanner::listerDestroyed( QObject* dirLister ) { + Q_UNUSED( dirLister ); qDebug() << Q_FUNC_INFO; m_dirListerThreadController->deleteLater(); m_dirListerThreadController = 0; diff --git a/src/scanmanager.cpp b/src/scanmanager.cpp index f28cf0f82..27f134e01 100644 --- a/src/scanmanager.cpp +++ b/src/scanmanager.cpp @@ -273,6 +273,7 @@ ScanManager::scannerQuit() void ScanManager::scannerDestroyed( QObject* scanner ) { + Q_UNUSED( scanner ); qDebug() << Q_FUNC_INFO; m_musicScannerThreadController->deleteLater(); m_musicScannerThreadController = 0; diff --git a/src/sip/jabber/jabber_p.cpp b/src/sip/jabber/jabber_p.cpp index 267322f6b..6f38dd292 100644 --- a/src/sip/jabber/jabber_p.cpp +++ b/src/sip/jabber/jabber_p.cpp @@ -479,6 +479,7 @@ Jabber_p::handleLog( gloox::LogLevel level, gloox::LogArea area, const std::stri void Jabber_p::onResourceBindError( gloox::ResourceBindError error ) { + Q_UNUSED( error ); qDebug() << Q_FUNC_INFO; } @@ -486,6 +487,7 @@ Jabber_p::onResourceBindError( gloox::ResourceBindError error ) void Jabber_p::onSessionCreateError( gloox::SessionCreateError error ) { + Q_UNUSED( error ); qDebug() << Q_FUNC_INFO; } @@ -731,6 +733,7 @@ Jabber_p::handleNonrosterPresence( const gloox::Presence& presence ) void Jabber_p::handleVCard( const gloox::JID& jid, const gloox::VCard* vcard ) { + Q_UNUSED( vcard ); qDebug() << "VCARD RECEIVED!" << jid.bare().c_str(); } @@ -738,6 +741,8 @@ Jabber_p::handleVCard( const gloox::JID& jid, const gloox::VCard* vcard ) void Jabber_p::handleVCardResult( gloox::VCardHandler::VCardContext context, const gloox::JID& jid, gloox::StanzaError se ) { + Q_UNUSED( context ); + Q_UNUSED( se ); qDebug() << "VCARD RESULT RECEIVED!" << jid.bare().c_str(); } @@ -746,6 +751,7 @@ Jabber_p::handleVCardResult( gloox::VCardHandler::VCardContext context, const gl void Jabber_p::handleDiscoInfo( const gloox::JID& from, const gloox::Disco::Info& info, int context ) { + Q_UNUSED( context ); QString jidstr( from.full().c_str() ); //qDebug() << "DISCOinfo" << jidstr; if ( info.hasFeature("tomahawk:player") ) @@ -762,15 +768,19 @@ Jabber_p::handleDiscoInfo( const gloox::JID& from, const gloox::Disco::Info& inf void -Jabber_p::handleDiscoItems( const gloox::JID& /*iq*/, const gloox::Disco::Items&, int /*context*/ ) +Jabber_p::handleDiscoItems( const gloox::JID& iq, const gloox::Disco::Items& items, int context ) { + Q_UNUSED( iq ); + Q_UNUSED( items ); + Q_UNUSED( context ); qDebug() << Q_FUNC_INFO; } void -Jabber_p::handleDiscoError( const gloox::JID& j, const gloox::Error* e, int /*context*/ ) +Jabber_p::handleDiscoError( const gloox::JID& j, const gloox::Error* e, int context ) { + Q_UNUSED( context ); qDebug() << Q_FUNC_INFO << j.full().c_str() << e->text().c_str() << e->type(); } /// END DISCO STUFF diff --git a/src/sip/twitter/twitter.cpp b/src/sip/twitter/twitter.cpp index d1ef42b33..643f11fcd 100644 --- a/src/sip/twitter/twitter.cpp +++ b/src/sip/twitter/twitter.cpp @@ -638,6 +638,8 @@ TwitterPlugin::directMessagePosted( const QTweetDMStatus& message ) void TwitterPlugin::directMessagePostError( QTweetNetBase::ErrorCode errorCode, const QString &message ) { + Q_UNUSED( errorCode ); + Q_UNUSED( message ); qDebug() << Q_FUNC_INFO; qDebug() << "TwitterPlugin received an error posting direct message: " << m_directMessageNew.data()->lastErrorMessage(); } diff --git a/src/sip/twitter/twitter.h b/src/sip/twitter/twitter.h index c0da00d7f..418cc1f07 100644 --- a/src/sip/twitter/twitter.h +++ b/src/sip/twitter/twitter.h @@ -64,14 +64,19 @@ public slots: void sendMsg( const QString& to, const QString& msg ) { + Q_UNUSED( to ); + Q_UNUSED( msg ); } void broadcastMsg( const QString &msg ) { + Q_UNUSED( msg ); } void addContact( const QString &jid, const QString& msg = QString() ) { + Q_UNUSED( jid ); + Q_UNUSED( msg ); } private slots: diff --git a/src/sip/twitter/twitterconfigwidget.cpp b/src/sip/twitter/twitterconfigwidget.cpp index 00eb46d87..f728b4107 100644 --- a/src/sip/twitter/twitterconfigwidget.cpp +++ b/src/sip/twitter/twitterconfigwidget.cpp @@ -170,6 +170,7 @@ TwitterConfigWidget::deauthenticateTwitter() void TwitterConfigWidget::tweetComboBoxIndexChanged( int index ) { + Q_UNUSED( index ); if( ui->twitterTweetComboBox->currentText() == tr( "Global Tweet" ) ) //FIXME: use data! { ui->twitterUserTweetLineEdit->setReadOnly( true ); diff --git a/src/sip/zeroconf/zeroconf.h b/src/sip/zeroconf/zeroconf.h index d538f9a67..2d971ebb0 100644 --- a/src/sip/zeroconf/zeroconf.h +++ b/src/sip/zeroconf/zeroconf.h @@ -57,14 +57,19 @@ public slots: void sendMsg( const QString& to, const QString& msg ) { + Q_UNUSED( to ); + Q_UNUSED( msg ); } void broadcastMsg( const QString &msg ) { + Q_UNUSED( msg ); } void addContact( const QString &jid, const QString& msg = QString() ) { + Q_UNUSED( jid ); + Q_UNUSED( msg ); } private slots: diff --git a/src/web/api_v1.cpp b/src/web/api_v1.cpp index 67b2e4d8b..d0a3b0475 100644 --- a/src/web/api_v1.cpp +++ b/src/web/api_v1.cpp @@ -145,6 +145,7 @@ Api_v1::api( QxtWebRequestEvent* event ) void Api_v1::sid( QxtWebRequestEvent* event, QString unused ) { + Q_UNUSED( unused ); using namespace Tomahawk; RID rid = event->url.path().mid( 5 ); qDebug() << "Request for sid " << rid; @@ -206,6 +207,8 @@ Api_v1::stat( QxtWebRequestEvent* event ) void Api_v1::statResult( const QString& clientToken, const QString& name, bool valid ) { + Q_UNUSED( clientToken ) + Q_UNUSED( name ) QVariantMap m; m.insert( "name", "playdar" ); m.insert( "version", "0.1.1" ); // TODO (needs to be >=0.1.1 for JS to work) From d3ca52cba33c01401e04c7550e4433e48c6aff54 Mon Sep 17 00:00:00 2001 From: Frank Osterfeld Date: Wed, 13 Apr 2011 04:49:12 +0800 Subject: [PATCH 300/329] link on OS X --- src/sip/jreen/CMakeLists.txt | 1 + 1 file changed, 1 insertion(+) diff --git a/src/sip/jreen/CMakeLists.txt b/src/sip/jreen/CMakeLists.txt index df2a4f7b6..b06fac816 100644 --- a/src/sip/jreen/CMakeLists.txt +++ b/src/sip/jreen/CMakeLists.txt @@ -37,6 +37,7 @@ target_link_libraries( tomahawk_sipjreen ${QT_LIBRARIES} ${LIBJREEN_LIBRARY} ${OS_SPECIFIC_LINK_LIBRARIES} + tomahawklib ) IF( APPLE ) From 800c06e67881cdcc00c1442ec773c1992a007d92 Mon Sep 17 00:00:00 2001 From: Frank Osterfeld Date: Wed, 13 Apr 2011 04:49:48 +0800 Subject: [PATCH 301/329] initialize m_dragging variable (fixes valgrind warning). --- src/libtomahawk/playlist/trackview.cpp | 1 + 1 file changed, 1 insertion(+) diff --git a/src/libtomahawk/playlist/trackview.cpp b/src/libtomahawk/playlist/trackview.cpp index 3e7b11bef..ce1240b1b 100644 --- a/src/libtomahawk/playlist/trackview.cpp +++ b/src/libtomahawk/playlist/trackview.cpp @@ -47,6 +47,7 @@ TrackView::TrackView( QWidget* parent ) , m_overlay( new OverlayWidget( this ) ) , m_loadingSpinner( new LoadingSpinner( this ) ) , m_resizing( false ) + , m_dragging( false ) { setSortingEnabled( false ); setAlternatingRowColors( true ); From b214dd01f8fbd4680869466446a5983ef2e5f841 Mon Sep 17 00:00:00 2001 From: Frank Osterfeld Date: Wed, 13 Apr 2011 04:50:51 +0800 Subject: [PATCH 302/329] randomize XMPP resource name so that multiple Tomahawk/JReen instances do not collide. --- src/sip/jreen/jabber_p.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/sip/jreen/jabber_p.cpp b/src/sip/jreen/jabber_p.cpp index 93987b35e..9e4aa8798 100644 --- a/src/sip/jreen/jabber_p.cpp +++ b/src/sip/jreen/jabber_p.cpp @@ -60,7 +60,7 @@ Jabber_p::Jabber_p( const QString& jid, const QString& password, const QString& m_jid = Jreen::JID( jid ); m_client = new Jreen::Client( jid, password ); - m_client->setResource( QString( "tomahawk%1" ).arg( "DOMME" ) ); + m_client->setResource( QString( "tomahawk-jreen%1" ).arg( QString::number( qrand() % 10000 ) ) ); Jreen::Capabilities::Ptr caps = m_client->presence().findExtension(); caps->setNode(TOMAHAWK_CAP_NODE_NAME); From aca94b1b55855ca98998aceb59b17ad269211528 Mon Sep 17 00:00:00 2001 From: Jeff Mitchell Date: Tue, 12 Apr 2011 18:19:50 -0400 Subject: [PATCH 303/329] Fix some bad function usage asserts --- src/libtomahawk/playlist/albumproxymodel.cpp | 2 +- src/libtomahawk/playlist/albumview.cpp | 2 +- src/libtomahawk/playlist/collectionview.cpp | 2 +- src/libtomahawk/playlist/playlistmanager.cpp | 6 +++--- src/libtomahawk/playlist/playlistview.cpp | 2 +- src/libtomahawk/playlist/trackproxymodel.cpp | 2 +- src/libtomahawk/playlist/trackview.cpp | 2 +- src/libtomahawk/widgets/welcomewidget.cpp | 2 +- 8 files changed, 10 insertions(+), 10 deletions(-) diff --git a/src/libtomahawk/playlist/albumproxymodel.cpp b/src/libtomahawk/playlist/albumproxymodel.cpp index c3b3fe29c..f35aa9b26 100644 --- a/src/libtomahawk/playlist/albumproxymodel.cpp +++ b/src/libtomahawk/playlist/albumproxymodel.cpp @@ -38,7 +38,7 @@ AlbumProxyModel::AlbumProxyModel( QObject* parent ) setSortCaseSensitivity( Qt::CaseInsensitive ); setDynamicSortFilter( true ); - setSourceModel( 0 ); + setSourceAlbumModel( 0 ); } void diff --git a/src/libtomahawk/playlist/albumview.cpp b/src/libtomahawk/playlist/albumview.cpp index 9f6ad1a57..351fd8ea2 100644 --- a/src/libtomahawk/playlist/albumview.cpp +++ b/src/libtomahawk/playlist/albumview.cpp @@ -87,7 +87,7 @@ AlbumView::setAlbumModel( AlbumModel* model ) if ( m_proxyModel ) { - m_proxyModel->setSourceModel( m_model ); + m_proxyModel->setSourceAlbumModel( m_model ); m_proxyModel->sort( 0 ); } diff --git a/src/libtomahawk/playlist/collectionview.cpp b/src/libtomahawk/playlist/collectionview.cpp index ff538e3c1..6d93742f6 100644 --- a/src/libtomahawk/playlist/collectionview.cpp +++ b/src/libtomahawk/playlist/collectionview.cpp @@ -62,7 +62,7 @@ CollectionView::setModel( QAbstractItemModel* model ) void CollectionView::setTrackModel( TrackModel* model ) { - TrackView::setModel( model ); + TrackView::setTrackModel( model ); setGuid( "collectionview" ); connect( model, SIGNAL( trackCountChanged( unsigned int ) ), SLOT( onTrackCountChanged( unsigned int ) ) ); diff --git a/src/libtomahawk/playlist/playlistmanager.cpp b/src/libtomahawk/playlist/playlistmanager.cpp index 1fada1bc0..a1fec6071 100644 --- a/src/libtomahawk/playlist/playlistmanager.cpp +++ b/src/libtomahawk/playlist/playlistmanager.cpp @@ -89,7 +89,7 @@ PlaylistManager::PlaylistManager( QObject* parent ) m_queueView = new QueueView( m_splitter ); m_queueModel = new PlaylistModel( m_queueView ); - m_queueView->queue()->setModel( m_queueModel ); + m_queueView->queue()->setPlaylistModel( m_queueModel ); AudioEngine::instance()->setQueue( m_queueView->queue()->proxyModel() ); m_splitter->addWidget( m_queueView ); @@ -102,14 +102,14 @@ PlaylistManager::PlaylistManager( QObject* parent ) m_superCollectionView = new CollectionView(); m_superCollectionFlatModel = new CollectionFlatModel( m_superCollectionView ); - m_superCollectionView->setModel( m_superCollectionFlatModel ); + m_superCollectionView->setTrackModel( m_superCollectionFlatModel ); m_superCollectionView->setFrameShape( QFrame::NoFrame ); m_superCollectionView->setAttribute( Qt::WA_MacShowFocusRect, 0 ); m_superCollectionView->proxyModel()->setShowOfflineResults( false ); m_superAlbumView = new AlbumView(); m_superAlbumModel = new AlbumModel( m_superAlbumView ); - m_superAlbumView->setModel( m_superAlbumModel ); + m_superAlbumView->setAlbumModel( m_superAlbumModel ); m_superAlbumView->setFrameShape( QFrame::NoFrame ); m_superAlbumView->setAttribute( Qt::WA_MacShowFocusRect, 0 ); diff --git a/src/libtomahawk/playlist/playlistview.cpp b/src/libtomahawk/playlist/playlistview.cpp index 0ba3373db..175d0705a 100644 --- a/src/libtomahawk/playlist/playlistview.cpp +++ b/src/libtomahawk/playlist/playlistview.cpp @@ -58,7 +58,7 @@ PlaylistView::setPlaylistModel( PlaylistModel* model ) { m_model = model; - TrackView::setModel( m_model ); + TrackView::setTrackModel( m_model ); setColumnHidden( 5, true ); // Hide age column per default if ( !m_model->playlist().isNull() ) diff --git a/src/libtomahawk/playlist/trackproxymodel.cpp b/src/libtomahawk/playlist/trackproxymodel.cpp index 2c11bcd6d..91283c7ad 100644 --- a/src/libtomahawk/playlist/trackproxymodel.cpp +++ b/src/libtomahawk/playlist/trackproxymodel.cpp @@ -40,7 +40,7 @@ TrackProxyModel::TrackProxyModel( QObject* parent ) setSortCaseSensitivity( Qt::CaseInsensitive ); setDynamicSortFilter( true ); - setSourceModel( 0 ); + setSourceTrackModel( 0 ); } diff --git a/src/libtomahawk/playlist/trackview.cpp b/src/libtomahawk/playlist/trackview.cpp index ce1240b1b..4016382d8 100644 --- a/src/libtomahawk/playlist/trackview.cpp +++ b/src/libtomahawk/playlist/trackview.cpp @@ -124,7 +124,7 @@ TrackView::setTrackModel( TrackModel* model ) if ( m_proxyModel ) { - m_proxyModel->setSourceModel( m_model ); + m_proxyModel->setSourceTrackModel( m_model ); } connect( m_model, SIGNAL( itemSizeChanged( QModelIndex ) ), SLOT( onItemResized( QModelIndex ) ) ); diff --git a/src/libtomahawk/widgets/welcomewidget.cpp b/src/libtomahawk/widgets/welcomewidget.cpp index 55154e32f..5297710e5 100644 --- a/src/libtomahawk/widgets/welcomewidget.cpp +++ b/src/libtomahawk/widgets/welcomewidget.cpp @@ -47,7 +47,7 @@ WelcomeWidget::WelcomeWidget( QWidget* parent ) ui->tracksView->overlay()->setEnabled( false ); m_tracksModel = new PlaylistModel( ui->tracksView ); - ui->tracksView->setModel( m_tracksModel ); + ui->tracksView->setPlaylistModel( m_tracksModel ); m_tracksModel->loadHistory( Tomahawk::source_ptr(), HISTORY_TRACK_ITEMS ); m_timer = new QTimer( this ); From 9d28c11089db367f7eda5734a11a5a339c32d357 Mon Sep 17 00:00:00 2001 From: Frank Osterfeld Date: Wed, 13 Apr 2011 14:08:11 +0200 Subject: [PATCH 304/329] build on OS X These flags break the build and are also disabled for all the other plugins. --- src/sip/twitter/CMakeLists.txt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/sip/twitter/CMakeLists.txt b/src/sip/twitter/CMakeLists.txt index bb5b26346..f50abba2d 100644 --- a/src/sip/twitter/CMakeLists.txt +++ b/src/sip/twitter/CMakeLists.txt @@ -47,7 +47,7 @@ target_link_libraries( tomahawk_siptwitter ) IF( APPLE ) - SET( CMAKE_SHARED_LINKER_FLAGS ${CMAKE_SHARED_LINKER_FLAGS} "-undefined dynamic_lookup" ) +# SET( CMAKE_SHARED_LINKER_FLAGS ${CMAKE_SHARED_LINKER_FLAGS} "-undefined dynamic_lookup" ) ENDIF( APPLE ) install( TARGETS tomahawk_siptwitter DESTINATION lib${LIB_SUFFIX} ) From 9b5be063bd35b593fba1c6e6097094bec98d60d1 Mon Sep 17 00:00:00 2001 From: Frank Osterfeld Date: Wed, 13 Apr 2011 14:08:55 +0200 Subject: [PATCH 305/329] use setTrackModel, as CollectionView::setModel() suggests. Fixes assertion when selecting a collection. --- src/libtomahawk/playlist/playlistmanager.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/libtomahawk/playlist/playlistmanager.cpp b/src/libtomahawk/playlist/playlistmanager.cpp index a1fec6071..78cf5d809 100644 --- a/src/libtomahawk/playlist/playlistmanager.cpp +++ b/src/libtomahawk/playlist/playlistmanager.cpp @@ -269,7 +269,7 @@ PlaylistManager::show( const Tomahawk::collection_ptr& collection ) { view = new CollectionView(); CollectionFlatModel* model = new CollectionFlatModel(); - view->setModel( model ); + view->setTrackModel( model ); view->setFrameShape( QFrame::NoFrame ); view->setAttribute( Qt::WA_MacShowFocusRect, 0 ); From 34fe17a3e6730d27129ebaa6bf599151b0ee46e5 Mon Sep 17 00:00:00 2001 From: Leo Franchi Date: Wed, 13 Apr 2011 08:58:36 -0400 Subject: [PATCH 306/329] assert less :( --- src/libtomahawk/playlist/playlistmanager.cpp | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/src/libtomahawk/playlist/playlistmanager.cpp b/src/libtomahawk/playlist/playlistmanager.cpp index 78cf5d809..94302de16 100644 --- a/src/libtomahawk/playlist/playlistmanager.cpp +++ b/src/libtomahawk/playlist/playlistmanager.cpp @@ -156,7 +156,7 @@ PlaylistManager::show( const Tomahawk::playlist_ptr& playlist ) { view = new PlaylistView(); PlaylistModel* model = new PlaylistModel(); - view->setModel( model ); + view->setPlaylistModel( model ); view->setFrameShape( QFrame::NoFrame ); view->setAttribute( Qt::WA_MacShowFocusRect, 0 ); model->loadPlaylist( playlist ); @@ -212,7 +212,7 @@ PlaylistManager::show( const Tomahawk::artist_ptr& artist ) { view = new PlaylistView(); PlaylistModel* model = new PlaylistModel(); - view->setModel( model ); + view->setPlaylistModel( model ); view->setFrameShape( QFrame::NoFrame ); view->setAttribute( Qt::WA_MacShowFocusRect, 0 ); model->append( artist ); @@ -239,7 +239,7 @@ PlaylistManager::show( const Tomahawk::album_ptr& album ) { view = new PlaylistView(); PlaylistModel* model = new PlaylistModel(); - view->setModel( model ); + view->setPlaylistModel( model ); view->setFrameShape( QFrame::NoFrame ); view->setAttribute( Qt::WA_MacShowFocusRect, 0 ); model->append( album ); @@ -292,7 +292,7 @@ PlaylistManager::show( const Tomahawk::collection_ptr& collection ) { aview = new AlbumView(); AlbumModel* amodel = new AlbumModel( aview ); - aview->setModel( amodel ); + aview->setAlbumModel( amodel ); aview->setFrameShape( QFrame::NoFrame ); aview->setAttribute( Qt::WA_MacShowFocusRect, 0 ); amodel->addCollection( collection ); From 1c409e40f29fa95e34af07288cac53d8c766285f Mon Sep 17 00:00:00 2001 From: Leo Franchi Date: Wed, 13 Apr 2011 10:34:40 -0400 Subject: [PATCH 307/329] more crash fixes..... --- .../playlist/dynamic/DynamicView.cpp | 48 +++++++++---------- .../dynamic/widgets/DynamicWidget.cpp | 2 +- 2 files changed, 25 insertions(+), 25 deletions(-) diff --git a/src/libtomahawk/playlist/dynamic/DynamicView.cpp b/src/libtomahawk/playlist/dynamic/DynamicView.cpp index 85b87aa53..ab1a67deb 100644 --- a/src/libtomahawk/playlist/dynamic/DynamicView.cpp +++ b/src/libtomahawk/playlist/dynamic/DynamicView.cpp @@ -1,5 +1,5 @@ /* === This file is part of Tomahawk Player - === - * + * * Copyright 2010-2011, Christian Muehlhaeuser * * Tomahawk is free software: you can redistribute it and/or modify @@ -49,18 +49,18 @@ DynamicView::DynamicView( QWidget* parent ) setContentsMargins( 0, 0, 0, 0 ); setFrameShape( QFrame::NoFrame ); setAttribute( Qt::WA_MacShowFocusRect, 0 ); - + m_fadeOutAnim.setDuration( FADE_LENGTH ); m_fadeOutAnim.setCurveShape( QTimeLine::LinearCurve ); m_fadeOutAnim.setFrameRange( 100, 0 ); m_fadeOutAnim.setUpdateInterval( 5 ); - + QEasingCurve curve( QEasingCurve::OutBounce ); curve.setAmplitude( .25 ); m_slideAnim.setEasingCurve( curve ); m_slideAnim.setDirection( QTimeLine::Forward ); m_fadeOutAnim.setUpdateInterval( 5 ); - + connect( &m_fadeOutAnim, SIGNAL( frameChanged( int ) ), viewport(), SLOT( update() ) ); connect( &m_fadeOutAnim, SIGNAL( finished() ), this, SLOT( animFinished() ) ); } @@ -70,12 +70,12 @@ DynamicView::~DynamicView() } -void +void DynamicView::setDynamicModel( DynamicModel* model) { m_model = model; - PlaylistView::setModel( m_model ); - + PlaylistView::setPlaylistModel( m_model ); + connect( m_model, SIGNAL( trackCountChanged( unsigned int ) ), SLOT( onTrackCountChanged( unsigned int ) ) ); connect( m_model, SIGNAL( checkForOverflow() ), this, SLOT( checkForOverflow() ) ); } @@ -84,37 +84,37 @@ void DynamicView::setOnDemand( bool onDemand ) { m_onDemand = onDemand; - + if( m_onDemand ) setVerticalScrollBarPolicy( Qt::ScrollBarAlwaysOff ); else setVerticalScrollBarPolicy( Qt::ScrollBarAsNeeded ); } -void +void DynamicView::setReadOnly( bool readOnly ) { m_readOnly = readOnly; } -void +void DynamicView::showMessageTimeout( const QString& title, const QString& body ) { m_title = title; m_body = body; - + overlay()->setText( QString( "%1:\n\n%2" ).arg( m_title, m_body ) ); overlay()->show( 10 ); } -void +void DynamicView::showMessage(const QString& message) { overlay()->setText( message ); overlay()->show(); } -void +void DynamicView::setDynamicWorking(bool working) { m_working = working; @@ -125,7 +125,7 @@ DynamicView::setDynamicWorking(bool working) } -void +void DynamicView::onTrackCountChanged( unsigned int tracks ) { if ( tracks == 0 && !m_working ) @@ -173,7 +173,7 @@ DynamicView::checkForOverflow() } } -void +void DynamicView::collapseEntries( int startRow, int num, int numToKeep ) { qDebug() << "BEGINNING TO COLLAPSE FROM" << startRow << num << numToKeep; @@ -192,7 +192,7 @@ DynamicView::collapseEntries( int startRow, int num, int numToKeep ) } else { m_fadeOnly = false; } - + // we capture the image of the rows we're going to collapse // then we capture the image of the target row we're going to animate downwards // then we fade the first image out while sliding the second image up. @@ -204,7 +204,7 @@ DynamicView::collapseEntries( int startRow, int num, int numToKeep ) QRect fadingRectViewport = fadingRect; // all values that we use in paintEvent() have to be in viewport coords fadingRect.moveTo( viewport()->mapTo( this, fadingRect.topLeft() ) ); //fadingRect.setBottom( qMin( fadingRect.bottom(), viewport()->mapTo( this, viewport()->rect().bottomLeft() ).y() ) ); // limit what we capture to the viewport rect, if the last item is partially obscured - + m_fadingIndexes = QPixmap::grabWidget( this, fadingRect ); // but all values we use to grab the widgetr have to be in scrollarea coords :( m_fadingPointAnchor = QPoint( 0, fadingRectViewport.topLeft().y() ); @@ -212,7 +212,7 @@ DynamicView::collapseEntries( int startRow, int num, int numToKeep ) m_bg = backgroundBetween( m_fadingIndexes.rect(), startRow ); m_fadeOutAnim.start(); - + qDebug() << "Grabbed fading indexes from rect:" << fadingRect << m_fadingIndexes.size() << "ANCHORED:" << m_fadingPointAnchor; if( !m_fadeOnly ) { @@ -233,7 +233,7 @@ DynamicView::collapseEntries( int startRow, int num, int numToKeep ) QRect slidingRectViewport = slidingRect; // map internal view coord to external qscrollarea slidingRect.moveTo( viewport()->mapTo( this, slidingRect.topLeft() ) ); - + m_slidingIndex = QPixmap::grabWidget( this, slidingRect ); m_bottomAnchor = QPoint( 0, slidingRectViewport.topLeft().y() ); m_bottomOfAnim = QPoint( 0, slidingRectViewport.bottomLeft().y() ); @@ -246,7 +246,7 @@ DynamicView::collapseEntries( int startRow, int num, int numToKeep ) QTimer::singleShot( SLIDE_OFFSET, &m_slideAnim, SLOT( start() ) ); } - + // delete the actual indices QModelIndexList todel; for( int i = 0; i < num; i++ ) { @@ -294,11 +294,11 @@ DynamicView::animFinished() m_checkOnCollapse = false; } -void +void DynamicView::paintEvent( QPaintEvent* event ) { TrackView::paintEvent(event); - + QPainter p( viewport() ); if( m_fadeOutAnim.state() == QTimeLine::Running ) { // both run together p.save(); @@ -316,8 +316,8 @@ DynamicView::paintEvent( QPaintEvent* event ) // qDebug() << "FAST SETOPACITY:" << p.paintEngine()->hasFeature(QPaintEngine::ConstantOpacity); p.setOpacity( 1 - m_fadeOutAnim.currentValue() ); p.drawPixmap( m_fadingPointAnchor, m_fadingIndexes ); - p.restore(); - + p.restore(); + if( m_slideAnim.state() == QTimeLine::Running ) { // draw the collapsing entry p.drawPixmap( 0, m_slideAnim.currentFrame(), m_slidingIndex ); diff --git a/src/libtomahawk/playlist/dynamic/widgets/DynamicWidget.cpp b/src/libtomahawk/playlist/dynamic/widgets/DynamicWidget.cpp index 0aa65d3bc..60a4fc52d 100644 --- a/src/libtomahawk/playlist/dynamic/widgets/DynamicWidget.cpp +++ b/src/libtomahawk/playlist/dynamic/widgets/DynamicWidget.cpp @@ -65,7 +65,7 @@ DynamicWidget::DynamicWidget( const Tomahawk::dynplaylist_ptr& playlist, QWidget m_model = new DynamicModel( this ); m_view = new DynamicView( this ); - m_view->setModel( m_model ); + m_view->setDynamicModel( m_model ); m_view->setContentsMargins( 0, 0, 0, 0 ); m_layout->addWidget( m_view, 1 ); From f2308beb18d279ed107e2407c978d4cbd2f72385 Mon Sep 17 00:00:00 2001 From: Christian Muehlhaeuser Date: Wed, 6 Apr 2011 12:51:16 +0200 Subject: [PATCH 308/329] * Split up PlItem into Track- and TreeModelItem. --- src/libtomahawk/CMakeLists.txt | 2 ++ src/libtomahawk/playlist/collectionmodel.cpp | 30 +++++++++---------- src/libtomahawk/playlist/collectionmodel.h | 8 ++--- .../playlist/collectionproxymodel.cpp | 5 ---- src/libtomahawk/playlist/trackmodel.cpp | 15 ---------- src/libtomahawk/playlist/trackmodelitem.cpp | 25 +++++----------- src/libtomahawk/playlist/trackmodelitem.h | 3 -- 7 files changed, 28 insertions(+), 60 deletions(-) diff --git a/src/libtomahawk/CMakeLists.txt b/src/libtomahawk/CMakeLists.txt index cdc92f878..b5c91ac14 100644 --- a/src/libtomahawk/CMakeLists.txt +++ b/src/libtomahawk/CMakeLists.txt @@ -90,6 +90,7 @@ set( libSources playlist/trackproxymodel.cpp playlist/trackview.cpp playlist/trackheader.cpp + playlist/treemodelitem.cpp playlist/albumitem.cpp playlist/albummodel.cpp playlist/albumproxymodel.cpp @@ -245,6 +246,7 @@ set( libHeaders playlist/trackproxymodel.h playlist/trackview.h playlist/trackheader.h + playlist/treemodelitem.h playlist/albumitem.h playlist/albummodel.h playlist/albumproxymodel.h diff --git a/src/libtomahawk/playlist/collectionmodel.cpp b/src/libtomahawk/playlist/collectionmodel.cpp index ec8808764..ded4d0bde 100644 --- a/src/libtomahawk/playlist/collectionmodel.cpp +++ b/src/libtomahawk/playlist/collectionmodel.cpp @@ -49,8 +49,8 @@ CollectionModel::index( int row, int column, const QModelIndex& parent ) const if ( !m_rootItem || row < 0 || column < 0 ) return QModelIndex(); - TrackModelItem* parentItem = itemFromIndex( parent ); - TrackModelItem* childItem = parentItem->children.value( row ); + TreeModelItem* parentItem = itemFromIndex( parent ); + TreeModelItem* childItem = parentItem->children.value( row ); if ( !childItem ) return QModelIndex(); @@ -64,7 +64,7 @@ CollectionModel::rowCount( const QModelIndex& parent ) const if ( parent.column() > 0 ) return 0; - TrackModelItem* parentItem = itemFromIndex( parent ); + TreeModelItem* parentItem = itemFromIndex( parent ); if ( !parentItem ) return 0; @@ -83,15 +83,15 @@ CollectionModel::columnCount( const QModelIndex& parent ) const QModelIndex CollectionModel::parent( const QModelIndex& child ) const { - TrackModelItem* entry = itemFromIndex( child ); + TreeModelItem* entry = itemFromIndex( child ); if ( !entry ) return QModelIndex(); - TrackModelItem* parentEntry = entry->parent; + TreeModelItem* parentEntry = entry->parent; if ( !parentEntry ) return QModelIndex(); - TrackModelItem* grandparentEntry = parentEntry->parent; + TreeModelItem* grandparentEntry = parentEntry->parent; if ( !grandparentEntry ) return QModelIndex(); @@ -106,7 +106,7 @@ CollectionModel::data( const QModelIndex& index, int role ) const if ( role != Qt::DisplayRole ) return QVariant(); - TrackModelItem* entry = itemFromIndex( index ); + TreeModelItem* entry = itemFromIndex( index ); if ( !entry ) return QVariant(); @@ -199,7 +199,7 @@ CollectionModel::removeCollection( const collection_ptr& collection ) disconnect( collection.data(), SIGNAL( tracksFinished( Tomahawk::collection_ptr ) ), this, SLOT( onTracksAddingFinished( Tomahawk::collection_ptr ) ) ); - QList plitems = m_collectionIndex.values( collection ); + QList plitems = m_collectionIndex.values( collection ); m_collectionIndex.remove( collection ); } @@ -210,17 +210,17 @@ CollectionModel::onTracksAdded( const QList& tracks, const { // int c = rowCount( QModelIndex() ); - TrackModelItem* plitem; + TreeModelItem* plitem; foreach( const Tomahawk::query_ptr& query, tracks ) { - TrackModelItem* parent = m_rootItem; + TreeModelItem* parent = m_rootItem; if ( parent->hash.contains( query->artist() ) ) { parent = parent->hash.value( query->artist() ); } else { - parent = new TrackModelItem( query->artist(), m_rootItem ); + parent = new TreeModelItem( query->artist(), m_rootItem ); m_rootItem->hash.insert( query->artist(), parent ); } @@ -232,14 +232,14 @@ CollectionModel::onTracksAdded( const QList& tracks, const } else { - TrackModelItem* subitem = new TrackModelItem( query->album(), parent ); + TreeModelItem* subitem = new TreeModelItem( query->album(), parent ); parent->hash.insert( query->album(), subitem ); parent->childCount++; subitem->childCount++; parent = subitem; } - plitem = new TrackModelItem( query, parent ); + plitem = new TreeModelItem( query, parent ); m_collectionIndex.insertMulti( collection, plitem ); } @@ -275,11 +275,11 @@ CollectionModel::onSourceOffline( Tomahawk::source_ptr src ) } -TrackModelItem* +TreeModelItem* CollectionModel::itemFromIndex( const QModelIndex& index ) const { if ( index.isValid() ) - return static_cast( index.internalPointer() ); + return static_cast( index.internalPointer() ); else { return m_rootItem; diff --git a/src/libtomahawk/playlist/collectionmodel.h b/src/libtomahawk/playlist/collectionmodel.h index 2ba760743..32d0e611b 100644 --- a/src/libtomahawk/playlist/collectionmodel.h +++ b/src/libtomahawk/playlist/collectionmodel.h @@ -23,7 +23,7 @@ #include #include -#include "trackmodelitem.h" +#include "treemodelitem.h" #include "collection.h" #include "query.h" #include "typedefs.h" @@ -62,7 +62,7 @@ public: virtual void setRepeatMode( PlaylistInterface::RepeatMode /*mode*/ ) {} virtual void setShuffled( bool /*shuffled*/ ) {} - TrackModelItem* itemFromIndex( const QModelIndex& index ) const; + TreeModelItem* itemFromIndex( const QModelIndex& index ) const; signals: void repeatModeChanged( PlaylistInterface::RepeatMode mode ); @@ -79,8 +79,8 @@ private slots: void onSourceOffline( Tomahawk::source_ptr src ); private: - TrackModelItem* m_rootItem; - QMap< Tomahawk::collection_ptr, TrackModelItem* > m_collectionIndex; + TreeModelItem* m_rootItem; + QMap< Tomahawk::collection_ptr, TreeModelItem* > m_collectionIndex; }; #endif // COLLECTIONMODEL_H diff --git a/src/libtomahawk/playlist/collectionproxymodel.cpp b/src/libtomahawk/playlist/collectionproxymodel.cpp index f6c5dd7b5..668b3266f 100644 --- a/src/libtomahawk/playlist/collectionproxymodel.cpp +++ b/src/libtomahawk/playlist/collectionproxymodel.cpp @@ -46,11 +46,6 @@ CollectionProxyModel::lessThan( const QModelIndex& left, const QModelIndex& righ const Tomahawk::query_ptr& q1 = p1->query(); const Tomahawk::query_ptr& q2 = p2->query(); - if ( q1.isNull() || q2.isNull() ) - { - return QString::localeAwareCompare( p1->caption, p2->caption ) < 0; - } - QString artist1 = q1->artist(); QString artist2 = q2->artist(); QString album1 = q1->album(); diff --git a/src/libtomahawk/playlist/trackmodel.cpp b/src/libtomahawk/playlist/trackmodel.cpp index 0249aa1d1..51ae0590a 100644 --- a/src/libtomahawk/playlist/trackmodel.cpp +++ b/src/libtomahawk/playlist/trackmodel.cpp @@ -127,21 +127,6 @@ TrackModel::data( const QModelIndex& index, int role ) const return QVariant(); const query_ptr& query = entry->query(); - if ( query.isNull() ) - { - if ( !index.column() ) - { - return entry->caption.isEmpty() ? "Unknown" : entry->caption; - } - - if ( index.column() == 1 ) - { - return entry->childCount; - } - - return QVariant( "" ); - } - if ( !query->numResults() ) { switch( index.column() ) diff --git a/src/libtomahawk/playlist/trackmodelitem.cpp b/src/libtomahawk/playlist/trackmodelitem.cpp index e88659601..b421bb3f8 100644 --- a/src/libtomahawk/playlist/trackmodelitem.cpp +++ b/src/libtomahawk/playlist/trackmodelitem.cpp @@ -56,22 +56,6 @@ TrackModelItem::TrackModelItem( TrackModelItem* parent, QAbstractItemModel* mode } -TrackModelItem::TrackModelItem( const QString& caption, TrackModelItem* parent ) -{ - this->parent = parent; - this->caption = caption; - this->model = parent->model; - childCount = 0; - m_isPlaying = false; - toberemoved = false; - - if ( parent ) - { - parent->children.append( this ); - } -} - - TrackModelItem::TrackModelItem( const Tomahawk::query_ptr& query, TrackModelItem* parent, int row ) : QObject( parent ) { @@ -86,16 +70,21 @@ TrackModelItem::TrackModelItem( const Tomahawk::plentry_ptr& entry, TrackModelIt setupItem( entry->query(), parent, row ); } + const Tomahawk::plentry_ptr& TrackModelItem::entry() const { return m_entry; } + const Tomahawk::query_ptr& TrackModelItem::query() const { - if ( !m_entry.isNull() ) return m_entry->query(); else return m_query; + if ( !m_entry.isNull() ) + return m_entry->query(); + else + return m_query; } @@ -123,7 +112,7 @@ TrackModelItem::setupItem( const Tomahawk::query_ptr& query, TrackModelItem* par m_query = query; if ( query->numResults() ) { - emit dataChanged(); +// emit dataChanged(); } else { diff --git a/src/libtomahawk/playlist/trackmodelitem.h b/src/libtomahawk/playlist/trackmodelitem.h index 70b93923c..3d0118c6c 100644 --- a/src/libtomahawk/playlist/trackmodelitem.h +++ b/src/libtomahawk/playlist/trackmodelitem.h @@ -37,7 +37,6 @@ public: virtual ~TrackModelItem(); explicit TrackModelItem( TrackModelItem* parent = 0, QAbstractItemModel* model = 0 ); - explicit TrackModelItem( const QString& caption, TrackModelItem* parent = 0 ); explicit TrackModelItem( const Tomahawk::query_ptr& query, TrackModelItem* parent = 0, int row = -1 ); explicit TrackModelItem( const Tomahawk::plentry_ptr& entry, TrackModelItem* parent = 0, int row = -1 ); @@ -49,8 +48,6 @@ public: TrackModelItem* parent; QVector children; - QHash hash; - QString caption; int childCount; QPersistentModelIndex index; QAbstractItemModel* model; From d4745e1e40adcec007464b3b5c6709753e6a0693 Mon Sep 17 00:00:00 2001 From: Christian Muehlhaeuser Date: Thu, 7 Apr 2011 06:21:19 +0200 Subject: [PATCH 309/329] * Further progress making a clean(er) tree model. --- src/libtomahawk/CMakeLists.txt | 12 ++++- src/libtomahawk/playlist/albumproxymodel.cpp | 3 +- .../playlist/collectionproxymodel.cpp | 1 - src/libtomahawk/playlist/playlistmanager.cpp | 29 ++++++++-- src/libtomahawk/playlist/playlistmanager.h | 4 ++ src/libtomahawk/playlist/topbar/topbar.cpp | 14 +++-- src/libtomahawk/playlist/trackproxymodel.cpp | 1 - .../{collectionmodel.cpp => treemodel.cpp} | 53 +++++++++++++------ .../{collectionmodel.h => treemodel.h} | 14 +++-- src/sourcetree/sourcetreeview.cpp | 2 - src/sourcetree/sourcetreeview.h | 9 ++-- 11 files changed, 99 insertions(+), 43 deletions(-) rename src/libtomahawk/playlist/{collectionmodel.cpp => treemodel.cpp} (83%) rename src/libtomahawk/playlist/{collectionmodel.h => treemodel.h} (87%) diff --git a/src/libtomahawk/CMakeLists.txt b/src/libtomahawk/CMakeLists.txt index b5c91ac14..77ad46ca1 100644 --- a/src/libtomahawk/CMakeLists.txt +++ b/src/libtomahawk/CMakeLists.txt @@ -74,7 +74,10 @@ set( libSources database/databasecommand_clientauthvalid.cpp database/database.cpp - playlist/collectionmodel.cpp + playlist/treemodel.cpp + playlist/treeproxymodel.cpp + playlist/treeheader.cpp + playlist/treeitemdelegate.cpp playlist/collectionproxymodel.cpp playlist/collectionflatmodel.cpp playlist/collectionview.cpp @@ -96,6 +99,7 @@ set( libSources playlist/albumproxymodel.cpp playlist/albumitemdelegate.cpp playlist/albumview.cpp + playlist/artistview.cpp playlist/topbar/topbar.cpp playlist/topbar/clearbutton.cpp @@ -230,7 +234,10 @@ set( libHeaders network/controlconnection.h network/portfwdthread.h - playlist/collectionmodel.h + playlist/treemodel.h + playlist/treeproxymodel.h + playlist/treeheader.h + playlist/treeitemdelegate.h playlist/collectionproxymodel.h playlist/collectionflatmodel.h playlist/collectionview.h @@ -252,6 +259,7 @@ set( libHeaders playlist/albumproxymodel.h playlist/albumitemdelegate.h playlist/albumview.h + playlist/artistview.h playlist/topbar/topbar.h playlist/topbar/clearbutton.h diff --git a/src/libtomahawk/playlist/albumproxymodel.cpp b/src/libtomahawk/playlist/albumproxymodel.cpp index f35aa9b26..95a764e15 100644 --- a/src/libtomahawk/playlist/albumproxymodel.cpp +++ b/src/libtomahawk/playlist/albumproxymodel.cpp @@ -1,5 +1,5 @@ /* === This file is part of Tomahawk Player - === - * + * * Copyright 2010-2011, Christian Muehlhaeuser * * Tomahawk is free software: you can redistribute it and/or modify @@ -22,7 +22,6 @@ #include #include "query.h" -#include "collectionmodel.h" AlbumProxyModel::AlbumProxyModel( QObject* parent ) diff --git a/src/libtomahawk/playlist/collectionproxymodel.cpp b/src/libtomahawk/playlist/collectionproxymodel.cpp index 668b3266f..4f602e5db 100644 --- a/src/libtomahawk/playlist/collectionproxymodel.cpp +++ b/src/libtomahawk/playlist/collectionproxymodel.cpp @@ -23,7 +23,6 @@ #include "album.h" #include "query.h" -#include "collectionmodel.h" CollectionProxyModel::CollectionProxyModel( QObject* parent ) diff --git a/src/libtomahawk/playlist/playlistmanager.cpp b/src/libtomahawk/playlist/playlistmanager.cpp index 94302de16..6eefc11d4 100644 --- a/src/libtomahawk/playlist/playlistmanager.cpp +++ b/src/libtomahawk/playlist/playlistmanager.cpp @@ -28,7 +28,7 @@ #include "widgets/infowidgets/sourceinfowidget.h" #include "widgets/welcomewidget.h" -#include "collectionmodel.h" +#include "treemodel.h" #include "collectionflatmodel.h" #include "collectionview.h" #include "playlistmodel.h" @@ -36,6 +36,7 @@ #include "queueview.h" #include "trackproxymodel.h" #include "trackmodel.h" +#include "artistview.h" #include "albumview.h" #include "albumproxymodel.h" #include "albummodel.h" @@ -261,6 +262,7 @@ PlaylistManager::show( const Tomahawk::album_ptr& album ) bool PlaylistManager::show( const Tomahawk::collection_ptr& collection ) { + qDebug() << Q_FUNC_INFO << m_currentMode; m_currentCollection = collection; if ( m_currentMode == 0 ) { @@ -285,6 +287,29 @@ PlaylistManager::show( const Tomahawk::collection_ptr& collection ) setPage( view ); } + if ( m_currentMode == 1 ) + { + ArtistView* view; + if ( !m_treeViews.contains( collection ) ) + { + view = new ArtistView(); + TreeModel* model = new TreeModel(); + view->setModel( model ); + view->setFrameShape( QFrame::NoFrame ); + view->setAttribute( Qt::WA_MacShowFocusRect, 0 ); + + model->addCollection( collection ); + + m_treeViews.insert( collection, view ); + } + else + { + view = m_treeViews.value( collection ); + } + + setPage( view ); + } + if ( m_currentMode == 2 ) { AlbumView* aview; @@ -400,8 +425,6 @@ PlaylistManager::setTableMode() void PlaylistManager::setTreeMode() { - return; - qDebug() << Q_FUNC_INFO; m_currentMode = 1; diff --git a/src/libtomahawk/playlist/playlistmanager.h b/src/libtomahawk/playlist/playlistmanager.h index 6c7f66578..bceb89d08 100644 --- a/src/libtomahawk/playlist/playlistmanager.h +++ b/src/libtomahawk/playlist/playlistmanager.h @@ -32,6 +32,7 @@ class AnimatedSplitter; class AlbumModel; class AlbumView; +class ArtistView; class CollectionModel; class CollectionFlatModel; class CollectionView; @@ -40,6 +41,8 @@ class PlaylistView; class QueueView; class TrackProxyModel; class TrackModel; +class TreeProxyModel; +class TreeModel; class TrackView; class SourceInfoWidget; class InfoBar; @@ -167,6 +170,7 @@ private: QHash< Tomahawk::dynplaylist_ptr, Tomahawk::DynamicWidget* > m_dynamicWidgets; QHash< Tomahawk::collection_ptr, CollectionView* > m_collectionViews; + QHash< Tomahawk::collection_ptr, ArtistView* > m_treeViews; QHash< Tomahawk::collection_ptr, AlbumView* > m_collectionAlbumViews; QHash< Tomahawk::artist_ptr, PlaylistView* > m_artistViews; QHash< Tomahawk::album_ptr, PlaylistView* > m_albumViews; diff --git a/src/libtomahawk/playlist/topbar/topbar.cpp b/src/libtomahawk/playlist/topbar/topbar.cpp index 5018092f3..b60741a9e 100644 --- a/src/libtomahawk/playlist/topbar/topbar.cpp +++ b/src/libtomahawk/playlist/topbar/topbar.cpp @@ -1,5 +1,5 @@ /* === This file is part of Tomahawk Player - === - * + * * Copyright 2010-2011, Christian Muehlhaeuser * * Tomahawk is free software: you can redistribute it and/or modify @@ -72,8 +72,6 @@ TopBar::TopBar( QWidget* parent ) ui->radioDetailed->setFocusPolicy( Qt::NoFocus ); ui->radioCloud->setFocusPolicy( Qt::NoFocus ); - ui->radioDetailed->setEnabled( false ); - connect( ui->radioNormal, SIGNAL( clicked() ), SIGNAL( flatMode() ) ); connect( ui->radioDetailed, SIGNAL( clicked() ), SIGNAL( artistMode() ) ); connect( ui->radioCloud, SIGNAL( clicked() ), SIGNAL( albumMode() ) ); @@ -87,19 +85,19 @@ TopBar::TopBar( QWidget* parent ) connect( PlaylistManager::instance(), SIGNAL( numSourcesChanged( unsigned int ) ), SLOT( setNumSources( unsigned int ) ) ); - + connect( PlaylistManager::instance(), SIGNAL( numTracksChanged( unsigned int ) ), SLOT( setNumTracks( unsigned int ) ) ); - + connect( PlaylistManager::instance(), SIGNAL( numArtistsChanged( unsigned int ) ), SLOT( setNumArtists( unsigned int ) ) ); - + connect( PlaylistManager::instance(), SIGNAL( numShownChanged( unsigned int ) ), SLOT( setNumShown( unsigned int ) ) ); - + connect( PlaylistManager::instance(), SIGNAL( statsAvailable( bool ) ), SLOT( setStatsVisible( bool ) ) ); - + connect( PlaylistManager::instance(), SIGNAL( modesAvailable( bool ) ), SLOT( setModesVisible( bool ) ) ); diff --git a/src/libtomahawk/playlist/trackproxymodel.cpp b/src/libtomahawk/playlist/trackproxymodel.cpp index 91283c7ad..2ed2c94b1 100644 --- a/src/libtomahawk/playlist/trackproxymodel.cpp +++ b/src/libtomahawk/playlist/trackproxymodel.cpp @@ -23,7 +23,6 @@ #include "album.h" #include "query.h" -#include "collectionmodel.h" TrackProxyModel::TrackProxyModel( QObject* parent ) diff --git a/src/libtomahawk/playlist/collectionmodel.cpp b/src/libtomahawk/playlist/treemodel.cpp similarity index 83% rename from src/libtomahawk/playlist/collectionmodel.cpp rename to src/libtomahawk/playlist/treemodel.cpp index ded4d0bde..814134dbb 100644 --- a/src/libtomahawk/playlist/collectionmodel.cpp +++ b/src/libtomahawk/playlist/treemodel.cpp @@ -16,7 +16,7 @@ * along with Tomahawk. If not, see . */ -#include "collectionmodel.h" +#include "treemodel.h" #include #include @@ -28,7 +28,7 @@ using namespace Tomahawk; -CollectionModel::CollectionModel( QObject* parent ) +TreeModel::TreeModel( QObject* parent ) : QAbstractItemModel( parent ) , m_rootItem( 0 ) { @@ -38,13 +38,13 @@ CollectionModel::CollectionModel( QObject* parent ) } -CollectionModel::~CollectionModel() +TreeModel::~TreeModel() { } QModelIndex -CollectionModel::index( int row, int column, const QModelIndex& parent ) const +TreeModel::index( int row, int column, const QModelIndex& parent ) const { if ( !m_rootItem || row < 0 || column < 0 ) return QModelIndex(); @@ -59,7 +59,7 @@ CollectionModel::index( int row, int column, const QModelIndex& parent ) const int -CollectionModel::rowCount( const QModelIndex& parent ) const +TreeModel::rowCount( const QModelIndex& parent ) const { if ( parent.column() > 0 ) return 0; @@ -73,7 +73,7 @@ CollectionModel::rowCount( const QModelIndex& parent ) const int -CollectionModel::columnCount( const QModelIndex& parent ) const +TreeModel::columnCount( const QModelIndex& parent ) const { Q_UNUSED( parent ); return 4; @@ -81,7 +81,7 @@ CollectionModel::columnCount( const QModelIndex& parent ) const QModelIndex -CollectionModel::parent( const QModelIndex& child ) const +TreeModel::parent( const QModelIndex& child ) const { TreeModelItem* entry = itemFromIndex( child ); if ( !entry ) @@ -101,7 +101,7 @@ CollectionModel::parent( const QModelIndex& child ) const QVariant -CollectionModel::data( const QModelIndex& index, int role ) const +TreeModel::data( const QModelIndex& index, int role ) const { if ( role != Qt::DisplayRole ) return QVariant(); @@ -162,7 +162,7 @@ CollectionModel::data( const QModelIndex& index, int role ) const QVariant -CollectionModel::headerData( int section, Qt::Orientation orientation, int role ) const +TreeModel::headerData( int section, Qt::Orientation orientation, int role ) const { QStringList headers; headers << tr( "Name" ) << tr( "Tracks" ) << tr( "Duration" ) << tr( "Origin" ); @@ -176,7 +176,30 @@ CollectionModel::headerData( int section, Qt::Orientation orientation, int role void -CollectionModel::addCollection( const collection_ptr& collection ) +TreeModel::setCurrentItem( const QModelIndex& index ) +{ + qDebug() << Q_FUNC_INFO; + TreeModelItem* oldEntry = itemFromIndex( m_currentIndex ); + if ( oldEntry ) + { + oldEntry->setIsPlaying( false ); + } + + TreeModelItem* entry = itemFromIndex( index ); + if ( entry ) + { + m_currentIndex = index; + entry->setIsPlaying( true ); + } + else + { + m_currentIndex = QModelIndex(); + } +} + + +void +TreeModel::addCollection( const collection_ptr& collection ) { qDebug() << Q_FUNC_INFO << collection->name() << collection->source()->id() @@ -192,7 +215,7 @@ CollectionModel::addCollection( const collection_ptr& collection ) void -CollectionModel::removeCollection( const collection_ptr& collection ) +TreeModel::removeCollection( const collection_ptr& collection ) { disconnect( collection.data(), SIGNAL( tracksAdded( QList, Tomahawk::collection_ptr ) ), this, SLOT( onTracksAdded( QList, Tomahawk::collection_ptr ) ) ); @@ -206,7 +229,7 @@ CollectionModel::removeCollection( const collection_ptr& collection ) void -CollectionModel::onTracksAdded( const QList& tracks, const collection_ptr& collection ) +TreeModel::onTracksAdded( const QList& tracks, const collection_ptr& collection ) { // int c = rowCount( QModelIndex() ); @@ -250,7 +273,7 @@ CollectionModel::onTracksAdded( const QList& tracks, const void -CollectionModel::onTracksAddingFinished( const Tomahawk::collection_ptr& collection ) +TreeModel::onTracksAddingFinished( const Tomahawk::collection_ptr& collection ) { qDebug() << "Finished loading tracks" << collection->source()->friendlyName(); @@ -264,7 +287,7 @@ CollectionModel::onTracksAddingFinished( const Tomahawk::collection_ptr& collect void -CollectionModel::onSourceOffline( Tomahawk::source_ptr src ) +TreeModel::onSourceOffline( Tomahawk::source_ptr src ) { qDebug() << Q_FUNC_INFO; @@ -276,7 +299,7 @@ CollectionModel::onSourceOffline( Tomahawk::source_ptr src ) TreeModelItem* -CollectionModel::itemFromIndex( const QModelIndex& index ) const +TreeModel::itemFromIndex( const QModelIndex& index ) const { if ( index.isValid() ) return static_cast( index.internalPointer() ); diff --git a/src/libtomahawk/playlist/collectionmodel.h b/src/libtomahawk/playlist/treemodel.h similarity index 87% rename from src/libtomahawk/playlist/collectionmodel.h rename to src/libtomahawk/playlist/treemodel.h index 32d0e611b..393a9ed6d 100644 --- a/src/libtomahawk/playlist/collectionmodel.h +++ b/src/libtomahawk/playlist/treemodel.h @@ -34,13 +34,13 @@ class QMetaData; -class DLLEXPORT CollectionModel : public QAbstractItemModel +class DLLEXPORT TreeModel : public QAbstractItemModel { Q_OBJECT public: - explicit CollectionModel( QObject* parent = 0 ); - ~CollectionModel(); + explicit TreeModel( QObject* parent = 0 ); + ~TreeModel(); QModelIndex index( int row, int column, const QModelIndex& parent ) const; QModelIndex parent( const QModelIndex& child ) const; @@ -50,12 +50,14 @@ public: int rowCount( const QModelIndex& parent ) const; int columnCount( const QModelIndex& parent ) const; - QVariant data( const QModelIndex& index, int role ) const; + QVariant data( const QModelIndex& index, int role = Qt::DisplayRole ) const; QVariant headerData( int section, Qt::Orientation orientation, int role ) const; void addCollection( const Tomahawk::collection_ptr& collection ); void removeCollection( const Tomahawk::collection_ptr& collection ); + virtual QPersistentModelIndex currentItem() { return m_currentIndex; } + virtual PlaylistInterface::RepeatMode repeatMode() const { return PlaylistInterface::NoRepeat; } virtual bool shuffled() const { return false; } @@ -64,6 +66,9 @@ public: TreeModelItem* itemFromIndex( const QModelIndex& index ) const; +public slots: + virtual void setCurrentItem( const QModelIndex& index ); + signals: void repeatModeChanged( PlaylistInterface::RepeatMode mode ); void shuffleModeChanged( bool enabled ); @@ -81,6 +86,7 @@ private slots: private: TreeModelItem* m_rootItem; QMap< Tomahawk::collection_ptr, TreeModelItem* > m_collectionIndex; + QPersistentModelIndex m_currentIndex; }; #endif // COLLECTIONMODEL_H diff --git a/src/sourcetree/sourcetreeview.cpp b/src/sourcetree/sourcetreeview.cpp index 4ceccd2c1..4935b8d0e 100644 --- a/src/sourcetree/sourcetreeview.cpp +++ b/src/sourcetree/sourcetreeview.cpp @@ -19,7 +19,6 @@ #include "sourcetreeview.h" #include "playlist.h" -#include "playlist/collectionmodel.h" #include "playlist/playlistmanager.h" #include "sourcetreeitem.h" #include "sourcesmodel.h" @@ -61,7 +60,6 @@ private: SourceTreeView::SourceTreeView( QWidget* parent ) : QTreeView( parent ) - , m_collectionModel( new CollectionModel( this ) ) , m_dragging( false ) { setFrameShape( QFrame::NoFrame ); diff --git a/src/sourcetree/sourcetreeview.h b/src/sourcetree/sourcetreeview.h index e9cbe51b0..f2d8581e3 100644 --- a/src/sourcetree/sourcetreeview.h +++ b/src/sourcetree/sourcetreeview.h @@ -1,5 +1,5 @@ /* === This file is part of Tomahawk Player - === - * + * * Copyright 2010-2011, Christian Muehlhaeuser * * Tomahawk is free software: you can redistribute it and/or modify @@ -39,7 +39,7 @@ public: public slots: void showOfflineSources(); void hideOfflineSources(); - + signals: void onOnline( const QModelIndex& index ); void onOffline( const QModelIndex& index ); @@ -50,14 +50,14 @@ private slots: void onCollectionActivated( const Tomahawk::collection_ptr& collection ); void onSuperCollectionActivated(); void onTempPageActivated(); - + void onItemActivated( const QModelIndex& index ); void onSelectionChanged(); void loadPlaylist(); void deletePlaylist(); void renamePlaylist(); - + void onCustomContextMenu( const QPoint& pos ); void onSourceOffline( Tomahawk::source_ptr ); @@ -75,7 +75,6 @@ protected: private: void setupMenus(); - CollectionModel* m_collectionModel; SourcesModel* m_model; SourcesProxyModel* m_proxyModel; QModelIndex m_contextMenuIndex; From 43a08b795746ae9bf7068236546f85aea587dc9c Mon Sep 17 00:00:00 2001 From: Christian Muehlhaeuser Date: Sun, 10 Apr 2011 09:24:24 +0200 Subject: [PATCH 310/329] * Work in progress: The ArtistView & TreeModel. --- src/audiocontrols.cpp | 47 +- src/audiocontrols.h | 7 +- src/libtomahawk/CMakeLists.txt | 2 + src/libtomahawk/database/databasecommand.h | 7 +- .../database/databasecommand_allalbums.cpp | 103 +++- .../database/databasecommand_allalbums.h | 14 +- .../database/databasecommand_allartists.cpp | 68 +++ .../database/databasecommand_allartists.h | 69 +++ .../database/databasecommand_alltracks.cpp | 7 +- .../database/databasecommand_alltracks.h | 4 +- src/libtomahawk/playlist/albummodel.cpp | 8 +- src/libtomahawk/playlist/albummodel.h | 6 +- src/libtomahawk/playlist/artistview.cpp | 152 ++++++ src/libtomahawk/playlist/artistview.h | 79 +++ src/libtomahawk/playlist/playlistmanager.cpp | 18 +- src/libtomahawk/playlist/playlistmanager.h | 4 +- src/libtomahawk/playlist/treeheader.cpp | 145 +++++ src/libtomahawk/playlist/treeheader.h | 61 +++ src/libtomahawk/playlist/treeitemdelegate.cpp | 101 ++++ src/libtomahawk/playlist/treeitemdelegate.h | 46 ++ src/libtomahawk/playlist/treemodel.cpp | 494 +++++++++++++----- src/libtomahawk/playlist/treemodel.h | 101 ++-- src/libtomahawk/playlist/treemodelitem.cpp | 133 +++++ src/libtomahawk/playlist/treemodelitem.h | 69 +++ src/libtomahawk/playlist/treeproxymodel.cpp | 158 ++++++ src/libtomahawk/playlist/treeproxymodel.h | 81 +++ 26 files changed, 1734 insertions(+), 250 deletions(-) create mode 100644 src/libtomahawk/database/databasecommand_allartists.cpp create mode 100644 src/libtomahawk/database/databasecommand_allartists.h create mode 100644 src/libtomahawk/playlist/artistview.cpp create mode 100644 src/libtomahawk/playlist/artistview.h create mode 100644 src/libtomahawk/playlist/treeheader.cpp create mode 100644 src/libtomahawk/playlist/treeheader.h create mode 100644 src/libtomahawk/playlist/treeitemdelegate.cpp create mode 100644 src/libtomahawk/playlist/treeitemdelegate.h create mode 100644 src/libtomahawk/playlist/treemodelitem.cpp create mode 100644 src/libtomahawk/playlist/treemodelitem.h create mode 100644 src/libtomahawk/playlist/treeproxymodel.cpp create mode 100644 src/libtomahawk/playlist/treeproxymodel.h diff --git a/src/audiocontrols.cpp b/src/audiocontrols.cpp index e7b0e8739..4465e266b 100644 --- a/src/audiocontrols.cpp +++ b/src/audiocontrols.cpp @@ -31,7 +31,8 @@ #define LASTFM_DEFAULT_COVER "http://cdn.last.fm/flatness/catalogue/noimage" -static QString s_infoIdentifier = QString("AUDIOCONTROLS"); +static QString s_infoIdentifier = QString( "AUDIOCONTROLS" ); + AudioControls::AudioControls( QWidget* parent ) : QWidget( parent ) @@ -208,40 +209,6 @@ AudioControls::onVolumeChanged( int volume ) } -void -AudioControls::onCoverArtDownloaded() -{ - if ( m_currentTrack.isNull() ) - return; - - QNetworkReply* reply = qobject_cast( sender() ); - QUrl redir = reply->attribute( QNetworkRequest::RedirectionTargetAttribute ).toUrl(); - if ( redir.isEmpty() ) - { - const QByteArray ba = reply->readAll(); - if ( ba.length() ) - { - QPixmap pm; - pm.loadFromData( ba ); - - if ( pm.isNull() || reply->url().toString().startsWith( LASTFM_DEFAULT_COVER ) ) - ui->coverImage->setPixmap( m_defaultCover ); - else - ui->coverImage->setPixmap( pm.scaled( ui->coverImage->size(), Qt::IgnoreAspectRatio, Qt::SmoothTransformation ) ); - } - } - else - { - // Follow HTTP redirect - QNetworkRequest req( redir ); - QNetworkReply* reply = TomahawkUtils::nam()->get( req ); - connect( reply, SIGNAL( finished() ), SLOT( onCoverArtDownloaded() ) ); - } - - reply->deleteLater(); -} - - void AudioControls::onPlaybackStarted( const Tomahawk::result_ptr& result ) { @@ -249,18 +216,16 @@ AudioControls::onPlaybackStarted( const Tomahawk::result_ptr& result ) onPlaybackLoading( result ); - QString artistName = result->artist()->name(); - QString albumName = result->album()->name(); - Tomahawk::InfoSystem::InfoCustomData trackInfo; - trackInfo["artist"] = QVariant::fromValue< QString >( result->artist()->name() ); trackInfo["album"] = QVariant::fromValue< QString >( result->album()->name() ); + TomahawkApp::instance()->infoSystem()->getInfo( s_infoIdentifier, Tomahawk::InfoSystem::InfoAlbumCoverArt, QVariant::fromValue< Tomahawk::InfoSystem::InfoCustomData >( trackInfo ), Tomahawk::InfoSystem::InfoCustomData() ); } + void AudioControls::infoSystemInfo( QString caller, Tomahawk::InfoSystem::InfoType type, QVariant input, QVariant output, Tomahawk::InfoSystem::InfoCustomData customData ) { @@ -270,7 +235,7 @@ AudioControls::infoSystemInfo( QString caller, Tomahawk::InfoSystem::InfoType ty qDebug() << Q_FUNC_INFO; if ( caller != s_infoIdentifier || type != Tomahawk::InfoSystem::InfoAlbumCoverArt ) { - qDebug() << "info of wrong type or not with our identifier"; + qDebug() << "Info of wrong type or not with our identifier"; return; } @@ -300,6 +265,7 @@ AudioControls::infoSystemInfo( QString caller, Tomahawk::InfoSystem::InfoType ty } } + void AudioControls::infoSystemFinished( QString target ) { @@ -307,6 +273,7 @@ AudioControls::infoSystemFinished( QString target ) qDebug() << Q_FUNC_INFO; } + void AudioControls::onPlaybackLoading( const Tomahawk::result_ptr& result ) { diff --git a/src/audiocontrols.h b/src/audiocontrols.h index 3b90005d6..126608a05 100644 --- a/src/audiocontrols.h +++ b/src/audiocontrols.h @@ -41,12 +41,10 @@ public: signals: void playPressed(); void pausePressed(); - + public slots: void onRepeatModeChanged( PlaylistInterface::RepeatMode mode ); void onShuffleModeChanged( bool enabled ); - void infoSystemInfo( QString caller, Tomahawk::InfoSystem::InfoType type, QVariant input, QVariant output, Tomahawk::InfoSystem::InfoCustomData customData ); - void infoSystemFinished( QString target ); protected: void changeEvent( QEvent* e ); @@ -68,7 +66,8 @@ private slots: void onAlbumClicked(); void onTrackClicked(); - void onCoverArtDownloaded(); + void infoSystemInfo( QString caller, Tomahawk::InfoSystem::InfoType type, QVariant input, QVariant output, Tomahawk::InfoSystem::InfoCustomData customData ); + void infoSystemFinished( QString target ); private: Ui::AudioControls *ui; diff --git a/src/libtomahawk/CMakeLists.txt b/src/libtomahawk/CMakeLists.txt index 77ad46ca1..4b945ef7d 100644 --- a/src/libtomahawk/CMakeLists.txt +++ b/src/libtomahawk/CMakeLists.txt @@ -43,6 +43,7 @@ set( libSources database/databasecommand.cpp database/databasecommandloggable.cpp database/databasecommand_resolve.cpp + database/databasecommand_allartists.cpp database/databasecommand_allalbums.cpp database/databasecommand_alltracks.cpp database/databasecommand_addfiles.cpp @@ -194,6 +195,7 @@ set( libHeaders database/databasecommand.h database/databasecommandloggable.h database/databasecommand_resolve.h + database/databasecommand_allartists.h database/databasecommand_allalbums.h database/databasecommand_alltracks.h database/databasecommand_addfiles.h diff --git a/src/libtomahawk/database/databasecommand.h b/src/libtomahawk/database/databasecommand.h index 35fff3dfc..3ce9b6873 100644 --- a/src/libtomahawk/database/databasecommand.h +++ b/src/libtomahawk/database/databasecommand.h @@ -1,5 +1,5 @@ /* === This file is part of Tomahawk Player - === - * + * * Copyright 2010-2011, Christian Muehlhaeuser * * Tomahawk is free software: you can redistribute it and/or modify @@ -76,6 +76,9 @@ public: virtual bool singletonCmd() const { return false; } virtual bool localOnly() const { return false; } + virtual QVariant data() const { return m_data; } + virtual void setData( const QVariant& data ) { m_data = data; } + QString guid() const { if( m_guid.isEmpty() ) @@ -98,6 +101,8 @@ private: State m_state; Tomahawk::source_ptr m_source; mutable QString m_guid; + + QVariant m_data; }; Q_DECLARE_METATYPE( DatabaseCommand ) diff --git a/src/libtomahawk/database/databasecommand_allalbums.cpp b/src/libtomahawk/database/databasecommand_allalbums.cpp index 99af75aff..15aca3ed3 100644 --- a/src/libtomahawk/database/databasecommand_allalbums.cpp +++ b/src/libtomahawk/database/databasecommand_allalbums.cpp @@ -1,5 +1,5 @@ /* === This file is part of Tomahawk Player - === - * + * * Copyright 2010-2011, Christian Muehlhaeuser * * Tomahawk is free software: you can redistribute it and/or modify @@ -22,14 +22,13 @@ #include "databaseimpl.h" -void -DatabaseCommand_AllAlbums::exec( DatabaseImpl* dbi ) -{ - Q_ASSERT( !m_collection->source().isNull() ); +void +DatabaseCommand_AllAlbums::execForArtist( DatabaseImpl* dbi ) +{ TomahawkSqlQuery query = dbi->newquery(); QList al; - QString m_orderToken; + QString orderToken, sourceToken; switch ( m_sortOrder ) { @@ -37,22 +36,70 @@ DatabaseCommand_AllAlbums::exec( DatabaseImpl* dbi ) break; case ModificationTime: - m_orderToken = "file.mtime"; + orderToken = "file.mtime"; + } + + if ( !m_collection.isNull() ) + sourceToken = QString( "AND file.source %1 " ).arg( m_collection->source()->isLocal() ? "IS NULL" : QString( "= %1" ).arg( m_collection->source()->id() ) ); + + QString sql = QString( + "SELECT DISTINCT album.id, album.name " + "FROM album, file, file_join " + "WHERE file.id = file_join.file " + "AND file_join.album = album.id " + "AND album.artist = %1 " + "%2 " + "%3 %4 %5" + ).arg( m_artist->id() ) + .arg( sourceToken ) + .arg( m_sortOrder > 0 ? QString( "ORDER BY %1" ).arg( orderToken ) : QString() ) + .arg( m_sortDescending ? "DESC" : QString() ) + .arg( m_amount > 0 ? QString( "LIMIT 0, %1" ).arg( m_amount ) : QString() ); + + query.prepare( sql ); + query.exec(); + + while( query.next() ) + { + Tomahawk::album_ptr album = Tomahawk::Album::get( query.value( 0 ).toUInt(), query.value( 1 ).toString(), m_artist ); + al << album; + } + + if ( al.count() ) + emit albums( al, data() ); + emit done(); +} + + +void +DatabaseCommand_AllAlbums::execForCollection( DatabaseImpl* dbi ) +{ + TomahawkSqlQuery query = dbi->newquery(); + QList al; + QString orderToken; + + switch ( m_sortOrder ) + { + case 0: + break; + + case ModificationTime: + orderToken = "file.mtime"; } QString sql = QString( - "SELECT DISTINCT album.id, album.name, album.artist, artist.name " - "FROM album, file, file_join " - "LEFT OUTER JOIN artist " - "ON album.artist = artist.id " - "WHERE file.id = file_join.file " - "AND file_join.album = album.id " - "AND file.source %1 " - "%2 %3 %4" - ).arg( m_collection->source()->isLocal() ? "IS NULL" : QString( "= %1" ).arg( m_collection->source()->id() ) ) - .arg( m_sortOrder > 0 ? QString( "ORDER BY %1" ).arg( m_orderToken ) : QString() ) - .arg( m_sortDescending ? "DESC" : QString() ) - .arg( m_amount > 0 ? QString( "LIMIT 0, %1" ).arg( m_amount ) : QString() ); + "SELECT DISTINCT album.id, album.name, album.artist, artist.name " + "FROM album, file, file_join " + "LEFT OUTER JOIN artist " + "ON album.artist = artist.id " + "WHERE file.id = file_join.file " + "AND file_join.album = album.id " + "AND file.source %1 " + "%2 %3 %4" + ).arg( m_collection->source()->isLocal() ? "IS NULL" : QString( "= %1" ).arg( m_collection->source()->id() ) ) + .arg( m_sortOrder > 0 ? QString( "ORDER BY %1" ).arg( orderToken ) : QString() ) + .arg( m_sortDescending ? "DESC" : QString() ) + .arg( m_amount > 0 ? QString( "LIMIT 0, %1" ).arg( m_amount ) : QString() ); query.prepare( sql ); query.exec(); @@ -66,6 +113,20 @@ DatabaseCommand_AllAlbums::exec( DatabaseImpl* dbi ) } if ( al.count() ) - emit albums( al, m_collection ); - emit done( m_collection ); + emit albums( al, data() ); + emit done(); +} + + +void +DatabaseCommand_AllAlbums::exec( DatabaseImpl* dbi ) +{ + if ( !m_artist.isNull() ) + { + execForArtist( dbi ); + } + else + { + execForCollection( dbi ); + } } diff --git a/src/libtomahawk/database/databasecommand_allalbums.h b/src/libtomahawk/database/databasecommand_allalbums.h index 501e9ae5e..8520e0d07 100644 --- a/src/libtomahawk/database/databasecommand_allalbums.h +++ b/src/libtomahawk/database/databasecommand_allalbums.h @@ -1,5 +1,5 @@ /* === This file is part of Tomahawk Player - === - * + * * Copyright 2010-2011, Christian Muehlhaeuser * * Tomahawk is free software: you can redistribute it and/or modify @@ -38,9 +38,10 @@ public: ModificationTime = 1 }; - explicit DatabaseCommand_AllAlbums( const Tomahawk::collection_ptr& collection, QObject* parent = 0 ) + explicit DatabaseCommand_AllAlbums( const Tomahawk::collection_ptr& collection, const Tomahawk::artist_ptr& artist = Tomahawk::artist_ptr(), QObject* parent = 0 ) : DatabaseCommand( parent ) , m_collection( collection ) + , m_artist( artist ) , m_amount( 0 ) , m_sortOrder( DatabaseCommand_AllAlbums::None ) , m_sortDescending( false ) @@ -51,16 +52,21 @@ public: virtual bool doesMutates() const { return false; } virtual QString commandname() const { return "allalbums"; } + void execForCollection( DatabaseImpl* ); + void execForArtist( DatabaseImpl* ); + void setLimit( unsigned int amount ) { m_amount = amount; } void setSortOrder( DatabaseCommand_AllAlbums::SortOrder order ) { m_sortOrder = order; } void setSortDescending( bool descending ) { m_sortDescending = descending; } signals: - void albums( const QList&, const Tomahawk::collection_ptr& ); - void done( const Tomahawk::collection_ptr& ); + void albums( const QList&, const QVariant& data ); + void done(); private: Tomahawk::collection_ptr m_collection; + Tomahawk::artist_ptr m_artist; + unsigned int m_amount; DatabaseCommand_AllAlbums::SortOrder m_sortOrder; bool m_sortDescending; diff --git a/src/libtomahawk/database/databasecommand_allartists.cpp b/src/libtomahawk/database/databasecommand_allartists.cpp new file mode 100644 index 000000000..41b7e3079 --- /dev/null +++ b/src/libtomahawk/database/databasecommand_allartists.cpp @@ -0,0 +1,68 @@ +/* === This file is part of Tomahawk Player - === + * + * Copyright 2010-2011, Christian Muehlhaeuser + * + * Tomahawk is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * Tomahawk is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with Tomahawk. If not, see . + */ + +#include "databasecommand_allartists.h" + +#include + +#include "databaseimpl.h" + +void +DatabaseCommand_AllArtists::exec( DatabaseImpl* dbi ) +{ + TomahawkSqlQuery query = dbi->newquery(); + QList al; + QString orderToken, sourceToken; + + switch ( m_sortOrder ) + { + case 0: + break; + + case ModificationTime: + orderToken = "file.mtime"; + } + + if ( !m_collection.isNull() ) + sourceToken = QString( "AND file.source %1 " ).arg( m_collection->source()->isLocal() ? "IS NULL" : QString( "= %1" ).arg( m_collection->source()->id() ) ); + + QString sql = QString( + "SELECT DISTINCT artist.id, artist.name " + "FROM artist, file, file_join " + "WHERE file.id = file_join.file " + "AND file_join.artist = artist.id " + "%1 %2 %3 %4" + ).arg( sourceToken ) + .arg( m_sortOrder > 0 ? QString( "ORDER BY %1" ).arg( orderToken ) : QString() ) + .arg( m_sortDescending ? "DESC" : QString() ) + .arg( m_amount > 0 ? QString( "LIMIT 0, %1" ).arg( m_amount ) : QString() ); + + query.prepare( sql ); + query.exec(); + + while( query.next() ) + { + Tomahawk::artist_ptr artist = Tomahawk::Artist::get( query.value( 0 ).toUInt(), query.value( 1 ).toString() ); + + al << artist; + } + + if ( al.count() ) + emit artists( al ); + emit done(); +} diff --git a/src/libtomahawk/database/databasecommand_allartists.h b/src/libtomahawk/database/databasecommand_allartists.h new file mode 100644 index 000000000..6d13a107c --- /dev/null +++ b/src/libtomahawk/database/databasecommand_allartists.h @@ -0,0 +1,69 @@ +/* === This file is part of Tomahawk Player - === + * + * Copyright 2010-2011, Christian Muehlhaeuser + * + * Tomahawk is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * Tomahawk is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with Tomahawk. If not, see . + */ + +#ifndef DATABASECOMMAND_ALLARTISTS_H +#define DATABASECOMMAND_ALLARTISTS_H + +#include +#include + +#include "databasecommand.h" +#include "album.h" +#include "collection.h" +#include "typedefs.h" + +#include "dllmacro.h" + +class DLLEXPORT DatabaseCommand_AllArtists : public DatabaseCommand +{ +Q_OBJECT +public: + enum SortOrder { + None = 0, + ModificationTime = 1 + }; + + explicit DatabaseCommand_AllArtists( const Tomahawk::collection_ptr& collection = Tomahawk::collection_ptr(), QObject* parent = 0 ) + : DatabaseCommand( parent ) + , m_collection( collection ) + , m_amount( 0 ) + , m_sortOrder( DatabaseCommand_AllArtists::None ) + , m_sortDescending( false ) + {} + + virtual void exec( DatabaseImpl* ); + + virtual bool doesMutates() const { return false; } + virtual QString commandname() const { return "allartists"; } + + void setLimit( unsigned int amount ) { m_amount = amount; } + void setSortOrder( DatabaseCommand_AllArtists::SortOrder order ) { m_sortOrder = order; } + void setSortDescending( bool descending ) { m_sortDescending = descending; } + +signals: + void artists( const QList& ); + void done(); + +private: + Tomahawk::collection_ptr m_collection; + unsigned int m_amount; + DatabaseCommand_AllArtists::SortOrder m_sortOrder; + bool m_sortDescending; +}; + +#endif // DATABASECOMMAND_ALLARTISTS_H diff --git a/src/libtomahawk/database/databasecommand_alltracks.cpp b/src/libtomahawk/database/databasecommand_alltracks.cpp index 8e2548a3c..57ebf3afa 100644 --- a/src/libtomahawk/database/databasecommand_alltracks.cpp +++ b/src/libtomahawk/database/databasecommand_alltracks.cpp @@ -1,5 +1,5 @@ /* === This file is part of Tomahawk Player - === - * + * * Copyright 2010-2011, Christian Muehlhaeuser * * Tomahawk is free software: you can redistribute it and/or modify @@ -51,7 +51,6 @@ DatabaseCommand_AllTracks::exec( DatabaseImpl* dbi ) break; } - if ( !m_collection.isNull() ) sourceToken = QString( "AND file.source %1" ).arg( m_collection->source()->isLocal() ? "IS NULL" : QString( "= %1" ).arg( m_collection->source()->id() ) ); @@ -138,12 +137,12 @@ DatabaseCommand_AllTracks::exec( DatabaseImpl* dbi ) results << result; qry->addResults( results ); qry->setResolveFinished( true ); - + ql << qry; } qDebug() << Q_FUNC_INFO << ql.length(); - emit tracks( ql ); + emit tracks( ql, data() ); emit done( m_collection ); } diff --git a/src/libtomahawk/database/databasecommand_alltracks.h b/src/libtomahawk/database/databasecommand_alltracks.h index 5b8981aed..335c11585 100644 --- a/src/libtomahawk/database/databasecommand_alltracks.h +++ b/src/libtomahawk/database/databasecommand_alltracks.h @@ -1,5 +1,5 @@ /* === This file is part of Tomahawk Player - === - * + * * Copyright 2010-2011, Christian Muehlhaeuser * * Tomahawk is free software: you can redistribute it and/or modify @@ -63,7 +63,7 @@ public: void setSortDescending( bool descending ) { m_sortDescending = descending; } signals: - void tracks( const QList& ); + void tracks( const QList&, const QVariant& data ); void done( const Tomahawk::collection_ptr& ); private: diff --git a/src/libtomahawk/playlist/albummodel.cpp b/src/libtomahawk/playlist/albummodel.cpp index 6be94ee1a..8a1d36cb2 100644 --- a/src/libtomahawk/playlist/albummodel.cpp +++ b/src/libtomahawk/playlist/albummodel.cpp @@ -1,5 +1,5 @@ /* === This file is part of Tomahawk Player - === - * + * * Copyright 2010-2011, Christian Muehlhaeuser * * Tomahawk is free software: you can redistribute it and/or modify @@ -262,8 +262,8 @@ AlbumModel::addFilteredCollection( const collection_ptr& collection, unsigned in cmd->setSortOrder( order ); cmd->setSortDescending( true ); - connect( cmd, SIGNAL( albums( QList, Tomahawk::collection_ptr ) ), - SLOT( onAlbumsAdded( QList, Tomahawk::collection_ptr ) ) ); + connect( cmd, SIGNAL( albums( QList ) ), + SLOT( onAlbumsAdded( QList ) ) ); Database::instance()->enqueue( QSharedPointer( cmd ) ); @@ -272,7 +272,7 @@ AlbumModel::addFilteredCollection( const collection_ptr& collection, unsigned in void -AlbumModel::onAlbumsAdded( const QList& albums, const Tomahawk::collection_ptr& collection ) +AlbumModel::onAlbumsAdded( const QList& albums ) { Q_UNUSED( collection ); if ( !albums.count() ) diff --git a/src/libtomahawk/playlist/albummodel.h b/src/libtomahawk/playlist/albummodel.h index fd9361b60..ae8658e19 100644 --- a/src/libtomahawk/playlist/albummodel.h +++ b/src/libtomahawk/playlist/albummodel.h @@ -1,5 +1,5 @@ /* === This file is part of Tomahawk Player - === - * + * * Copyright 2010-2011, Christian Muehlhaeuser * * Tomahawk is free software: you can redistribute it and/or modify @@ -69,7 +69,7 @@ public: virtual QString description() const { return m_description; } virtual void setTitle( const QString& title ) { m_title = title; } virtual void setDescription( const QString& description ) { m_description = description; } - + AlbumItem* itemFromIndex( const QModelIndex& index ) const { if ( index.isValid() ) @@ -93,7 +93,7 @@ signals: protected: private slots: - void onAlbumsAdded( const QList& albums, const Tomahawk::collection_ptr& collection ); + void onAlbumsAdded( const QList& albums ); void onCoverArtDownloaded(); void onDataChanged(); diff --git a/src/libtomahawk/playlist/artistview.cpp b/src/libtomahawk/playlist/artistview.cpp new file mode 100644 index 000000000..ab79538b4 --- /dev/null +++ b/src/libtomahawk/playlist/artistview.cpp @@ -0,0 +1,152 @@ +/* === This file is part of Tomahawk Player - === + * + * Copyright 2010-2011, Christian Muehlhaeuser + * + * Tomahawk is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * Tomahawk is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with Tomahawk. If not, see . + */ + +#include "artistview.h" + +#include +#include +#include +#include +#include + +#include "audio/audioengine.h" + +#include "tomahawksettings.h" +#include "treeitemdelegate.h" +#include "playlistmanager.h" + +using namespace Tomahawk; + + +ArtistView::ArtistView( QWidget* parent ) + : QTreeView( parent ) + , m_model( 0 ) + , m_proxyModel( 0 ) +// , m_delegate( 0 ) +{ + setDragEnabled( true ); + setDropIndicatorShown( false ); + setDragDropOverwriteMode( false ); + setUniformRowHeights( false ); + setVerticalScrollMode( QAbstractItemView::ScrollPerPixel ); + setRootIsDecorated( true ); + + setProxyModel( new TreeProxyModel( this ) ); + + connect( this, SIGNAL( doubleClicked( QModelIndex ) ), SLOT( onItemActivated( QModelIndex ) ) ); +} + + +ArtistView::~ArtistView() +{ + qDebug() << Q_FUNC_INFO; +} + + +void +ArtistView::setProxyModel( TreeProxyModel* model ) +{ + m_proxyModel = model; + setItemDelegate( new TreeItemDelegate( this, m_proxyModel ) ); + + QTreeView::setModel( m_proxyModel ); +} + + +void +ArtistView::setModel( TreeModel* model ) +{ + m_model = model; + + if ( m_proxyModel ) + { + m_proxyModel->setSourceModel( model ); + m_proxyModel->sort( 0 ); + } + + connect( m_proxyModel, SIGNAL( filterChanged( QString ) ), SLOT( onFilterChanged( QString ) ) ); + + setAcceptDrops( false ); +} + + +void +ArtistView::onItemActivated( const QModelIndex& index ) +{ + TreeModelItem* item = m_model->itemFromIndex( m_proxyModel->mapToSource( index ) ); + if ( item ) + { + if ( !item->artist().isNull() ) + PlaylistManager::instance()->show( item->artist() ); + else if ( !item->album().isNull() ) + PlaylistManager::instance()->show( item->album() ); + else if ( !item->result().isNull() ) + AudioEngine::instance()->playItem( 0, item->result() ); + } +} + + +void +ArtistView::dragEnterEvent( QDragEnterEvent* event ) +{ + qDebug() << Q_FUNC_INFO; + QTreeView::dragEnterEvent( event ); +} + + +void +ArtistView::dragMoveEvent( QDragMoveEvent* event ) +{ + QTreeView::dragMoveEvent( event ); +} + + +void +ArtistView::dropEvent( QDropEvent* event ) +{ + QTreeView::dropEvent( event ); +} + + +void +ArtistView::paintEvent( QPaintEvent* event ) +{ + QTreeView::paintEvent( event ); +} + + +void +ArtistView::onFilterChanged( const QString& ) +{ + if ( selectedIndexes().count() ) + scrollTo( selectedIndexes().at( 0 ), QAbstractItemView::PositionAtCenter ); +} + + +void +ArtistView::startDrag( Qt::DropActions supportedActions ) +{ +} + + +// Inspired from dolphin's draganddrophelper.cpp +QPixmap +ArtistView::createDragPixmap( int itemCount ) const +{ + return QPixmap(); +} diff --git a/src/libtomahawk/playlist/artistview.h b/src/libtomahawk/playlist/artistview.h new file mode 100644 index 000000000..e22205c24 --- /dev/null +++ b/src/libtomahawk/playlist/artistview.h @@ -0,0 +1,79 @@ +/* === This file is part of Tomahawk Player - === + * + * Copyright 2010-2011, Christian Muehlhaeuser + * + * Tomahawk is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * Tomahawk is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with Tomahawk. If not, see . + */ + +#ifndef ARTISTVIEW_H +#define ARTISTVIEW_H + +#include +#include + +#include "treemodel.h" +#include "treeproxymodel.h" +#include "viewpage.h" + +#include "dllmacro.h" + +class DLLEXPORT ArtistView : public QTreeView, public Tomahawk::ViewPage +{ +Q_OBJECT + +public: + explicit ArtistView( QWidget* parent = 0 ); + ~ArtistView(); + + void setProxyModel( TreeProxyModel* model ); + + TreeModel* model() const { return m_model; } + TreeProxyModel* proxyModel() const { return m_proxyModel; } +// PlaylistItemDelegate* delegate() { return m_delegate; } + + void setModel( TreeModel* model ); + + virtual QWidget* widget() { return this; } + virtual PlaylistInterface* playlistInterface() const { return proxyModel(); } + + virtual QString title() const { return m_model->title(); } + virtual QString description() const { return m_model->description(); } + + virtual bool showModes() const { return true; } + + virtual bool jumpToCurrentTrack() { return false; } + +public slots: + void onItemActivated( const QModelIndex& index ); + +protected: + virtual void startDrag( Qt::DropActions supportedActions ); + virtual void dragEnterEvent( QDragEnterEvent* event ); + virtual void dragMoveEvent( QDragMoveEvent* event ); + virtual void dropEvent( QDropEvent* event ); + + void paintEvent( QPaintEvent* event ); + +private slots: + void onFilterChanged( const QString& filter ); + +private: + QPixmap createDragPixmap( int itemCount ) const; + + TreeModel* m_model; + TreeProxyModel* m_proxyModel; +// PlaylistItemDelegate* m_delegate; +}; + +#endif // ARTISTVIEW_H diff --git a/src/libtomahawk/playlist/playlistmanager.cpp b/src/libtomahawk/playlist/playlistmanager.cpp index 6eefc11d4..52a08c778 100644 --- a/src/libtomahawk/playlist/playlistmanager.cpp +++ b/src/libtomahawk/playlist/playlistmanager.cpp @@ -101,12 +101,12 @@ PlaylistManager::PlaylistManager( QObject* parent ) m_widget->layout()->addWidget( line ); m_widget->layout()->addWidget( m_splitter ); - m_superCollectionView = new CollectionView(); - m_superCollectionFlatModel = new CollectionFlatModel( m_superCollectionView ); - m_superCollectionView->setTrackModel( m_superCollectionFlatModel ); + m_superCollectionView = new ArtistView(); + m_superCollectionModel = new TreeModel( m_superCollectionView ); + m_superCollectionView->setModel( m_superCollectionModel ); m_superCollectionView->setFrameShape( QFrame::NoFrame ); m_superCollectionView->setAttribute( Qt::WA_MacShowFocusRect, 0 ); - m_superCollectionView->proxyModel()->setShowOfflineResults( false ); +// m_superCollectionView->proxyModel()->setShowOfflineResults( false ); m_superAlbumView = new AlbumView(); m_superAlbumModel = new AlbumModel( m_superAlbumView ); @@ -371,19 +371,19 @@ PlaylistManager::show( ViewPage* page ) bool PlaylistManager::showSuperCollection() { - QList< collection_ptr > toAdd; + if ( m_superCollections.isEmpty() ) + m_superCollectionModel->addAllCollections(); + foreach( const Tomahawk::source_ptr& source, SourceList::instance()->sources() ) { if ( !m_superCollections.contains( source->collection() ) ) { m_superCollections.append( source->collection() ); - toAdd << source->collection(); - m_superAlbumModel->addCollection( source->collection() ); +// m_superAlbumModel->addCollection( source->collection() ); } } - m_superCollectionFlatModel->addCollections( toAdd ); - m_superCollectionFlatModel->setTitle( tr( "All available tracks" ) ); + m_superCollectionModel->setTitle( tr( "All available tracks" ) ); m_superAlbumModel->setTitle( tr( "All available albums" ) ); if ( m_currentMode == 0 ) diff --git a/src/libtomahawk/playlist/playlistmanager.h b/src/libtomahawk/playlist/playlistmanager.h index bceb89d08..8ccf91f9d 100644 --- a/src/libtomahawk/playlist/playlistmanager.h +++ b/src/libtomahawk/playlist/playlistmanager.h @@ -162,8 +162,8 @@ private: AlbumModel* m_superAlbumModel; AlbumView* m_superAlbumView; - CollectionFlatModel* m_superCollectionFlatModel; - CollectionView* m_superCollectionView; + TreeModel* m_superCollectionModel; + ArtistView* m_superCollectionView; WelcomeWidget* m_welcomeWidget; QList< Tomahawk::collection_ptr > m_superCollections; diff --git a/src/libtomahawk/playlist/treeheader.cpp b/src/libtomahawk/playlist/treeheader.cpp new file mode 100644 index 000000000..12ca97c04 --- /dev/null +++ b/src/libtomahawk/playlist/treeheader.cpp @@ -0,0 +1,145 @@ +/* === This file is part of Tomahawk Player - === + * + * Copyright 2010-2011, Christian Muehlhaeuser + * + * Tomahawk is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * Tomahawk is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with Tomahawk. If not, see . + */ + +#include "treeheader.h" + +#include +#include +#include + +#include "tomahawksettings.h" +#include "playlist/treemodel.h" +#include "playlist/artistview.h" + + +TreeHeader::TreeHeader( ArtistView* parent ) + : QHeaderView( Qt::Horizontal, parent ) + , m_parent( parent ) + , m_menu( new QMenu( this ) ) + , m_sigmap( new QSignalMapper( this ) ) + , m_init( false ) +{ + setResizeMode( QHeaderView::Interactive ); + setMinimumSectionSize( 60 ); + setDefaultAlignment( Qt::AlignLeft ); + setMovable( true ); + setStretchLastSection( true ); +// setCascadingSectionResizes( true ); + +// m_menu->addAction( tr( "Resize columns to fit window" ), this, SLOT( onToggleResizeColumns() ) ); +// m_menu->addSeparator(); + + connect( this, SIGNAL( sectionResized( int, int, int ) ), SLOT( onSectionResized() ) ); + connect( m_sigmap, SIGNAL( mapped( int ) ), SLOT( toggleVisibility( int ) ) ); +} + + +TreeHeader::~TreeHeader() +{ +} + + +void +TreeHeader::onSectionResized() +{ + if ( !m_init ) + return; + +// TomahawkSettings::instance()->setPlaylistColumnSizes( m_parent->guid(), saveState() ); +} + + +int +TreeHeader::visibleSectionCount() const +{ + return count() - hiddenSectionCount(); +} + + +void +TreeHeader::checkState() +{ + if ( !count() || m_init ) + return; + +/* QByteArray state = TomahawkSettings::instance()->playlistColumnSizes( m_parent->guid() ); + if ( !state.isEmpty() ) + restoreState( state ); + else + { + QList< double > m_columnWeights; + m_columnWeights << 0.21 << 0.22 << 0.20 << 0.05 << 0.05 << 0.05 << 0.05 << 0.05; // << 0.12; + + for ( int i = 0; i < count() - 1; i++ ) + { + if ( isSectionHidden( i ) ) + continue; + + double nw = (double)m_parent->width() * m_columnWeights.at( i ); + qDebug() << "Setting default size:" << i << nw; + resizeSection( i, qMax( minimumSectionSize(), int( nw - 0.5 ) ) ); + } + } + + m_init = true;*/ +} + + +void +TreeHeader::addColumnToMenu( int index ) +{ + QString title = m_parent->model()->headerData( index, Qt::Horizontal, Qt::DisplayRole ).toString(); + + QAction* action = m_menu->addAction( title, m_sigmap, SLOT( map() ) ); + action->setCheckable( true ); + action->setChecked( !isSectionHidden( index ) ); + m_visActions << action; + + m_sigmap->setMapping( action, index ); +} + + +void +TreeHeader::contextMenuEvent( QContextMenuEvent* e ) +{ + qDeleteAll( m_visActions ); + m_visActions.clear(); + + for ( int i = 0; i < count(); i++ ) + addColumnToMenu( i ); + + m_menu->popup( e->globalPos() ); +} + + +void +TreeHeader::onToggleResizeColumns() +{ +} + + +void +TreeHeader::toggleVisibility( int index ) +{ + qDebug() << Q_FUNC_INFO << index; + + if ( isSectionHidden( index ) ) + showSection( index ); + else + hideSection( index ); +} diff --git a/src/libtomahawk/playlist/treeheader.h b/src/libtomahawk/playlist/treeheader.h new file mode 100644 index 000000000..73a81ada6 --- /dev/null +++ b/src/libtomahawk/playlist/treeheader.h @@ -0,0 +1,61 @@ +/* === This file is part of Tomahawk Player - === + * + * Copyright 2010-2011, Christian Muehlhaeuser + * + * Tomahawk is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * Tomahawk is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with Tomahawk. If not, see . + */ + +#ifndef TREEHEADER_H +#define TREEHEADER_H + +#include +#include + +#include "dllmacro.h" + +class ArtistView; + +class DLLEXPORT TreeHeader : public QHeaderView +{ +Q_OBJECT + +public: + explicit TreeHeader( ArtistView* parent = 0 ); + ~TreeHeader(); + + int visibleSectionCount() const; + +public slots: + void toggleVisibility( int index ); + void checkState(); + +protected: + void contextMenuEvent( QContextMenuEvent* e ); + +private slots: + void onSectionResized(); + void onToggleResizeColumns(); + +private: + void addColumnToMenu( int index ); + + ArtistView* m_parent; + + QMenu* m_menu; + QSignalMapper* m_sigmap; + QList m_visActions; + bool m_init; +}; + +#endif diff --git a/src/libtomahawk/playlist/treeitemdelegate.cpp b/src/libtomahawk/playlist/treeitemdelegate.cpp new file mode 100644 index 000000000..fbc1fa16b --- /dev/null +++ b/src/libtomahawk/playlist/treeitemdelegate.cpp @@ -0,0 +1,101 @@ +/* === This file is part of Tomahawk Player - === + * + * Copyright 2010-2011, Christian Muehlhaeuser + * + * Tomahawk is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * Tomahawk is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with Tomahawk. If not, see . + */ + +#include "treeitemdelegate.h" + +#include +#include +#include +#include + +#include "query.h" +#include "result.h" + +#include "utils/tomahawkutils.h" + +#include "treemodelitem.h" +#include "treeproxymodel.h" + + +TreeItemDelegate::TreeItemDelegate( QAbstractItemView* parent, TreeProxyModel* proxy ) + : QStyledItemDelegate( (QObject*)parent ) + , m_view( parent ) + , m_model( proxy ) +{ +} + + +QSize +TreeItemDelegate::sizeHint( const QStyleOptionViewItem& option, const QModelIndex& index ) const +{ + QSize size = QStyledItemDelegate::sizeHint( option, index ); + return size; +} + + +void +TreeItemDelegate::paint( QPainter* painter, const QStyleOptionViewItem& option, const QModelIndex& index ) const +{ + TreeModelItem* item = m_model->sourceModel()->itemFromIndex( m_model->mapToSource( index ) ); + if ( !item ) + return; + + QString text; + if ( !item->artist().isNull() ) + { + text = item->artist()->name(); + } + else if ( !item->album().isNull() ) + { + text = item->album()->name(); + } + else if ( !item->result().isNull() ) + { + return QStyledItemDelegate::paint( painter, option, index ); + } + + QStyleOptionViewItemV4 opt = option; + initStyleOption( &opt, QModelIndex() ); + qApp->style()->drawControl( QStyle::CE_ItemViewItem, &opt, painter ); + + if ( option.state & QStyle::State_Selected ) + { + opt.palette.setColor( QPalette::Text, opt.palette.color( QPalette::HighlightedText ) ); + } + + painter->save(); + painter->setRenderHint( QPainter::Antialiasing ); + painter->setPen( opt.palette.color( QPalette::Text ) ); + + QRect r = option.rect.adjusted( 4, 4, -option.rect.width() + option.rect.height() - 8, -4 ); +// painter->drawPixmap( option.rect.adjusted( 4, 4, -4, -38 ), QPixmap( RESPATH "images/cover-shadow.png" ) ); + painter->drawPixmap( r, item->cover ); + + QTextOption to; + to.setAlignment( Qt::AlignVCenter ); + QFont font = opt.font; + QFont boldFont = opt.font; + boldFont.setBold( true ); + + r = option.rect.adjusted( option.rect.height(), 6, 0, -option.rect.height() + 22 ); + painter->drawText( r, text, to ); + + painter->setFont( boldFont ); + + painter->restore(); +} diff --git a/src/libtomahawk/playlist/treeitemdelegate.h b/src/libtomahawk/playlist/treeitemdelegate.h new file mode 100644 index 000000000..234b0d326 --- /dev/null +++ b/src/libtomahawk/playlist/treeitemdelegate.h @@ -0,0 +1,46 @@ +/* === This file is part of Tomahawk Player - === + * + * Copyright 2010-2011, Christian Muehlhaeuser + * + * Tomahawk is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * Tomahawk is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with Tomahawk. If not, see . + */ + +#ifndef TREEITEMDELEGATE_H +#define TREEITEMDELEGATE_H + +#include + +#include "dllmacro.h" + +class TreeProxyModel; + +class DLLEXPORT TreeItemDelegate : public QStyledItemDelegate +{ +Q_OBJECT + +public: + TreeItemDelegate( QAbstractItemView* parent = 0, TreeProxyModel* proxy = 0 ); + +protected: + void paint( QPainter* painter, const QStyleOptionViewItem& option, const QModelIndex& index ) const; + QSize sizeHint( const QStyleOptionViewItem& option, const QModelIndex& index ) const; + +// QWidget* createEditor( QWidget* parent, const QStyleOptionViewItem& option, const QModelIndex& index ) const; + +private: + QAbstractItemView* m_view; + TreeProxyModel* m_model; +}; + +#endif // TREEITEMDELEGATE_H diff --git a/src/libtomahawk/playlist/treemodel.cpp b/src/libtomahawk/playlist/treemodel.cpp index 814134dbb..86dc33077 100644 --- a/src/libtomahawk/playlist/treemodel.cpp +++ b/src/libtomahawk/playlist/treemodel.cpp @@ -19,22 +19,36 @@ #include "treemodel.h" #include +#include #include -#include +#include -#include "sourcelist.h" +#include "database/databasecommand_allalbums.h" +#include "database/databasecommand_alltracks.h" +#include "database/database.h" #include "utils/tomahawkutils.h" +#define LASTFM_DEFAULT_COVER "http://cdn.last.fm/flatness/catalogue/noimage" + +static QString s_infoIdentifier = QString( "TREEMODEL" ); + using namespace Tomahawk; TreeModel::TreeModel( QObject* parent ) : QAbstractItemModel( parent ) - , m_rootItem( 0 ) + , m_rootItem( new TreeModelItem( 0, this ) ) { qDebug() << Q_FUNC_INFO; - connect( SourceList::instance(), SIGNAL( sourceRemoved( Tomahawk::source_ptr ) ), SLOT( onSourceOffline( Tomahawk::source_ptr ) ) ); + m_defaultCover = QPixmap( RESPATH "images/no-album-art-placeholder.png" ) + .scaled( QSize( 120, 120 ), Qt::IgnoreAspectRatio, Qt::SmoothTransformation ); + +/* connect( TomahawkApp::instance()->infoSystem(), + SIGNAL( info( QString, Tomahawk::InfoSystem::InfoType, QVariant, QVariant, Tomahawk::InfoSystem::InfoCustomData ) ), + SLOT( infoSystemInfo( QString, Tomahawk::InfoSystem::InfoType, QVariant, QVariant, Tomahawk::InfoSystem::InfoCustomData ) ) ); + + connect( TomahawkApp::instance()->infoSystem(), SIGNAL( finished( QString ) ), SLOT( infoSystemFinished( QString ) ) );*/ } @@ -58,6 +72,52 @@ TreeModel::index( int row, int column, const QModelIndex& parent ) const } +bool +TreeModel::canFetchMore( const QModelIndex& parent ) const +{ + TreeModelItem* parentItem = itemFromIndex( parent ); + + if ( parentItem->fetchingMore ) + return false; + + if ( !parentItem->artist().isNull() ) + { + return true; + } + else if ( !parentItem->album().isNull() ) + { + return true; + } + + return false; +} + + +void +TreeModel::fetchMore( const QModelIndex& parent ) +{ + qDebug() << Q_FUNC_INFO; + + TreeModelItem* parentItem = itemFromIndex( parent ); + if ( !parentItem || parentItem->fetchingMore ) + return; + + parentItem->fetchingMore = true; + if ( !parentItem->artist().isNull() ) + { + qDebug() << "Artist" << parentItem->artist()->name(); + addAlbums( parentItem->artist(), parent ); + } + else if ( !parentItem->album().isNull() ) + { + qDebug() << "Artist" << parentItem->album()->name(); + addTracks( parentItem->album(), parent ); + } + else + qDebug() << "Something else"; +} + + int TreeModel::rowCount( const QModelIndex& parent ) const { @@ -68,6 +128,12 @@ TreeModel::rowCount( const QModelIndex& parent ) const if ( !parentItem ) return 0; + if ( !parentItem->artist().isNull() || !parentItem->album().isNull() ) + { + if ( !parentItem->children.count() ) + return 1; + } + return parentItem->children.count(); } @@ -103,58 +169,43 @@ TreeModel::parent( const QModelIndex& child ) const QVariant TreeModel::data( const QModelIndex& index, int role ) const { - if ( role != Qt::DisplayRole ) - return QVariant(); - TreeModelItem* entry = itemFromIndex( index ); if ( !entry ) return QVariant(); - const query_ptr& query = entry->query(); - if ( query.isNull() ) + if ( role == Qt::SizeHintRole ) { - if ( !index.column() ) + if ( !entry->result().isNull() ) { - return entry->caption.isEmpty() ? "Unknown" : entry->caption; + return QSize( 128, 20 ); + } + else if ( !entry->album().isNull() ) + { + return QSize( 128, 32 ); } - if ( index.column() == 1 ) - { - return entry->childCount; - } - - return QVariant( "" ); + return QSize( 128, 44 ); } - if ( !query->numResults() ) + if ( role != Qt::DisplayRole ) // && role != Qt::ToolTipRole ) + return QVariant(); + + switch( index.column() ) { - switch( index.column() ) - { - case 0: - return query->track(); - break; - } - } - else - { - switch( index.column() ) - { - case 0: - return query->results().first()->track(); - break; - - case 1: - return QVariant(); - break; - - case 2: - return TomahawkUtils::timeToString( query->results().first()->duration() ); - break; - - case 3: - return query->results().first()->collection()->source()->friendlyName(); - break; - } + case 0: + if ( !entry->artist().isNull() ) + { + return entry->artist()->name(); + } + else if ( !entry->artist().isNull() ) + { + return entry->album()->name(); + } + else if ( !entry->result().isNull() ) + { + return entry->result()->track(); + } + break; } return QVariant(); @@ -165,7 +216,7 @@ QVariant TreeModel::headerData( int section, Qt::Orientation orientation, int role ) const { QStringList headers; - headers << tr( "Name" ) << tr( "Tracks" ) << tr( "Duration" ) << tr( "Origin" ); + headers << tr( "Artist" ); if ( orientation == Qt::Horizontal && role == Qt::DisplayRole && section >= 0 ) { return headers.at( section ); @@ -175,29 +226,136 @@ TreeModel::headerData( int section, Qt::Orientation orientation, int role ) cons } -void -TreeModel::setCurrentItem( const QModelIndex& index ) +Qt::ItemFlags +TreeModel::flags( const QModelIndex& index ) const +{ + Qt::ItemFlags defaultFlags = QAbstractItemModel::flags( index ); + + if ( index.isValid() && index.column() == 0 ) + return Qt::ItemIsDragEnabled | Qt::ItemIsDropEnabled | defaultFlags; + else + return defaultFlags; +} + + +QStringList +TreeModel::mimeTypes() const +{ + QStringList types; + types << "application/tomahawk.query.list"; + return types; +} + + +QMimeData* +TreeModel::mimeData( const QModelIndexList &indexes ) const { qDebug() << Q_FUNC_INFO; - TreeModelItem* oldEntry = itemFromIndex( m_currentIndex ); - if ( oldEntry ) + + QByteArray queryData; + QDataStream queryStream( &queryData, QIODevice::WriteOnly ); + + foreach ( const QModelIndex& i, indexes ) { - oldEntry->setIsPlaying( false ); + if ( i.column() > 0 ) + continue; + + QModelIndex idx = index( i.row(), 0, i.parent() ); + TreeModelItem* item = itemFromIndex( idx ); + if ( item ) + { + const album_ptr& album = item->album(); + queryStream << qlonglong( &album ); + } } - TreeModelItem* entry = itemFromIndex( index ); - if ( entry ) + QMimeData* mimeData = new QMimeData(); + mimeData->setData( "application/tomahawk.query.list", queryData ); + + return mimeData; +} + + +void +TreeModel::removeIndex( const QModelIndex& index ) +{ + qDebug() << Q_FUNC_INFO; + + if ( index.column() > 0 ) + return; + + TreeModelItem* item = itemFromIndex( index ); + if ( item ) { - m_currentIndex = index; - entry->setIsPlaying( true ); + emit beginRemoveRows( index.parent(), index.row(), index.row() ); + delete item; + emit endRemoveRows(); } - else +} + + +void +TreeModel::removeIndexes( const QList& indexes ) +{ + foreach( const QModelIndex& idx, indexes ) { - m_currentIndex = QModelIndex(); + removeIndex( idx ); } } +void +TreeModel::addAllCollections() +{ + qDebug() << Q_FUNC_INFO; + + DatabaseCommand_AllArtists* cmd = new DatabaseCommand_AllArtists(); + + connect( cmd, SIGNAL( artists( QList ) ), + SLOT( onArtistsAdded( QList ) ) ); + + Database::instance()->enqueue( QSharedPointer( cmd ) ); + + m_title = tr( "All Artists" ); +} + + +void +TreeModel::addAlbums( const artist_ptr& artist, const QModelIndex& parent ) +{ + qDebug() << Q_FUNC_INFO; + + DatabaseCommand_AllAlbums* cmd = new DatabaseCommand_AllAlbums( m_collection, artist ); + cmd->setData( parent.row() ); + + connect( cmd, SIGNAL( albums( QList, QVariant ) ), + SLOT( onAlbumsAdded( QList, QVariant ) ) ); + + Database::instance()->enqueue( QSharedPointer( cmd ) ); +} + + +void +TreeModel::addTracks( const album_ptr& album, const QModelIndex& parent ) +{ + qDebug() << Q_FUNC_INFO; + + DatabaseCommand_AllTracks* cmd = new DatabaseCommand_AllTracks( m_collection ); + cmd->setAlbum( album.data() ); + + QList< QVariant > rows; + rows << parent.row(); + rows << parent.parent().row(); + + cmd->setData( QVariant( rows ) ); + + connect( cmd, SIGNAL( tracks( QList, QVariant ) ), + SLOT( onTracksAdded( QList, QVariant ) ) ); + + Database::instance()->enqueue( QSharedPointer( cmd ) ); +} + + void TreeModel::addCollection( const collection_ptr& collection ) { @@ -205,106 +363,200 @@ TreeModel::addCollection( const collection_ptr& collection ) << collection->source()->id() << collection->source()->userName(); - emit loadingStarts(); + m_collection = collection; + DatabaseCommand_AllArtists* cmd = new DatabaseCommand_AllArtists( collection ); - connect( collection.data(), SIGNAL( tracksAdded( QList, Tomahawk::collection_ptr ) ), - SLOT( onTracksAdded( QList, Tomahawk::collection_ptr ) ) ); - connect( collection.data(), SIGNAL( tracksFinished( Tomahawk::collection_ptr ) ), - SLOT( onTracksAddingFinished( Tomahawk::collection_ptr ) ) ); + connect( cmd, SIGNAL( artists( QList ) ), + SLOT( onArtistsAdded( QList ) ) ); + + Database::instance()->enqueue( QSharedPointer( cmd ) ); + + m_title = tr( "All artists from %1" ).arg( collection->source()->friendlyName() ); } void -TreeModel::removeCollection( const collection_ptr& collection ) +TreeModel::addFilteredCollection( const collection_ptr& collection, unsigned int amount, DatabaseCommand_AllArtists::SortOrder order ) { - disconnect( collection.data(), SIGNAL( tracksAdded( QList, Tomahawk::collection_ptr ) ), - this, SLOT( onTracksAdded( QList, Tomahawk::collection_ptr ) ) ); - disconnect( collection.data(), SIGNAL( tracksFinished( Tomahawk::collection_ptr ) ), - this, SLOT( onTracksAddingFinished( Tomahawk::collection_ptr ) ) ); + qDebug() << Q_FUNC_INFO << collection->name() + << collection->source()->id() + << collection->source()->userName() + << amount << order; - QList plitems = m_collectionIndex.values( collection ); + DatabaseCommand_AllArtists* cmd = new DatabaseCommand_AllArtists( collection ); + cmd->setLimit( amount ); + cmd->setSortOrder( order ); + cmd->setSortDescending( true ); - m_collectionIndex.remove( collection ); + connect( cmd, SIGNAL( artists( QList, Tomahawk::collection_ptr ) ), + SLOT( onArtistsAdded( QList, Tomahawk::collection_ptr ) ) ); + + Database::instance()->enqueue( QSharedPointer( cmd ) ); + + m_title = tr( "All albums from %1" ).arg( collection->source()->friendlyName() ); } void -TreeModel::onTracksAdded( const QList& tracks, const collection_ptr& collection ) +TreeModel::onArtistsAdded( const QList& artists ) { -// int c = rowCount( QModelIndex() ); + if ( !artists.count() ) + return; - TreeModelItem* plitem; - foreach( const Tomahawk::query_ptr& query, tracks ) + int c = rowCount( QModelIndex() ); + QPair< int, int > crows; + crows.first = c; + crows.second = c + artists.count() - 1; + + emit beginInsertRows( QModelIndex(), crows.first, crows.second ); + + TreeModelItem* artistitem; + foreach( const artist_ptr& artist, artists ) { - TreeModelItem* parent = m_rootItem; - if ( parent->hash.contains( query->artist() ) ) - { - parent = parent->hash.value( query->artist() ); - } - else - { - parent = new TreeModelItem( query->artist(), m_rootItem ); - m_rootItem->hash.insert( query->artist(), parent ); - } + qDebug() << artist->name(); + artistitem = new TreeModelItem( artist, m_rootItem ); + artistitem->cover = m_defaultCover; + artistitem->index = createIndex( m_rootItem->children.count() - 1, 0, artistitem ); - if ( parent->hash.contains( query->album() ) ) - { - parent->childCount++; - parent = parent->hash.value( query->album() ); - parent->childCount++; - } - else - { - TreeModelItem* subitem = new TreeModelItem( query->album(), parent ); - parent->hash.insert( query->album(), subitem ); - parent->childCount++; - subitem->childCount++; - parent = subitem; - } - - plitem = new TreeModelItem( query, parent ); - m_collectionIndex.insertMulti( collection, plitem ); + connect( artistitem, SIGNAL( dataChanged() ), SLOT( onDataChanged() ) ); } - reset(); - + emit endInsertRows(); qDebug() << rowCount( QModelIndex() ); } void -TreeModel::onTracksAddingFinished( const Tomahawk::collection_ptr& collection ) +TreeModel::onAlbumsAdded( const QList& albums, const QVariant& data ) { - qDebug() << "Finished loading tracks" << collection->source()->friendlyName(); + qDebug() << Q_FUNC_INFO << albums.count(); + if ( !albums.count() ) + return; - disconnect( collection.data(), SIGNAL( tracksAdded( QList, Tomahawk::collection_ptr ) ), - this, SLOT( onTracksAdded( QList, Tomahawk::collection_ptr ) ) ); - disconnect( collection.data(), SIGNAL( tracksFinished( Tomahawk::collection_ptr ) ), - this, SLOT( onTracksAddingFinished( Tomahawk::collection_ptr ) ) ); + QModelIndex parent = index( data.toUInt(), 0, QModelIndex() ); + TreeModelItem* parentItem = itemFromIndex( parent ); - emit loadingFinished(); + // the -1 is because we fake a rowCount of 1 to trigger Qt calling fetchMore() + int c = rowCount( parent ) - 1; + QPair< int, int > crows; + crows.first = c; + crows.second = c + albums.count() - 1; + + if ( crows.second > 0 ) + emit beginInsertRows( parent, crows.first + 1, crows.second ); + + TreeModelItem* albumitem; + foreach( const album_ptr& album, albums ) + { + albumitem = new TreeModelItem( album, parentItem ); + albumitem->cover = m_defaultCover; + albumitem->index = createIndex( parentItem->children.count() - 1, 0, albumitem ); + connect( albumitem, SIGNAL( dataChanged() ), SLOT( onDataChanged() ) ); + +/* Tomahawk::InfoSystem::InfoCustomData trackInfo; + trackInfo["artist"] = QVariant::fromValue< QString >( album->artist()->name() ); + trackInfo["album"] = QVariant::fromValue< QString >( album->name() ); + trackInfo["pptr"] = (qlonglong)albumitem; + + TomahawkApp::instance()->infoSystem()->getInfo( + s_infoIdentifier, Tomahawk::InfoSystem::InfoAlbumCoverArt, + QVariant::fromValue< Tomahawk::InfoSystem::InfoCustomData >( trackInfo ), Tomahawk::InfoSystem::InfoCustomData() );*/ + } + + if ( crows.second > 0 ) + emit endInsertRows(); + else + emit dataChanged( albumitem->index, albumitem->index.sibling( albumitem->index.row(), columnCount( QModelIndex() ) - 1 ) ); + + qDebug() << rowCount( parent ); } void -TreeModel::onSourceOffline( Tomahawk::source_ptr src ) +TreeModel::onTracksAdded( const QList& tracks, const QVariant& data ) +{ + qDebug() << Q_FUNC_INFO << tracks.count(); + if ( !tracks.count() ) + return; + + QList< QVariant > rows = data.toList(); + + QModelIndex parent = index( rows.first().toUInt(), 0, index( rows.at( 1 ).toUInt(), 0, QModelIndex() ) ); + TreeModelItem* parentItem = itemFromIndex( parent ); + + // the -1 is because we fake a rowCount of 1 to trigger Qt calling fetchMore() + int c = rowCount( parent ) - 1; + QPair< int, int > crows; + crows.first = c; + crows.second = c + tracks.count() - 1; + + if ( crows.second > 0 ) + emit beginInsertRows( parent, crows.first + 1, crows.second ); + + TreeModelItem* item; + foreach( const query_ptr& query, tracks ) + { + qDebug() << query->toString(); + item = new TreeModelItem( query->results().first(), parentItem ); + item->cover = m_defaultCover; + item->index = createIndex( parentItem->children.count() - 1, 0, item ); + + connect( item, SIGNAL( dataChanged() ), SLOT( onDataChanged() ) ); + } + + if ( crows.second > 0 ) + emit endInsertRows(); + else + emit dataChanged( item->index, item->index.sibling( item->index.row(), columnCount( QModelIndex() ) - 1 ) ); + + qDebug() << rowCount( parent ); +} + + +void +TreeModel::infoSystemInfo( QString caller, Tomahawk::InfoSystem::InfoType type, QVariant input, QVariant output, Tomahawk::InfoSystem::InfoCustomData customData ) { qDebug() << Q_FUNC_INFO; - - if ( m_collectionIndex.contains( src->collection() ) ) + if ( caller != s_infoIdentifier || type != Tomahawk::InfoSystem::InfoAlbumCoverArt ) { - removeCollection( src->collection() ); + qDebug() << "Info of wrong type or not with our identifier"; + return; + } + + if ( !output.canConvert< Tomahawk::InfoSystem::InfoCustomData >() ) + { + qDebug() << "Cannot convert fetched art from a QByteArray"; + return; + } + + Tomahawk::InfoSystem::InfoCustomData returnedData = output.value< Tomahawk::InfoSystem::InfoCustomData >(); + const QByteArray ba = returnedData["imgbytes"].toByteArray(); + if ( ba.length() ) + { + QPixmap pm; + pm.loadFromData( ba ); + + qlonglong pptr = input.toLongLong(); + TreeModelItem* ai = reinterpret_cast(pptr); + + if ( pm.isNull() || returnedData["url"].toString().startsWith( LASTFM_DEFAULT_COVER ) ) + ai->cover = m_defaultCover; + else + ai->cover = pm; } } -TreeModelItem* -TreeModel::itemFromIndex( const QModelIndex& index ) const +void +TreeModel::infoSystemFinished( QString target ) { - if ( index.isValid() ) - return static_cast( index.internalPointer() ); - else - { - return m_rootItem; - } + qDebug() << Q_FUNC_INFO; +} + + +void +TreeModel::onDataChanged() +{ + TreeModelItem* p = (TreeModelItem*)sender(); + emit dataChanged( p->index, p->index.sibling( p->index.row(), columnCount( QModelIndex() ) - 1 ) ); } diff --git a/src/libtomahawk/playlist/treemodel.h b/src/libtomahawk/playlist/treemodel.h index 393a9ed6d..6062c37cb 100644 --- a/src/libtomahawk/playlist/treemodel.h +++ b/src/libtomahawk/playlist/treemodel.h @@ -16,19 +16,19 @@ * along with Tomahawk. If not, see . */ -#ifndef COLLECTIONMODEL_H -#define COLLECTIONMODEL_H +#ifndef TREEMODEL_H +#define TREEMODEL_H #include -#include -#include +#include + +#include "album.h" +#include "collection.h" +#include "playlistinterface.h" +#include "database/databasecommand_allartists.h" #include "treemodelitem.h" -#include "collection.h" -#include "query.h" -#include "typedefs.h" -#include "playlist.h" -#include "playlistinterface.h" +#include "tomahawk/infosystem.h" #include "dllmacro.h" @@ -40,53 +40,84 @@ Q_OBJECT public: explicit TreeModel( QObject* parent = 0 ); - ~TreeModel(); + virtual ~TreeModel(); - QModelIndex index( int row, int column, const QModelIndex& parent ) const; - QModelIndex parent( const QModelIndex& child ) const; + virtual QModelIndex index( int row, int column, const QModelIndex& parent ) const; + virtual QModelIndex parent( const QModelIndex& child ) const; - int trackCount() const { return rowCount( QModelIndex() ); } + virtual bool isReadOnly() const { return true; } - int rowCount( const QModelIndex& parent ) const; - int columnCount( const QModelIndex& parent ) const; + virtual int trackCount() const { return rowCount( QModelIndex() ); } + virtual int albumCount() const { return rowCount( QModelIndex() ); } - QVariant data( const QModelIndex& index, int role = Qt::DisplayRole ) const; - QVariant headerData( int section, Qt::Orientation orientation, int role ) const; + virtual int rowCount( const QModelIndex& parent ) const; + virtual int columnCount( const QModelIndex& parent ) const; + virtual QVariant data( const QModelIndex& index, int role = Qt::DisplayRole ) const; + virtual QVariant headerData( int section, Qt::Orientation orientation, int role ) const; + + virtual void removeIndex( const QModelIndex& index ); + virtual void removeIndexes( const QList& indexes ); + + virtual QMimeData* mimeData( const QModelIndexList& indexes ) const; + virtual QStringList mimeTypes() const; + virtual Qt::ItemFlags flags( const QModelIndex& index ) const; + + void addAllCollections(); void addCollection( const Tomahawk::collection_ptr& collection ); - void removeCollection( const Tomahawk::collection_ptr& collection ); + void addFilteredCollection( const Tomahawk::collection_ptr& collection, unsigned int amount, DatabaseCommand_AllArtists::SortOrder order ); - virtual QPersistentModelIndex currentItem() { return m_currentIndex; } + void addAlbums( const Tomahawk::artist_ptr& artist, const QModelIndex& parent ); + void addTracks( const Tomahawk::album_ptr& album, const QModelIndex& parent ); - virtual PlaylistInterface::RepeatMode repeatMode() const { return PlaylistInterface::NoRepeat; } - virtual bool shuffled() const { return false; } + virtual QString title() const { return m_title; } + virtual QString description() const { return m_description; } + virtual void setTitle( const QString& title ) { m_title = title; } + virtual void setDescription( const QString& description ) { m_description = description; } - virtual void setRepeatMode( PlaylistInterface::RepeatMode /*mode*/ ) {} - virtual void setShuffled( bool /*shuffled*/ ) {} - - TreeModelItem* itemFromIndex( const QModelIndex& index ) const; + TreeModelItem* itemFromIndex( const QModelIndex& index ) const + { + if ( index.isValid() ) + return static_cast( index.internalPointer() ); + else + { + return m_rootItem; + } + } public slots: - virtual void setCurrentItem( const QModelIndex& index ); + virtual void setRepeatMode( PlaylistInterface::RepeatMode /*mode*/ ) {} + virtual void setShuffled( bool /*shuffled*/ ) {} signals: void repeatModeChanged( PlaylistInterface::RepeatMode mode ); void shuffleModeChanged( bool enabled ); - void loadingStarts(); - void loadingFinished(); void trackCountChanged( unsigned int tracks ); -private slots: - void onTracksAdded( const QList& tracks, const Tomahawk::collection_ptr& collection ); - void onTracksAddingFinished( const Tomahawk::collection_ptr& collection ); +protected: + bool canFetchMore( const QModelIndex& parent ) const; + void fetchMore( const QModelIndex& parent ); - void onSourceOffline( Tomahawk::source_ptr src ); +private slots: + void onArtistsAdded( const QList& artists ); + void onAlbumsAdded( const QList& albums, const QVariant& data ); + void onTracksAdded( const QList& tracks, const QVariant& data ); + + void infoSystemInfo( QString caller, Tomahawk::InfoSystem::InfoType type, QVariant input, QVariant output, Tomahawk::InfoSystem::InfoCustomData customData ); + void infoSystemFinished( QString target ); + + void onDataChanged(); private: - TreeModelItem* m_rootItem; - QMap< Tomahawk::collection_ptr, TreeModelItem* > m_collectionIndex; QPersistentModelIndex m_currentIndex; + TreeModelItem* m_rootItem; + QPixmap m_defaultCover; + + QString m_title; + QString m_description; + + Tomahawk::collection_ptr m_collection; }; -#endif // COLLECTIONMODEL_H +#endif // ALBUMMODEL_H diff --git a/src/libtomahawk/playlist/treemodelitem.cpp b/src/libtomahawk/playlist/treemodelitem.cpp new file mode 100644 index 000000000..9e1c03fa2 --- /dev/null +++ b/src/libtomahawk/playlist/treemodelitem.cpp @@ -0,0 +1,133 @@ +/* === This file is part of Tomahawk Player - === + * + * Copyright 2010-2011, Christian Muehlhaeuser + * + * Tomahawk is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * Tomahawk is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with Tomahawk. If not, see . + */ + +#include "treemodelitem.h" + +#include "utils/tomahawkutils.h" + +#include + +using namespace Tomahawk; + + +TreeModelItem::~TreeModelItem() +{ + // Don't use qDeleteAll here! The children will remove themselves + // from the list when they get deleted and the qDeleteAll iterator + // will fail badly! + for ( int i = children.count() - 1; i >= 0; i-- ) + delete children.at( i ); + + if ( parent && index.isValid() ) + { + parent->children.removeAt( index.row() ); + } +} + + +TreeModelItem::TreeModelItem( TreeModelItem* parent, QAbstractItemModel* model ) +{ + this->parent = parent; + this->model = model; + childCount = 0; + toberemoved = false; + fetchingMore = false; + + if ( parent ) + { + parent->children.append( this ); + } +} + + +TreeModelItem::TreeModelItem( const Tomahawk::album_ptr& album, TreeModelItem* parent, int row ) + : QObject( parent ) + , m_album( album ) +{ + this->parent = parent; + fetchingMore = false; + + if ( parent ) + { + if ( row < 0 ) + { + parent->children.append( this ); + row = parent->children.count() - 1; + } + else + { + parent->children.insert( row, this ); + } + + this->model = parent->model; + } + + toberemoved = false; +} + + +TreeModelItem::TreeModelItem( const Tomahawk::artist_ptr& artist, TreeModelItem* parent, int row ) + : QObject( parent ) + , m_artist( artist ) +{ + this->parent = parent; + fetchingMore = false; + + if ( parent ) + { + if ( row < 0 ) + { + parent->children.append( this ); + row = parent->children.count() - 1; + } + else + { + parent->children.insert( row, this ); + } + + this->model = parent->model; + } + + toberemoved = false; +} + + +TreeModelItem::TreeModelItem( const Tomahawk::result_ptr& result, TreeModelItem* parent, int row ) + : QObject( parent ) + , m_result( result ) +{ + this->parent = parent; + fetchingMore = false; + + if ( parent ) + { + if ( row < 0 ) + { + parent->children.append( this ); + row = parent->children.count() - 1; + } + else + { + parent->children.insert( row, this ); + } + + this->model = parent->model; + } + + toberemoved = false; +} diff --git a/src/libtomahawk/playlist/treemodelitem.h b/src/libtomahawk/playlist/treemodelitem.h new file mode 100644 index 000000000..16538727b --- /dev/null +++ b/src/libtomahawk/playlist/treemodelitem.h @@ -0,0 +1,69 @@ +/* === This file is part of Tomahawk Player - === + * + * Copyright 2010-2011, Christian Muehlhaeuser + * + * Tomahawk is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * Tomahawk is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with Tomahawk. If not, see . + */ + +#ifndef TREEMODELITEM_H +#define TREEMODELITEM_H + +#include +#include +#include +#include + +#include "result.h" + +#include "dllmacro.h" + +class DLLEXPORT TreeModelItem : public QObject +{ +Q_OBJECT + +public: + ~TreeModelItem(); + + explicit TreeModelItem( TreeModelItem* parent = 0, QAbstractItemModel* model = 0 ); + explicit TreeModelItem( const Tomahawk::artist_ptr& artist, TreeModelItem* parent = 0, int row = -1 ); + explicit TreeModelItem( const Tomahawk::album_ptr& album, TreeModelItem* parent = 0, int row = -1 ); + explicit TreeModelItem( const Tomahawk::result_ptr& result, TreeModelItem* parent = 0, int row = -1 ); + + const Tomahawk::artist_ptr& artist() const { return m_artist; }; + const Tomahawk::album_ptr& album() const { return m_album; }; + const Tomahawk::result_ptr& result() const { return m_result; }; + + void setCover( const QPixmap& cover ) { this->cover = cover; emit dataChanged(); } + + TreeModelItem* parent; + QList children; + QHash hash; + int childCount; + QPersistentModelIndex index; + QAbstractItemModel* model; + QPixmap cover; + + bool toberemoved; + bool fetchingMore; + +signals: + void dataChanged(); + +private: + Tomahawk::artist_ptr m_artist; + Tomahawk::album_ptr m_album; + Tomahawk::result_ptr m_result; +}; + +#endif // TREEMODELITEM_H diff --git a/src/libtomahawk/playlist/treeproxymodel.cpp b/src/libtomahawk/playlist/treeproxymodel.cpp new file mode 100644 index 000000000..357596ed0 --- /dev/null +++ b/src/libtomahawk/playlist/treeproxymodel.cpp @@ -0,0 +1,158 @@ +/* === This file is part of Tomahawk Player - === + * + * Copyright 2010-2011, Christian Muehlhaeuser + * + * Tomahawk is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * Tomahawk is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with Tomahawk. If not, see . + */ + +#include "treeproxymodel.h" + +#include +#include + +#include "query.h" + + +TreeProxyModel::TreeProxyModel( QObject* parent ) + : QSortFilterProxyModel( parent ) + , PlaylistInterface( this ) + , m_model( 0 ) + , m_repeatMode( PlaylistInterface::NoRepeat ) + , m_shuffled( false ) +{ + qsrand( QTime( 0, 0, 0 ).secsTo( QTime::currentTime() ) ); + + setFilterCaseSensitivity( Qt::CaseInsensitive ); + setSortCaseSensitivity( Qt::CaseInsensitive ); + setDynamicSortFilter( true ); + + setSourceModel( 0 ); +} + + +void +TreeProxyModel::setSourceModel( TreeModel* sourceModel ) +{ + m_model = sourceModel; + + connect( m_model, SIGNAL( trackCountChanged( unsigned int ) ), + SIGNAL( sourceTrackCountChanged( unsigned int ) ) ); + + QSortFilterProxyModel::setSourceModel( sourceModel ); +} + + +void +TreeProxyModel::setFilter( const QString& pattern ) +{ + qDebug() << Q_FUNC_INFO; + setFilterRegExp( pattern ); + + emit filterChanged( pattern ); +} + + +bool +TreeProxyModel::filterAcceptsRow( int sourceRow, const QModelIndex& sourceParent ) const +{ + if ( filterRegExp().isEmpty() ) + return true; + + TreeModelItem* pi = sourceModel()->itemFromIndex( sourceModel()->index( sourceRow, 0, sourceParent ) ); + if ( !pi ) + return false; + + QStringList sl = filterRegExp().pattern().split( " ", QString::SkipEmptyParts ); + + bool found = true; + foreach( const QString& s, sl ) + { + if ( !textForItem( pi ).contains( s, Qt::CaseInsensitive ) ) + { + found = false; + } + } + + return found; +} + + +bool +TreeProxyModel::lessThan( const QModelIndex& left, const QModelIndex& right ) const +{ + TreeModelItem* p1 = sourceModel()->itemFromIndex( left ); + TreeModelItem* p2 = sourceModel()->itemFromIndex( right ); + + if ( !p1 ) + return true; + if ( !p2 ) + return false; + + return QString::localeAwareCompare( textForItem( p1 ), textForItem( p2 ) ) < 0; +} + + +void +TreeProxyModel::removeIndex( const QModelIndex& index ) +{ + qDebug() << Q_FUNC_INFO; + + if ( !sourceModel() ) + return; + if ( index.column() > 0 ) + return; + + sourceModel()->removeIndex( mapToSource( index ) ); +} + + +void +TreeProxyModel::removeIndexes( const QList& indexes ) +{ + if ( !sourceModel() ) + return; + + foreach( const QModelIndex& idx, indexes ) + { + removeIndex( idx ); + } +} + + +Tomahawk::result_ptr +TreeProxyModel::siblingItem( int itemsAway ) +{ + qDebug() << Q_FUNC_INFO; + return Tomahawk::result_ptr( 0 ); +} + + +QString +TreeProxyModel::textForItem( TreeModelItem* item ) const +{ + if ( !item->artist().isNull() ) + { + return item->artist()->name(); + } + else if ( !item->album().isNull() ) + { + return item->album()->name(); + } + else if ( !item->result().isNull() ) + { + return item->result()->track(); + } + + return QString(); +} diff --git a/src/libtomahawk/playlist/treeproxymodel.h b/src/libtomahawk/playlist/treeproxymodel.h new file mode 100644 index 000000000..b441e155b --- /dev/null +++ b/src/libtomahawk/playlist/treeproxymodel.h @@ -0,0 +1,81 @@ +/* === This file is part of Tomahawk Player - === + * + * Copyright 2010-2011, Christian Muehlhaeuser + * + * Tomahawk is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * Tomahawk is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with Tomahawk. If not, see . + */ + +#ifndef TREEPROXYMODEL_H +#define TREEPROXYMODEL_H + +#include + +#include "playlistinterface.h" +#include "treemodel.h" + +#include "dllmacro.h" + +class DLLEXPORT TreeProxyModel : public QSortFilterProxyModel, public PlaylistInterface +{ +Q_OBJECT + +public: + explicit TreeProxyModel( QObject* parent = 0 ); + + virtual TreeModel* sourceModel() const { return m_model; } + virtual void setSourceModel( TreeModel* sourceModel ); + + virtual QList tracks() { Q_ASSERT( FALSE ); QList queries; return queries; } + + virtual int unfilteredTrackCount() const { return sourceModel()->rowCount( QModelIndex() ); } + virtual int trackCount() const { return rowCount( QModelIndex() ); } + virtual int albumCount() const { return rowCount( QModelIndex() ); } + + virtual void removeIndex( const QModelIndex& index ); + virtual void removeIndexes( const QList& indexes ); + + virtual Tomahawk::result_ptr siblingItem( int direction ); + + virtual void setFilter( const QString& pattern ); + + virtual PlaylistInterface::RepeatMode repeatMode() const { return m_repeatMode; } + virtual bool shuffled() const { return m_shuffled; } + virtual PlaylistInterface::ViewMode viewMode() const { return PlaylistInterface::Tree; } + +signals: + void repeatModeChanged( PlaylistInterface::RepeatMode mode ); + void shuffleModeChanged( bool enabled ); + + void trackCountChanged( unsigned int tracks ); + void sourceTrackCountChanged( unsigned int tracks ); + + void filterChanged( const QString& filter ); + +public slots: + virtual void setRepeatMode( RepeatMode mode ) { m_repeatMode = mode; emit repeatModeChanged( mode ); } + virtual void setShuffled( bool enabled ) { m_shuffled = enabled; emit shuffleModeChanged( enabled ); } + +protected: + bool filterAcceptsRow( int sourceRow, const QModelIndex& sourceParent ) const; + bool lessThan( const QModelIndex& left, const QModelIndex& right ) const; + +private: + QString textForItem( TreeModelItem* item ) const; + + TreeModel* m_model; + RepeatMode m_repeatMode; + bool m_shuffled; +}; + +#endif // TREEPROXYMODEL_H From 9f4c3bb461765a088c15d17c083758dd9522fd92 Mon Sep 17 00:00:00 2001 From: Christian Muehlhaeuser Date: Thu, 14 Apr 2011 00:32:16 +0200 Subject: [PATCH 311/329] * Moved InfoSystem into libTomahawk. * Made InfoSystem a singleton. * Don't access InfoSystem via TomahawkApp any longer. * Progress on ArtistView & TreeModel. --- include/tomahawk/tomahawkapp.h | 7 -- src/CMakeLists.txt | 14 ---- src/audiocontrols.cpp | 15 ++-- src/audiocontrols.h | 2 +- src/libtomahawk/CMakeLists.txt | 19 ++++- src/libtomahawk/album.cpp | 4 +- src/libtomahawk/artist.cpp | 4 +- .../database/databasecollection.cpp | 12 +-- .../infosystem/infoplugins/echonestplugin.cpp | 22 +++-- .../infosystem/infoplugins/echonestplugin.h | 14 ++-- .../infosystem/infoplugins/lastfmplugin.cpp | 40 ++++----- .../infosystem/infoplugins/lastfmplugin.h | 16 ++-- .../infoplugins/musixmatchplugin.cpp | 8 +- .../infosystem/infoplugins/musixmatchplugin.h | 13 +-- .../infosystem/infosystem.cpp | 44 ++++++---- .../libtomahawk/infosystem}/infosystem.h | 57 +++++++------ .../infosystem/infosystemcache.cpp | 0 .../infosystem/infosystemcache.h | 12 +-- src/libtomahawk/network/servent.cpp | 2 +- src/libtomahawk/playlist/albummodel.cpp | 6 +- src/libtomahawk/playlist/artistview.cpp | 19 +++++ src/libtomahawk/playlist/artistview.h | 3 + .../playlist/collectionflatmodel.cpp | 4 +- src/libtomahawk/playlist/playlistmanager.cpp | 4 + src/libtomahawk/playlist/treeitemdelegate.cpp | 6 +- src/libtomahawk/playlist/treemodel.cpp | 84 ++++++++++++------- src/libtomahawk/playlist/treemodel.h | 13 ++- src/libtomahawk/playlist/treeproxymodel.cpp | 6 ++ src/scanmanager.cpp | 28 +++---- src/scrobbler.cpp | 45 +++++----- src/scrobbler.h | 12 +-- src/tomahawkapp.cpp | 8 +- src/web/api_v1.h | 5 +- src/xmppbot/xmppbot.cpp | 59 +++++++------ src/xmppbot/xmppbot.h | 18 ++-- 35 files changed, 352 insertions(+), 273 deletions(-) rename src/{ => libtomahawk}/infosystem/infoplugins/echonestplugin.cpp (98%) rename src/{ => libtomahawk}/infosystem/infoplugins/echonestplugin.h (97%) rename src/{ => libtomahawk}/infosystem/infoplugins/lastfmplugin.cpp (98%) rename src/{ => libtomahawk}/infosystem/infoplugins/lastfmplugin.h (97%) rename src/{ => libtomahawk}/infosystem/infoplugins/musixmatchplugin.cpp (98%) rename src/{ => libtomahawk}/infosystem/infoplugins/musixmatchplugin.h (96%) rename src/{ => libtomahawk}/infosystem/infosystem.cpp (92%) rename {include/tomahawk => src/libtomahawk/infosystem}/infosystem.h (96%) rename src/{ => libtomahawk}/infosystem/infosystemcache.cpp (100%) rename src/{ => libtomahawk}/infosystem/infosystemcache.h (97%) diff --git a/include/tomahawk/tomahawkapp.h b/include/tomahawk/tomahawkapp.h index 5ff7b6c27..5e7a7db7f 100644 --- a/include/tomahawk/tomahawkapp.h +++ b/include/tomahawk/tomahawkapp.h @@ -52,10 +52,6 @@ class XMPPBot; namespace Tomahawk { class ShortcutHandler; - namespace InfoSystem - { - class InfoSystem; - } } #ifdef LIBLASTFM_FOUND @@ -83,7 +79,6 @@ public: static TomahawkApp* instance(); SipHandler* sipHandler() { return m_sipHandler; } - Tomahawk::InfoSystem::InfoSystem* infoSystem() { return m_infoSystem; } XMPPBot* xmppBot() { return m_xmppBot; } #ifndef TOMAHAWK_HEADLESS @@ -138,8 +133,6 @@ private: bool m_headless; - Tomahawk::InfoSystem::InfoSystem* m_infoSystem; - QxtHttpServerConnector m_connector; QxtHttpSessionManager m_session; }; diff --git a/src/CMakeLists.txt b/src/CMakeLists.txt index 933c6a7ac..1b9a95e65 100644 --- a/src/CMakeLists.txt +++ b/src/CMakeLists.txt @@ -34,12 +34,6 @@ ENDIF() SET( tomahawkSources ${tomahawkSources} sip/SipHandler.cpp - infosystem/infosystemcache.cpp - infosystem/infosystem.cpp - infosystem/infoplugins/echonestplugin.cpp - infosystem/infoplugins/lastfmplugin.cpp - infosystem/infoplugins/musixmatchplugin.cpp - web/api_v1.cpp resolvers/scriptresolver.cpp @@ -74,15 +68,9 @@ SET( tomahawkSourcesGui ${tomahawkSourcesGui} SET( tomahawkHeaders ${tomahawkHeaders} "${TOMAHAWK_INC_DIR}/tomahawk/tomahawkapp.h" - "${TOMAHAWK_INC_DIR}/tomahawk/infosystem.h" sip/SipHandler.h - infosystem/infosystemcache.h - infosystem/infoplugins/echonestplugin.h - infosystem/infoplugins/lastfmplugin.h - infosystem/infoplugins/musixmatchplugin.h - web/api_v1.h resolvers/scriptresolver.h @@ -137,8 +125,6 @@ INCLUDE_DIRECTORIES( topbar utils libtomahawk - libtomahawk/utils - infosystem mac ${THIRDPARTY_DIR}/alsa-playback diff --git a/src/audiocontrols.cpp b/src/audiocontrols.cpp index 4465e266b..93bfd71e7 100644 --- a/src/audiocontrols.cpp +++ b/src/audiocontrols.cpp @@ -21,7 +21,6 @@ #include -#include "tomahawk/tomahawkapp.h" #include "audio/audioengine.h" #include "playlist/playlistmanager.h" #include "utils/imagebutton.h" @@ -31,7 +30,7 @@ #define LASTFM_DEFAULT_COVER "http://cdn.last.fm/flatness/catalogue/noimage" -static QString s_infoIdentifier = QString( "AUDIOCONTROLS" ); +static QString s_acInfoIdentifier = QString( "AUDIOCONTROLS" ); AudioControls::AudioControls( QWidget* parent ) @@ -168,11 +167,11 @@ AudioControls::AudioControls( QWidget* parent ) m_defaultCover = QPixmap( RESPATH "images/no-album-art-placeholder.png" ) .scaled( ui->coverImage->size(), Qt::IgnoreAspectRatio, Qt::SmoothTransformation ); - connect( TomahawkApp::instance()->infoSystem(), + connect( Tomahawk::InfoSystem::InfoSystem::instance(), SIGNAL( info( QString, Tomahawk::InfoSystem::InfoType, QVariant, QVariant, Tomahawk::InfoSystem::InfoCustomData ) ), SLOT( infoSystemInfo( QString, Tomahawk::InfoSystem::InfoType, QVariant, QVariant, Tomahawk::InfoSystem::InfoCustomData ) ) ); - connect( TomahawkApp::instance()->infoSystem(), SIGNAL( finished( QString ) ), SLOT( infoSystemFinished( QString ) ) ); + connect( Tomahawk::InfoSystem::InfoSystem::instance(), SIGNAL( finished( QString ) ), SLOT( infoSystemFinished( QString ) ) ); onPlaybackStopped(); // initial state } @@ -220,8 +219,8 @@ AudioControls::onPlaybackStarted( const Tomahawk::result_ptr& result ) trackInfo["artist"] = QVariant::fromValue< QString >( result->artist()->name() ); trackInfo["album"] = QVariant::fromValue< QString >( result->album()->name() ); - TomahawkApp::instance()->infoSystem()->getInfo( - s_infoIdentifier, Tomahawk::InfoSystem::InfoAlbumCoverArt, + Tomahawk::InfoSystem::InfoSystem::instance()->getInfo( + s_acInfoIdentifier, Tomahawk::InfoSystem::InfoAlbumCoverArt, QVariant::fromValue< Tomahawk::InfoSystem::InfoCustomData >( trackInfo ), Tomahawk::InfoSystem::InfoCustomData() ); } @@ -232,8 +231,8 @@ AudioControls::infoSystemInfo( QString caller, Tomahawk::InfoSystem::InfoType ty Q_UNUSED( input ); Q_UNUSED( customData ); - qDebug() << Q_FUNC_INFO; - if ( caller != s_infoIdentifier || type != Tomahawk::InfoSystem::InfoAlbumCoverArt ) + qDebug() << Q_FUNC_INFO << caller << type << s_acInfoIdentifier << Tomahawk::InfoSystem::InfoAlbumCoverArt; + if ( caller != s_acInfoIdentifier || type != Tomahawk::InfoSystem::InfoAlbumCoverArt ) { qDebug() << "Info of wrong type or not with our identifier"; return; diff --git a/src/audiocontrols.h b/src/audiocontrols.h index 126608a05..a2de2d35b 100644 --- a/src/audiocontrols.h +++ b/src/audiocontrols.h @@ -23,7 +23,7 @@ #include "result.h" #include "playlistinterface.h" -#include "tomahawk/infosystem.h" +#include "infosystem/infosystem.h" namespace Ui { diff --git a/src/libtomahawk/CMakeLists.txt b/src/libtomahawk/CMakeLists.txt index 4b945ef7d..ae7dc0350 100644 --- a/src/libtomahawk/CMakeLists.txt +++ b/src/libtomahawk/CMakeLists.txt @@ -75,6 +75,12 @@ set( libSources database/databasecommand_clientauthvalid.cpp database/database.cpp + infosystem/infosystemcache.cpp + infosystem/infosystem.cpp + infosystem/infoplugins/echonestplugin.cpp + infosystem/infoplugins/lastfmplugin.cpp + infosystem/infoplugins/musixmatchplugin.cpp + playlist/treemodel.cpp playlist/treeproxymodel.cpp playlist/treeheader.cpp @@ -226,6 +232,12 @@ set( libHeaders database/databasecommand_addclientauth.h database/databasecommand_clientauthvalid.h + infosystem/infosystem.h + infosystem/infosystemcache.h + infosystem/infoplugins/echonestplugin.h + infosystem/infoplugins/lastfmplugin.h + infosystem/infoplugins/musixmatchplugin.h + network/bufferiodevice.h network/msgprocessor.h network/remotecollection.h @@ -330,9 +342,9 @@ include_directories( . ${CMAKE_CURRENT_BINARY_DIR} ${CMAKE_CURRENT_BINARY_DIR}/. ${LIBECHONEST_INCLUDE_DIR}/.. ${CLUCENE_INCLUDE_DIR} ${CLUCENE_LIBRARY_DIR} + ${CMAKE_BINARY_DIR}/thirdparty/liblastfm2/src ../../include - ../network playlist ${THIRDPARTY_DIR}/libportfwd/include @@ -394,6 +406,10 @@ IF( UNIX AND NOT APPLE ) ) ENDIF( UNIX AND NOT APPLE ) +IF(LIBLASTFM_FOUND) + SET(LINK_LIBRARIES ${LINK_LIBRARIES} tomahawk_lastfm2 ) +ENDIF(LIBLASTFM_FOUND) + qt4_wrap_ui( libUI_H ${libUI} ) qt4_wrap_cpp( libMoc ${libHeaders} ) @@ -422,6 +438,7 @@ target_link_libraries( tomahawklib ${QT_LIBRARIES} ${OS_SPECIFIC_LINK_LIBRARIES} ${CMAKE_THREAD_LIBS_INIT} + ${LINK_LIBRARIES} ) INSTALL( TARGETS tomahawklib DESTINATION lib${LIB_SUFFIX} ) diff --git a/src/libtomahawk/album.cpp b/src/libtomahawk/album.cpp index 5fede95c5..425183174 100644 --- a/src/libtomahawk/album.cpp +++ b/src/libtomahawk/album.cpp @@ -1,5 +1,5 @@ /* === This file is part of Tomahawk Player - === - * + * * Copyright 2010-2011, Christian Muehlhaeuser * * Tomahawk is free software: you can redistribute it and/or modify @@ -93,7 +93,7 @@ Album::tracks() cmd->setAlbum( this ); cmd->setSortOrder( DatabaseCommand_AllTracks::AlbumPosition ); - connect( cmd, SIGNAL( tracks( QList ) ), + connect( cmd, SIGNAL( tracks( QList, QVariant ) ), SLOT( onTracksAdded( QList ) ) ); Database::instance()->enqueue( QSharedPointer( cmd ) ); diff --git a/src/libtomahawk/artist.cpp b/src/libtomahawk/artist.cpp index bc130e55f..0d24772a4 100644 --- a/src/libtomahawk/artist.cpp +++ b/src/libtomahawk/artist.cpp @@ -1,5 +1,5 @@ /* === This file is part of Tomahawk Player - === - * + * * Copyright 2010-2011, Christian Muehlhaeuser * * Tomahawk is free software: you can redistribute it and/or modify @@ -95,7 +95,7 @@ Artist::tracks() cmd->setArtist( this ); cmd->setSortOrder( DatabaseCommand_AllTracks::Album ); - connect( cmd, SIGNAL( tracks( QList ) ), + connect( cmd, SIGNAL( tracks( QList, QVariant ) ), SLOT( onTracksAdded( QList ) ) ); Database::instance()->enqueue( QSharedPointer( cmd ) ); diff --git a/src/libtomahawk/database/databasecollection.cpp b/src/libtomahawk/database/databasecollection.cpp index 564180398..23e36584d 100644 --- a/src/libtomahawk/database/databasecollection.cpp +++ b/src/libtomahawk/database/databasecollection.cpp @@ -1,5 +1,5 @@ /* === This file is part of Tomahawk Player - === - * + * * Copyright 2010-2011, Christian Muehlhaeuser * * Tomahawk is free software: you can redistribute it and/or modify @@ -52,10 +52,10 @@ DatabaseCollection::loadDynamicPlaylists() { qDebug() << Q_FUNC_INFO; DatabaseCommand_LoadAllDynamicPlaylists* cmd = new DatabaseCommand_LoadAllDynamicPlaylists( source() ); - + connect( cmd, SIGNAL( playlistLoaded( Tomahawk::source_ptr, QVariantList ) ), SLOT( dynamicPlaylistCreated( const Tomahawk::source_ptr&, const QVariantList& ) ) ); - + Database::instance()->enqueue( QSharedPointer( cmd ) ); } @@ -68,7 +68,7 @@ DatabaseCollection::loadTracks() setLoaded(); DatabaseCommand_AllTracks* cmd = new DatabaseCommand_AllTracks( source()->collection() ); - connect( cmd, SIGNAL( tracks( QList ) ), + connect( cmd, SIGNAL( tracks( QList, QVariant ) ), SLOT( setTracks( QList ) ) ); Database::instance()->enqueue( QSharedPointer( cmd ) ); @@ -112,12 +112,12 @@ DatabaseCollection::playlists() QList< dynplaylist_ptr > DatabaseCollection::dynamicPlaylists() { qDebug() << Q_FUNC_INFO; - + if ( Collection::dynamicPlaylists().isEmpty() ) { loadDynamicPlaylists(); } - + return Collection::dynamicPlaylists(); } diff --git a/src/infosystem/infoplugins/echonestplugin.cpp b/src/libtomahawk/infosystem/infoplugins/echonestplugin.cpp similarity index 98% rename from src/infosystem/infoplugins/echonestplugin.cpp rename to src/libtomahawk/infosystem/infoplugins/echonestplugin.cpp index 7bec6c653..73dd3b7b2 100644 --- a/src/infosystem/infoplugins/echonestplugin.cpp +++ b/src/libtomahawk/infosystem/infoplugins/echonestplugin.cpp @@ -1,5 +1,5 @@ /* === This file is part of Tomahawk Player - === - * + * * Copyright 2010-2011, Christian Muehlhaeuser * * Tomahawk is free software: you can redistribute it and/or modify @@ -16,8 +16,6 @@ * along with Tomahawk. If not, see . */ -#include "tomahawk/infosystem.h" -#include "tomahawk/tomahawkapp.h" #include "echonestplugin.h" #include #include @@ -72,10 +70,10 @@ void EchoNestPlugin::getSongProfile(const QString &caller, const QVariant& data, if( !isValidTrackData( caller, data, customData ) ) return; - + // Track track( data.toString() ); // Artist artist( customData.data()->property("artistName").toString() ); -// reply->setProperty("artist", QVariant::fromValue(artist)); +// reply->setProperty("artist", QVariant::fromValue(artist)); // reply->setProperty( "data", data ); // m_replyMap[reply] = customData; // connect(reply, SIGNAL(finished()), SLOT(getArtistBiographySlot())); @@ -85,7 +83,7 @@ void EchoNestPlugin::getArtistBiography(const QString &caller, const QVariant& d { if( !isValidArtistData( caller, data, customData ) ) return; - + Echonest::Artist artist( data.toString() ); QNetworkReply *reply = artist.fetchBiographies(); reply->setProperty("artist", QVariant::fromValue(artist)); @@ -99,7 +97,7 @@ void EchoNestPlugin::getArtistFamiliarity(const QString &caller, const QVariant& { if( !isValidArtistData( caller, data, customData ) ) return; - + qDebug() << "Fetching artist familiarity!" << data; Echonest::Artist artist( data.toString() ); QNetworkReply* reply = artist.fetchFamiliarity(); @@ -107,14 +105,14 @@ void EchoNestPlugin::getArtistFamiliarity(const QString &caller, const QVariant& reply->setProperty( "data", data ); m_replyMap[reply] = customData; m_callerMap[reply] = caller; - connect(reply, SIGNAL(finished()), SLOT(getArtistFamiliaritySlot())); + connect(reply, SIGNAL(finished()), SLOT(getArtistFamiliaritySlot())); } void EchoNestPlugin::getArtistHotttnesss(const QString &caller, const QVariant& data, InfoCustomData &customData) { if( !isValidArtistData( caller, data, customData ) ) return; - + Echonest::Artist artist( data.toString() ); QNetworkReply* reply = artist.fetchHotttnesss(); reply->setProperty( "artist", QVariant::fromValue(artist)); @@ -128,7 +126,7 @@ void EchoNestPlugin::getArtistTerms(const QString &caller, const QVariant& data, { if( !isValidArtistData( caller, data, customData ) ) return; - + Echonest::Artist artist( data.toString() ); QNetworkReply* reply = artist.fetchTerms( Echonest::Artist::Weight ); reply->setProperty( "artist", QVariant::fromValue(artist)); @@ -162,7 +160,7 @@ void EchoNestPlugin::getArtistBiographySlot() biographyMap[biography.site()]["attribution"] = biography.license().attribution; biographyMap[biography.site()]["licensetype"] = biography.license().type; biographyMap[biography.site()]["attribution"] = biography.license().url.toString(); - + } emit info( m_callerMap[reply], Tomahawk::InfoSystem::InfoArtistBiography, reply->property( "data" ), QVariant::fromValue(biographyMap), m_replyMap[reply] ); m_replyMap.remove(reply); @@ -256,7 +254,7 @@ bool EchoNestPlugin::isValidTrackData(const QString &caller, const QVariant& dat emit info(caller, Tomahawk::InfoSystem::InfoNoInfo, QVariant(), QVariant(), customData); return false; } - if (!customData.contains("artistName") || + if (!customData.contains("artistName") || customData["artistName"].toString().isEmpty()) return false; return true; diff --git a/src/infosystem/infoplugins/echonestplugin.h b/src/libtomahawk/infosystem/infoplugins/echonestplugin.h similarity index 97% rename from src/infosystem/infoplugins/echonestplugin.h rename to src/libtomahawk/infosystem/infoplugins/echonestplugin.h index d4a4db367..25276c373 100644 --- a/src/infosystem/infoplugins/echonestplugin.h +++ b/src/libtomahawk/infosystem/infoplugins/echonestplugin.h @@ -1,5 +1,5 @@ /* === This file is part of Tomahawk Player - === - * + * * Copyright 2010-2011, Christian Muehlhaeuser * * Tomahawk is free software: you can redistribute it and/or modify @@ -19,7 +19,7 @@ #ifndef ECHONESTPLUGIN_H #define ECHONESTPLUGIN_H -#include "tomahawk/infosystem.h" +#include "infosystem/infosystem.h" #include @@ -37,13 +37,13 @@ namespace InfoSystem class EchoNestPlugin : public InfoPlugin { Q_OBJECT - + public: EchoNestPlugin(QObject *parent); virtual ~EchoNestPlugin(); - + void getInfo( const QString &caller, const InfoType type, const QVariant &data, InfoCustomData customData ); - + private: void getSongProfile( const QString &caller, const QVariant &data, InfoCustomData &customData, const QString &item = QString() ); void getArtistBiography ( const QString &caller, const QVariant &data, InfoCustomData &customData ); @@ -55,14 +55,14 @@ private: bool isValidArtistData( const QString &caller, const QVariant& data, InfoCustomData& customData ); bool isValidTrackData( const QString &caller, const QVariant& data, InfoCustomData& customData ); Echonest::Artist artistFromReply( QNetworkReply* ); - + private slots: void getArtistBiographySlot(); void getArtistFamiliaritySlot(); void getArtistHotttnesssSlot(); void getArtistTermsSlot(); void getMiscTopSlot(); - + private: QHash< QNetworkReply*, InfoCustomData > m_replyMap; QHash< QNetworkReply*, QString > m_callerMap; diff --git a/src/infosystem/infoplugins/lastfmplugin.cpp b/src/libtomahawk/infosystem/infoplugins/lastfmplugin.cpp similarity index 98% rename from src/infosystem/infoplugins/lastfmplugin.cpp rename to src/libtomahawk/infosystem/infoplugins/lastfmplugin.cpp index fcff625bf..0cb840265 100644 --- a/src/infosystem/infoplugins/lastfmplugin.cpp +++ b/src/libtomahawk/infosystem/infoplugins/lastfmplugin.cpp @@ -1,5 +1,5 @@ /* === This file is part of Tomahawk Player - === - * + * * Copyright 2010-2011, Christian Muehlhaeuser * * Tomahawk is free software: you can redistribute it and/or modify @@ -26,7 +26,7 @@ #include "typedefs.h" #include "audio/audioengine.h" #include "tomahawksettings.h" -#include "tomahawk/tomahawkapp.h" +#include "utils/tomahawkutils.h" #include #include @@ -49,7 +49,7 @@ LastFmPlugin::LastFmPlugin( QObject* parent ) QSet< InfoType > supportedTypes; supportedTypes << InfoMiscSubmitScrobble << InfoMiscSubmitNowPlaying << InfoAlbumCoverArt; qobject_cast< InfoSystem* >(parent)->registerInfoTypes(this, supportedTypes); - + /* Your API Key is 7194b85b6d1f424fe1668173a78c0c4a Your secret is ba80f1df6d27ae63e9cb1d33ccf2052f @@ -61,14 +61,14 @@ LastFmPlugin::LastFmPlugin( QObject* parent ) lastfm::ws::ApiKey = "7194b85b6d1f424fe1668173a78c0c4a"; lastfm::ws::SharedSecret = "ba80f1df6d27ae63e9cb1d33ccf2052f"; lastfm::ws::Username = TomahawkSettings::instance()->lastFmUsername(); - + m_pw = TomahawkSettings::instance()->lastFmPassword(); - + if( TomahawkSettings::instance()->scrobblingEnabled() && !lastfm::ws::Username.isEmpty() ) { createScrobbler(); } - + //HACK work around a bug in liblastfm---it doesn't create its config dir, so when it // tries to write the track cache, it fails silently. until we have a fixed version, do this // code taken from Amarok (src/services/lastfm/ScrobblerAdapter.cpp) @@ -126,7 +126,7 @@ LastFmPlugin::nowPlaying( const QString &caller, const InfoType type, const QVar dataError( caller, type, data, customData ); return; } - + m_track = lastfm::MutableTrack(); m_track.stamp(); @@ -154,7 +154,7 @@ LastFmPlugin::scrobble( const QString &caller, const InfoType type, const QVaria qDebug() << Q_FUNC_INFO << m_track.toString(); m_scrobbler->cache( m_track ); m_scrobbler->submit(); - + emit info( caller, type, data, QVariant(), customData ); } @@ -173,11 +173,11 @@ LastFmPlugin::fetchCoverArt( const QString &caller, const InfoType type, const Q dataError( caller, type, data, customData ); return; } - + Tomahawk::InfoSystem::InfoCacheCriteria criteria; criteria["artist"] = hash["artist"].toString(); criteria["album"] = hash["album"].toString(); - + emit getCachedInfo( criteria, 2419200000, caller, type, data, customData ); } @@ -217,7 +217,7 @@ LastFmPlugin::coverArtReturned() InfoCustomData returnedData; returnedData["imgbytes"] = ba; returnedData["url"] = reply->url().toString(); - + InfoCustomData customData = reply->property( "customData" ).value< Tomahawk::InfoSystem::InfoCustomData >(); InfoType type = (Tomahawk::InfoSystem::InfoType)(reply->property( "type" ).toUInt()); emit info( @@ -227,12 +227,12 @@ LastFmPlugin::coverArtReturned() returnedData, customData ); - + InfoCustomData origData = reply->property( "origData" ).value< Tomahawk::InfoSystem::InfoCustomData >(); Tomahawk::InfoSystem::InfoCacheCriteria criteria; criteria["artist"] = origData["artist"].toString(); criteria["album"] = origData["album"].toString(); - emit updateCache( criteria, type, returnedData ); + emit updateCache( criteria, 2419200000, type, returnedData ); } else { @@ -280,12 +280,12 @@ LastFmPlugin::settingsChanged() void LastFmPlugin::onAuthenticated() { - if( !m_authJob ) + if( !m_authJob ) { qDebug() << Q_FUNC_INFO << "Help! No longer got a last.fm auth job!"; return; } - + if( m_authJob->error() == QNetworkReply::NoError ) { lastfm::XmlQuery lfm = lastfm::XmlQuery( m_authJob->readAll() ); @@ -294,7 +294,7 @@ LastFmPlugin::onAuthenticated() { qDebug() << "Error from authenticating with Last.fm service:" << lfm.text(); TomahawkSettings::instance()->setLastFmSessionKey( QByteArray() ); - + } else { @@ -310,7 +310,7 @@ LastFmPlugin::onAuthenticated() { qDebug() << "Got error in Last.fm authentication job:" << m_authJob->errorString(); } - + m_authJob->deleteLater(); } @@ -321,19 +321,19 @@ LastFmPlugin::createScrobbler() if( TomahawkSettings::instance()->lastFmSessionKey().isEmpty() ) // no session key, so get one { QString authToken = md5( ( lastfm::ws::Username.toLower() + md5( m_pw.toUtf8() ) ).toUtf8() ); - + QMap query; query[ "method" ] = "auth.getMobileSession"; query[ "username" ] = lastfm::ws::Username; query[ "authToken" ] = authToken; m_authJob = lastfm::ws::post( query ); - + connect( m_authJob, SIGNAL( finished() ), SLOT( onAuthenticated() ) ); } else { lastfm::ws::SessionKey = TomahawkSettings::instance()->lastFmSessionKey(); - + m_scrobbler = new lastfm::Audioscrobbler( "thk" ); m_scrobbler->moveToThread( thread() ); } diff --git a/src/infosystem/infoplugins/lastfmplugin.h b/src/libtomahawk/infosystem/infoplugins/lastfmplugin.h similarity index 97% rename from src/infosystem/infoplugins/lastfmplugin.h rename to src/libtomahawk/infosystem/infoplugins/lastfmplugin.h index 589d40db1..00b27b15e 100644 --- a/src/infosystem/infoplugins/lastfmplugin.h +++ b/src/libtomahawk/infosystem/infoplugins/lastfmplugin.h @@ -1,5 +1,5 @@ /* === This file is part of Tomahawk Player - === - * + * * Copyright 2010-2011, Christian Muehlhaeuser * * Tomahawk is free software: you can redistribute it and/or modify @@ -18,7 +18,7 @@ #ifndef LASTFMPLUGIN_H #define LASTFMPLUGIN_H -#include "tomahawk/infosystem.h" +#include "infosystem/infosystem.h" #include "result.h" #include @@ -38,30 +38,30 @@ namespace InfoSystem class LastFmPlugin : public InfoPlugin { Q_OBJECT - + public: LastFmPlugin( QObject *parent ); virtual ~LastFmPlugin(); - + void getInfo( const QString &caller, const InfoType type, const QVariant &data, InfoCustomData customData ); - + public slots: void settingsChanged(); void onAuthenticated(); void coverArtReturned(); virtual void notInCacheSlot( Tomahawk::InfoSystem::InfoCacheCriteria criteria, QString caller, Tomahawk::InfoSystem::InfoType type, QVariant input, Tomahawk::InfoSystem::InfoCustomData customData ); - + private: void fetchCoverArt( const QString &caller, const InfoType type, const QVariant& data, Tomahawk::InfoSystem::InfoCustomData &customData ); void scrobble( const QString &caller, const InfoType type, const QVariant& data, InfoCustomData &customData ); void createScrobbler(); void nowPlaying( const QString &caller, const InfoType type, const QVariant& data, InfoCustomData &customData ); void dataError( const QString &caller, const InfoType type, const QVariant& data, InfoCustomData &customData ); - + lastfm::MutableTrack m_track; lastfm::Audioscrobbler* m_scrobbler; QString m_pw; - + QNetworkReply* m_authJob; }; diff --git a/src/infosystem/infoplugins/musixmatchplugin.cpp b/src/libtomahawk/infosystem/infoplugins/musixmatchplugin.cpp similarity index 98% rename from src/infosystem/infoplugins/musixmatchplugin.cpp rename to src/libtomahawk/infosystem/infoplugins/musixmatchplugin.cpp index 128085ca6..914a29e27 100644 --- a/src/infosystem/infoplugins/musixmatchplugin.cpp +++ b/src/libtomahawk/infosystem/infoplugins/musixmatchplugin.cpp @@ -1,5 +1,5 @@ /* === This file is part of Tomahawk Player - === - * + * * Copyright 2010-2011, Christian Muehlhaeuser * * Tomahawk is free software: you can redistribute it and/or modify @@ -16,10 +16,10 @@ * along with Tomahawk. If not, see . */ -#include "tomahawk/infosystem.h" -#include "tomahawk/tomahawkapp.h" #include "musixmatchplugin.h" +#include "utils/tomahawkutils.h" + #include #include @@ -65,7 +65,7 @@ void MusixMatchPlugin::getInfo(const QString &caller, const InfoType type, const reply->setProperty("customData", QVariant::fromValue(customData)); reply->setProperty("origData", data); reply->setProperty("caller", caller); - + connect(reply, SIGNAL(finished()), SLOT(trackSearchSlot())); } diff --git a/src/infosystem/infoplugins/musixmatchplugin.h b/src/libtomahawk/infosystem/infoplugins/musixmatchplugin.h similarity index 96% rename from src/infosystem/infoplugins/musixmatchplugin.h rename to src/libtomahawk/infosystem/infoplugins/musixmatchplugin.h index 255ebd53d..3fcb1ded7 100644 --- a/src/infosystem/infoplugins/musixmatchplugin.h +++ b/src/libtomahawk/infosystem/infoplugins/musixmatchplugin.h @@ -1,5 +1,5 @@ /* === This file is part of Tomahawk Player - === - * + * * Copyright 2010-2011, Christian Muehlhaeuser * * Tomahawk is free software: you can redistribute it and/or modify @@ -18,7 +18,8 @@ #ifndef MUSIXMATCHPLUGIN_H #define MUSIXMATCHPLUGIN_H -#include "tomahawk/infosystem.h" + +#include "infosystem/infosystem.h" class QNetworkReply; @@ -31,16 +32,16 @@ namespace InfoSystem class MusixMatchPlugin : public InfoPlugin { Q_OBJECT - + public: MusixMatchPlugin(QObject *parent); virtual ~MusixMatchPlugin(); - + void getInfo(const QString &caller, const InfoType type, const QVariant &data, InfoCustomData customData); - + private: bool isValidTrackData( const QString &caller, const QVariant& data, InfoCustomData &customData ); - + public slots: void trackSearchSlot(); void trackLyricsSlot(); diff --git a/src/infosystem/infosystem.cpp b/src/libtomahawk/infosystem/infosystem.cpp similarity index 92% rename from src/infosystem/infosystem.cpp rename to src/libtomahawk/infosystem/infosystem.cpp index 7fd6158c6..6c8357ed6 100644 --- a/src/infosystem/infosystem.cpp +++ b/src/libtomahawk/infosystem/infosystem.cpp @@ -1,5 +1,5 @@ /* === This file is part of Tomahawk Player - === - * + * * Copyright 2010-2011, Christian Muehlhaeuser * * Tomahawk is free software: you can redistribute it and/or modify @@ -18,8 +18,8 @@ #include -#include "tomahawk/infosystem.h" -#include "tomahawkutils.h" +#include "infosystem.h" +#include "utils/tomahawkutils.h" #include "infosystemcache.h" #include "infoplugins/echonestplugin.h" #include "infoplugins/musixmatchplugin.h" @@ -27,7 +27,7 @@ namespace Tomahawk { - + namespace InfoSystem { @@ -40,9 +40,9 @@ InfoPlugin::InfoPlugin(QObject *parent) { QObject::connect( this, - SIGNAL( getCachedInfo( Tomahawk::InfoSystem::InfoCacheCriteria, QString, Tomahawk::InfoSystem::InfoType, QVariant, Tomahawk::InfoSystem::InfoCustomData ) ), + SIGNAL( getCachedInfo( Tomahawk::InfoSystem::InfoCacheCriteria, qint64, QString, Tomahawk::InfoSystem::InfoType, QVariant, Tomahawk::InfoSystem::InfoCustomData ) ), system->getCache(), - SLOT( getCachedInfoSlot( Tomahawk::InfoSystem::InfoCacheCriteria, QString, Tomahawk::InfoSystem::InfoType, QVariant, Tomahawk::InfoSystem::InfoCustomData ) ) + SLOT( getCachedInfoSlot( Tomahawk::InfoSystem::InfoCacheCriteria, qint64, QString, Tomahawk::InfoSystem::InfoType, QVariant, Tomahawk::InfoSystem::InfoCustomData ) ) ); QObject::connect( system->getCache(), @@ -52,35 +52,45 @@ InfoPlugin::InfoPlugin(QObject *parent) ); QObject::connect( this, - SIGNAL( updateCache( Tomahawk::InfoSystem::InfoCacheCriteria, Tomahawk::InfoSystem::InfoType, QVariant ) ), + SIGNAL( updateCache( Tomahawk::InfoSystem::InfoCacheCriteria, qint64, Tomahawk::InfoSystem::InfoType, QVariant ) ), system->getCache(), - SLOT( updateCacheSlot( Tomahawk::InfoSystem::InfoCacheCriteria, Tomahawk::InfoSystem::InfoType, QVariant ) ) + SLOT( updateCacheSlot( Tomahawk::InfoSystem::InfoCacheCriteria, qint64, Tomahawk::InfoSystem::InfoType, QVariant ) ) ); } } +InfoSystem* InfoSystem::s_instance = 0; + +InfoSystem* +InfoSystem::instance() +{ + return s_instance; +} + InfoSystem::InfoSystem(QObject *parent) : QObject(parent) { + s_instance = this; + qDebug() << Q_FUNC_INFO; qRegisterMetaType< QMap< QString, QMap< QString, QString > > >( "Tomahawk::InfoSystem::InfoGenericMap" ); qRegisterMetaType< QHash< QString, QVariant > >( "Tomahawk::InfoSystem::InfoCustomData" ); qRegisterMetaType< QHash< QString, QString > >( "Tomahawk::InfoSystem::InfoCacheCriteria" ); qRegisterMetaType< Tomahawk::InfoSystem::InfoType >( "Tomahawk::InfoSystem::InfoType" ); - + m_infoSystemCacheThreadController = new QThread( this ); m_cache = new InfoSystemCache(); m_cache->moveToThread( m_infoSystemCacheThreadController ); m_infoSystemCacheThreadController->start( QThread::IdlePriority ); - + InfoPluginPtr enptr( new EchoNestPlugin( this ) ); m_plugins.append( enptr ); InfoPluginPtr mmptr( new MusixMatchPlugin( this ) ); m_plugins.append( mmptr ); InfoPluginPtr lfmptr( new LastFmPlugin( this ) ); m_plugins.append( lfmptr ); - + Q_FOREACH( InfoPluginPtr plugin, m_plugins ) { connect( @@ -107,19 +117,19 @@ InfoSystem::~InfoSystem() if( m_infoSystemCacheThreadController ) { m_infoSystemCacheThreadController->quit(); - + while( !m_infoSystemCacheThreadController->isFinished() ) { QCoreApplication::processEvents( QEventLoop::AllEvents, 200 ); TomahawkUtils::Sleep::msleep( 100 ); } - + if( m_cache ) { delete m_cache; m_cache = 0; } - + delete m_infoSystemCacheThreadController; m_infoSystemCacheThreadController = 0; } @@ -152,7 +162,7 @@ void InfoSystem::getInfo(const QString &caller, const InfoType type, const QVari emit finished(caller); return; } - + InfoPluginPtr ptr = providers.first(); if (!ptr) { @@ -160,7 +170,7 @@ void InfoSystem::getInfo(const QString &caller, const InfoType type, const QVari emit finished(caller); return; } - + m_dataTracker[caller][type] = m_dataTracker[caller][type] + 1; qDebug() << "current count in dataTracker for type" << type << "is" << m_dataTracker[caller][type]; ptr.data()->getInfo(caller, type, data, customData); @@ -182,7 +192,7 @@ void InfoSystem::infoSlot(QString target, InfoType type, QVariant input, QVarian return; } emit info(target, type, input, output, customData); - + m_dataTracker[target][type] = m_dataTracker[target][type] - 1; qDebug() << "current count in dataTracker is " << m_dataTracker[target][type]; Q_FOREACH(InfoType testtype, m_dataTracker[target].keys()) diff --git a/include/tomahawk/infosystem.h b/src/libtomahawk/infosystem/infosystem.h similarity index 96% rename from include/tomahawk/infosystem.h rename to src/libtomahawk/infosystem/infosystem.h index e1f0017c5..7f883e20f 100644 --- a/include/tomahawk/infosystem.h +++ b/src/libtomahawk/infosystem/infosystem.h @@ -1,5 +1,5 @@ /* === This file is part of Tomahawk Player - === - * + * * Copyright 2010-2011, Christian Muehlhaeuser * * Tomahawk is free software: you can redistribute it and/or modify @@ -34,7 +34,7 @@ namespace Tomahawk { namespace InfoSystem { class InfoSystemCache; - + enum InfoType { InfoTrackID = 0, InfoTrackArtist, @@ -58,7 +58,7 @@ enum InfoType { InfoTrackDanceability, InfoTrackTempo, InfoTrackLoudness, - + InfoArtistID, InfoArtistName, InfoArtistBiography, @@ -74,7 +74,7 @@ enum InfoType { InfoArtistTerms, InfoArtistLinks, InfoArtistVideos, - + InfoAlbumID, InfoAlbumName, InfoAlbumArtist, @@ -85,10 +85,10 @@ enum InfoType { InfoMiscTopHotttness, InfoMiscTopTerms, - + InfoMiscSubmitNowPlaying, InfoMiscSubmitScrobble, - + InfoNoInfo }; @@ -100,7 +100,7 @@ typedef QHash< QString, QString > InfoCacheCriteria; class InfoPlugin : public QObject { Q_OBJECT - + public: InfoPlugin( QObject *parent ); @@ -108,15 +108,15 @@ public: { qDebug() << Q_FUNC_INFO; } - + virtual void getInfo( const QString &caller, const InfoType type, const QVariant &data, InfoCustomData customData ) = 0; - + signals: void getCachedInfo( Tomahawk::InfoSystem::InfoCacheCriteria criteria, qint64 newMaxAge, QString caller, Tomahawk::InfoSystem::InfoType type, QVariant input, Tomahawk::InfoSystem::InfoCustomData customData ); - void updateCache( Tomahawk::InfoSystem::InfoCacheCriteria criteria, Tomahawk::InfoSystem::InfoType type, QVariant output ); + void updateCache( Tomahawk::InfoSystem::InfoCacheCriteria criteria, qint64, Tomahawk::InfoSystem::InfoType type, QVariant output ); void info( QString caller, Tomahawk::InfoSystem::InfoType type, QVariant input, QVariant output, Tomahawk::InfoSystem::InfoCustomData customData ); void finished( QString, Tomahawk::InfoSystem::InfoType ); - + public slots: //FIXME: Make pure virtual when everything supports it virtual void notInCacheSlot( Tomahawk::InfoSystem::InfoCacheCriteria criteria, QString caller, Tomahawk::InfoSystem::InfoType type, QVariant input, Tomahawk::InfoSystem::InfoCustomData customData ) @@ -127,7 +127,7 @@ public slots: Q_UNUSED( input ); Q_UNUSED( customData ); } - + protected: InfoType m_type; }; @@ -137,38 +137,41 @@ typedef QWeakPointer< InfoPlugin > InfoPluginPtr; class InfoSystem : public QObject { Q_OBJECT - + public: - + static InfoSystem* instance(); + InfoSystem( QObject *parent ); ~InfoSystem(); - + void registerInfoTypes( const InfoPluginPtr &plugin, const QSet< InfoType > &types ); - + void getInfo( const QString &caller, const InfoType type, const QVariant &data, InfoCustomData customData ); void getInfo( const QString &caller, const InfoMap &input, InfoCustomData customData ); - + InfoSystemCache* getCache() { return m_cache; } signals: void info( QString caller, Tomahawk::InfoSystem::InfoType, QVariant input, QVariant output, Tomahawk::InfoSystem::InfoCustomData customData ); void finished( QString target ); - + public slots: void infoSlot( QString target, Tomahawk::InfoSystem::InfoType type, QVariant input, QVariant output, Tomahawk::InfoSystem::InfoCustomData customData ); - + private: QLinkedList< InfoPluginPtr > determineOrderedMatches( const InfoType type ) const; - + QMap< InfoType, QLinkedList< InfoPluginPtr > > m_infoMap; - + // For now, statically instantiate plugins; this is just somewhere to keep them QLinkedList< InfoPluginPtr > m_plugins; - + QHash< QString, QHash< InfoType, int > > m_dataTracker; - + InfoSystemCache* m_cache; QThread* m_infoSystemCacheThreadController; + + static InfoSystem* s_instance; }; } @@ -182,14 +185,14 @@ inline uint qHash( Tomahawk::InfoSystem::InfoCacheCriteria hash ) md5.addData( key.toUtf8() ); foreach( QString value, hash.values() ) md5.addData( value.toUtf8() ); - + QString hexData = md5.result(); - + uint returnval = 0; - + foreach( uint val, hexData.toUcs4() ) returnval += val; - + return returnval; } diff --git a/src/infosystem/infosystemcache.cpp b/src/libtomahawk/infosystem/infosystemcache.cpp similarity index 100% rename from src/infosystem/infosystemcache.cpp rename to src/libtomahawk/infosystem/infosystemcache.cpp diff --git a/src/infosystem/infosystemcache.h b/src/libtomahawk/infosystem/infosystemcache.h similarity index 97% rename from src/infosystem/infosystemcache.h rename to src/libtomahawk/infosystem/infosystemcache.h index 0ccad6d19..20307eab8 100644 --- a/src/infosystem/infosystemcache.h +++ b/src/libtomahawk/infosystem/infosystemcache.h @@ -1,5 +1,5 @@ /* === This file is part of Tomahawk Player - === - * + * * Copyright 2010-2011, Christian Muehlhaeuser * * Tomahawk is free software: you can redistribute it and/or modify @@ -23,11 +23,11 @@ #include #include -#include "tomahawk/infosystem.h" +#include "infosystem.h" namespace Tomahawk { - + namespace InfoSystem { @@ -37,7 +37,7 @@ Q_OBJECT public: InfoSystemCache( QObject *parent = 0 ); - + virtual ~InfoSystemCache(); signals: @@ -47,11 +47,11 @@ signals: public slots: void getCachedInfoSlot( Tomahawk::InfoSystem::InfoCacheCriteria criteria, qint64 newMaxAge, QString caller, Tomahawk::InfoSystem::InfoType type, QVariant input, Tomahawk::InfoSystem::InfoCustomData customData ); void updateCacheSlot( Tomahawk::InfoSystem::InfoCacheCriteria criteria, qint64 maxAge, Tomahawk::InfoSystem::InfoType type, QVariant output ); - + private: void loadCache( InfoType type, const QString &cacheFile ); void saveCache( InfoType type, const QString &cacheDir ); - + QHash< InfoType, QHash< InfoCacheCriteria, QVariant > > m_dataCache; QHash< InfoType, QHash< InfoCacheCriteria, QDateTime > > m_insertTimeCache; QHash< InfoType, QHash< InfoCacheCriteria, QDateTime > > m_maxTimeCache; diff --git a/src/libtomahawk/network/servent.cpp b/src/libtomahawk/network/servent.cpp index 16f46d428..a1ddff6a2 100644 --- a/src/libtomahawk/network/servent.cpp +++ b/src/libtomahawk/network/servent.cpp @@ -26,6 +26,7 @@ #include #include #include +#include #include #include "result.h" @@ -41,7 +42,6 @@ #include "tomahawksettings.h" #include "utils/tomahawkutils.h" #include -#include using namespace Tomahawk; diff --git a/src/libtomahawk/playlist/albummodel.cpp b/src/libtomahawk/playlist/albummodel.cpp index 8a1d36cb2..bf4ea4c05 100644 --- a/src/libtomahawk/playlist/albummodel.cpp +++ b/src/libtomahawk/playlist/albummodel.cpp @@ -240,8 +240,8 @@ AlbumModel::addCollection( const collection_ptr& collection ) DatabaseCommand_AllAlbums* cmd = new DatabaseCommand_AllAlbums( collection ); - connect( cmd, SIGNAL( albums( QList, Tomahawk::collection_ptr ) ), - SLOT( onAlbumsAdded( QList, Tomahawk::collection_ptr ) ) ); + connect( cmd, SIGNAL( albums( QList, QVariant ) ), + SLOT( onAlbumsAdded( QList ) ) ); Database::instance()->enqueue( QSharedPointer( cmd ) ); @@ -262,7 +262,7 @@ AlbumModel::addFilteredCollection( const collection_ptr& collection, unsigned in cmd->setSortOrder( order ); cmd->setSortDescending( true ); - connect( cmd, SIGNAL( albums( QList ) ), + connect( cmd, SIGNAL( albums( QList, QVariant ) ), SLOT( onAlbumsAdded( QList ) ) ); Database::instance()->enqueue( QSharedPointer( cmd ) ); diff --git a/src/libtomahawk/playlist/artistview.cpp b/src/libtomahawk/playlist/artistview.cpp index ab79538b4..88f83dc08 100644 --- a/src/libtomahawk/playlist/artistview.cpp +++ b/src/libtomahawk/playlist/artistview.cpp @@ -27,6 +27,7 @@ #include "audio/audioengine.h" #include "tomahawksettings.h" +#include "treeheader.h" #include "treeitemdelegate.h" #include "playlistmanager.h" @@ -37,17 +38,35 @@ ArtistView::ArtistView( QWidget* parent ) : QTreeView( parent ) , m_model( 0 ) , m_proxyModel( 0 ) + , m_header( new TreeHeader( this ) ) // , m_delegate( 0 ) { + setAlternatingRowColors( true ); setDragEnabled( true ); setDropIndicatorShown( false ); setDragDropOverwriteMode( false ); setUniformRowHeights( false ); setVerticalScrollMode( QAbstractItemView::ScrollPerPixel ); setRootIsDecorated( true ); + setAnimated( false ); + setAllColumnsShowFocus( true ); + setSelectionMode( QAbstractItemView::ExtendedSelection ); + setSelectionBehavior( QAbstractItemView::SelectRows ); + setHeader( m_header ); setProxyModel( new TreeProxyModel( this ) ); + #ifndef Q_WS_WIN + QFont f = font(); + f.setPointSize( f.pointSize() - 1 ); + setFont( f ); + #endif + + #ifdef Q_WS_MAC + f.setPointSize( f.pointSize() - 2 ); + setFont( f ); + #endif + connect( this, SIGNAL( doubleClicked( QModelIndex ) ), SLOT( onItemActivated( QModelIndex ) ) ); } diff --git a/src/libtomahawk/playlist/artistview.h b/src/libtomahawk/playlist/artistview.h index e22205c24..d272df314 100644 --- a/src/libtomahawk/playlist/artistview.h +++ b/src/libtomahawk/playlist/artistview.h @@ -28,6 +28,8 @@ #include "dllmacro.h" +class TreeHeader; + class DLLEXPORT ArtistView : public QTreeView, public Tomahawk::ViewPage { Q_OBJECT @@ -71,6 +73,7 @@ private slots: private: QPixmap createDragPixmap( int itemCount ) const; + TreeHeader* m_header; TreeModel* m_model; TreeProxyModel* m_proxyModel; // PlaylistItemDelegate* m_delegate; diff --git a/src/libtomahawk/playlist/collectionflatmodel.cpp b/src/libtomahawk/playlist/collectionflatmodel.cpp index aa7cae7e6..2090b6cba 100644 --- a/src/libtomahawk/playlist/collectionflatmodel.cpp +++ b/src/libtomahawk/playlist/collectionflatmodel.cpp @@ -102,8 +102,8 @@ CollectionFlatModel::addFilteredCollection( const collection_ptr& collection, un cmd->setSortOrder( order ); cmd->setSortDescending( true ); - connect( cmd, SIGNAL( tracks( QList, Tomahawk::collection_ptr ) ), - SLOT( onTracksAdded( QList, Tomahawk::collection_ptr ) ), Qt::QueuedConnection ); + connect( cmd, SIGNAL( tracks( QList, QVariant ) ), + SLOT( onTracksAdded( QList ) ), Qt::QueuedConnection ); Database::instance()->enqueue( QSharedPointer( cmd ) ); } diff --git a/src/libtomahawk/playlist/playlistmanager.cpp b/src/libtomahawk/playlist/playlistmanager.cpp index 52a08c778..cbf1337cf 100644 --- a/src/libtomahawk/playlist/playlistmanager.cpp +++ b/src/libtomahawk/playlist/playlistmanager.cpp @@ -390,6 +390,10 @@ PlaylistManager::showSuperCollection() { setPage( m_superCollectionView ); } + else if ( m_currentMode == 1 ) + { + setPage( m_superCollectionView ); + } else if ( m_currentMode == 2 ) { setPage( m_superAlbumView ); diff --git a/src/libtomahawk/playlist/treeitemdelegate.cpp b/src/libtomahawk/playlist/treeitemdelegate.cpp index fbc1fa16b..51704d7e8 100644 --- a/src/libtomahawk/playlist/treeitemdelegate.cpp +++ b/src/libtomahawk/playlist/treeitemdelegate.cpp @@ -78,6 +78,9 @@ TreeItemDelegate::paint( QPainter* painter, const QStyleOptionViewItem& option, opt.palette.setColor( QPalette::Text, opt.palette.color( QPalette::HighlightedText ) ); } + if ( index.column() > 0 ) + return; + painter->save(); painter->setRenderHint( QPainter::Antialiasing ); painter->setPen( opt.palette.color( QPalette::Text ) ); @@ -92,7 +95,8 @@ TreeItemDelegate::paint( QPainter* painter, const QStyleOptionViewItem& option, QFont boldFont = opt.font; boldFont.setBold( true ); - r = option.rect.adjusted( option.rect.height(), 6, 0, -option.rect.height() + 22 ); + r = option.rect.adjusted( option.rect.height(), 6, -4, -option.rect.height() + 22 ); + text = painter->fontMetrics().elidedText( text, Qt::ElideRight, r.width() ); painter->drawText( r, text, to ); painter->setFont( boldFont ); diff --git a/src/libtomahawk/playlist/treemodel.cpp b/src/libtomahawk/playlist/treemodel.cpp index 86dc33077..d95f10240 100644 --- a/src/libtomahawk/playlist/treemodel.cpp +++ b/src/libtomahawk/playlist/treemodel.cpp @@ -30,7 +30,7 @@ #define LASTFM_DEFAULT_COVER "http://cdn.last.fm/flatness/catalogue/noimage" -static QString s_infoIdentifier = QString( "TREEMODEL" ); +static QString s_tmInfoIdentifier = QString( "TREEMODEL" ); using namespace Tomahawk; @@ -44,11 +44,11 @@ TreeModel::TreeModel( QObject* parent ) m_defaultCover = QPixmap( RESPATH "images/no-album-art-placeholder.png" ) .scaled( QSize( 120, 120 ), Qt::IgnoreAspectRatio, Qt::SmoothTransformation ); -/* connect( TomahawkApp::instance()->infoSystem(), + connect( Tomahawk::InfoSystem::InfoSystem::instance(), SIGNAL( info( QString, Tomahawk::InfoSystem::InfoType, QVariant, QVariant, Tomahawk::InfoSystem::InfoCustomData ) ), SLOT( infoSystemInfo( QString, Tomahawk::InfoSystem::InfoType, QVariant, QVariant, Tomahawk::InfoSystem::InfoCustomData ) ) ); - connect( TomahawkApp::instance()->infoSystem(), SIGNAL( finished( QString ) ), SLOT( infoSystemFinished( QString ) ) );*/ + connect( Tomahawk::InfoSystem::InfoSystem::instance(), SIGNAL( finished( QString ) ), SLOT( infoSystemFinished( QString ) ) ); } @@ -142,7 +142,7 @@ int TreeModel::columnCount( const QModelIndex& parent ) const { Q_UNUSED( parent ); - return 4; + return 7; } @@ -190,22 +190,47 @@ TreeModel::data( const QModelIndex& index, int role ) const if ( role != Qt::DisplayRole ) // && role != Qt::ToolTipRole ) return QVariant(); - switch( index.column() ) + if ( !entry->artist().isNull() && index.column() == Name ) { - case 0: - if ( !entry->artist().isNull() ) - { - return entry->artist()->name(); - } - else if ( !entry->artist().isNull() ) - { - return entry->album()->name(); - } - else if ( !entry->result().isNull() ) - { - return entry->result()->track(); - } - break; + return entry->artist()->name(); + } + else if ( !entry->album().isNull() && index.column() == Name ) + { + return entry->album()->name(); + } + else + { + const result_ptr& result = entry->result(); + switch( index.column() ) + { + case Name: + return QString( "%1%2" ).arg( result->albumpos() > 0 ? QString( "%1. ").arg( result->albumpos() ) : QString() ) + .arg( result->track() ); + + case Duration: + return TomahawkUtils::timeToString( result->duration() ); + + case Bitrate: + return result->bitrate(); + + case Age: + return TomahawkUtils::ageToString( QDateTime::fromTime_t( result->modificationTime() ) ); + + case Year: + return result->year(); + + case Filesize: + return TomahawkUtils::filesizeToString( result->size() ); + + case Origin: + return result->friendlySource(); + + case AlbumPosition: + return result->albumpos(); + + default: + return QVariant(); + } } return QVariant(); @@ -216,7 +241,7 @@ QVariant TreeModel::headerData( int section, Qt::Orientation orientation, int role ) const { QStringList headers; - headers << tr( "Artist" ); + headers << tr( "Name" ) << tr( "Duration" ) << tr( "Bitrate" ) << tr( "Age" ) << tr( "Year" ) << tr( "Size" ) << tr( "Origin" ); if ( orientation == Qt::Horizontal && role == Qt::DisplayRole && section >= 0 ) { return headers.at( section ); @@ -453,14 +478,14 @@ TreeModel::onAlbumsAdded( const QList& albums, const QVaria albumitem->index = createIndex( parentItem->children.count() - 1, 0, albumitem ); connect( albumitem, SIGNAL( dataChanged() ), SLOT( onDataChanged() ) ); -/* Tomahawk::InfoSystem::InfoCustomData trackInfo; + Tomahawk::InfoSystem::InfoCustomData trackInfo; trackInfo["artist"] = QVariant::fromValue< QString >( album->artist()->name() ); trackInfo["album"] = QVariant::fromValue< QString >( album->name() ); - trackInfo["pptr"] = (qlonglong)albumitem; + trackInfo["pptr"] = QVariant::fromValue< qlonglong >( (qlonglong)albumitem ); - TomahawkApp::instance()->infoSystem()->getInfo( - s_infoIdentifier, Tomahawk::InfoSystem::InfoAlbumCoverArt, - QVariant::fromValue< Tomahawk::InfoSystem::InfoCustomData >( trackInfo ), Tomahawk::InfoSystem::InfoCustomData() );*/ + Tomahawk::InfoSystem::InfoSystem::instance()->getInfo( + s_tmInfoIdentifier, Tomahawk::InfoSystem::InfoAlbumCoverArt, + QVariant::fromValue< Tomahawk::InfoSystem::InfoCustomData >( trackInfo ), Tomahawk::InfoSystem::InfoCustomData() ); } if ( crows.second > 0 ) @@ -517,7 +542,7 @@ void TreeModel::infoSystemInfo( QString caller, Tomahawk::InfoSystem::InfoType type, QVariant input, QVariant output, Tomahawk::InfoSystem::InfoCustomData customData ) { qDebug() << Q_FUNC_INFO; - if ( caller != s_infoIdentifier || type != Tomahawk::InfoSystem::InfoAlbumCoverArt ) + if ( caller != s_tmInfoIdentifier || type != Tomahawk::InfoSystem::InfoAlbumCoverArt ) { qDebug() << "Info of wrong type or not with our identifier"; return; @@ -529,6 +554,7 @@ TreeModel::infoSystemInfo( QString caller, Tomahawk::InfoSystem::InfoType type, return; } + Tomahawk::InfoSystem::InfoCustomData pptr = input.value< Tomahawk::InfoSystem::InfoCustomData >(); Tomahawk::InfoSystem::InfoCustomData returnedData = output.value< Tomahawk::InfoSystem::InfoCustomData >(); const QByteArray ba = returnedData["imgbytes"].toByteArray(); if ( ba.length() ) @@ -536,13 +562,15 @@ TreeModel::infoSystemInfo( QString caller, Tomahawk::InfoSystem::InfoType type, QPixmap pm; pm.loadFromData( ba ); - qlonglong pptr = input.toLongLong(); - TreeModelItem* ai = reinterpret_cast(pptr); + qlonglong p = pptr["pptr"].toLongLong(); + TreeModelItem* ai = reinterpret_cast(p); if ( pm.isNull() || returnedData["url"].toString().startsWith( LASTFM_DEFAULT_COVER ) ) ai->cover = m_defaultCover; else ai->cover = pm; + + emit dataChanged( ai->index, ai->index.sibling( ai->index.row(), columnCount( QModelIndex() ) - 1 ) ); } } diff --git a/src/libtomahawk/playlist/treemodel.h b/src/libtomahawk/playlist/treemodel.h index 6062c37cb..68f23bd98 100644 --- a/src/libtomahawk/playlist/treemodel.h +++ b/src/libtomahawk/playlist/treemodel.h @@ -28,7 +28,7 @@ #include "database/databasecommand_allartists.h" #include "treemodelitem.h" -#include "tomahawk/infosystem.h" +#include "infosystem/infosystem.h" #include "dllmacro.h" @@ -39,6 +39,17 @@ class DLLEXPORT TreeModel : public QAbstractItemModel Q_OBJECT public: + enum Columns { + Name = 0, + Duration, + Bitrate, + Age, + Year, + Filesize, + Origin, + AlbumPosition + }; + explicit TreeModel( QObject* parent = 0 ); virtual ~TreeModel(); diff --git a/src/libtomahawk/playlist/treeproxymodel.cpp b/src/libtomahawk/playlist/treeproxymodel.cpp index 357596ed0..3f8b86b16 100644 --- a/src/libtomahawk/playlist/treeproxymodel.cpp +++ b/src/libtomahawk/playlist/treeproxymodel.cpp @@ -99,6 +99,12 @@ TreeProxyModel::lessThan( const QModelIndex& left, const QModelIndex& right ) co if ( !p2 ) return false; + if ( !p1->result().isNull() ) + { + if ( p1->result()->albumpos() != p2->result()->albumpos() ) + return p1->result()->albumpos() < p2->result()->albumpos(); + } + return QString::localeAwareCompare( textForItem( p1 ), textForItem( p2 ) ) < 0; } diff --git a/src/scanmanager.cpp b/src/scanmanager.cpp index 27f134e01..acca5dc90 100644 --- a/src/scanmanager.cpp +++ b/src/scanmanager.cpp @@ -1,5 +1,5 @@ /* === This file is part of Tomahawk Player - === - * + * * Copyright 2010-2011, Christian Muehlhaeuser * * Tomahawk is free software: you can redistribute it and/or modify @@ -26,7 +26,7 @@ #include "musicscanner.h" #include "tomahawksettings.h" -#include "tomahawkutils.h" +#include "utils/tomahawkutils.h" #include "database/database.h" #include "database/databasecommand_dirmtimes.h" @@ -60,15 +60,15 @@ ScanManager::ScanManager( QObject* parent ) m_deferredScanTimer->setSingleShot( false ); m_deferredScanTimer->setInterval( 1000 ); m_dirWatcher = new QFileSystemWatcher( this ); - + connect( TomahawkSettings::instance(), SIGNAL( changed() ), SLOT( onSettingsChanged() ) ); connect( m_queuedScanTimer, SIGNAL( timeout() ), SLOT( queuedScanTimeout() ) ); connect( m_deferredScanTimer, SIGNAL( timeout() ), SLOT( deferredScanTimeout() ) ); connect( m_dirWatcher, SIGNAL( directoryChanged( const QString & ) ), SLOT( handleChangedDir( const QString & ) ) ); - + if ( TomahawkSettings::instance()->hasScannerPaths() ) m_currScannerPaths = TomahawkSettings::instance()->scannerPaths(); - + qDebug() << "loading initial directories to watch"; QTimer::singleShot( 1000, this, SLOT( startupWatchPaths() ) ); m_deferredScanTimer->start(); @@ -78,11 +78,11 @@ ScanManager::ScanManager( QObject* parent ) ScanManager::~ScanManager() { qDebug() << Q_FUNC_INFO; - + if( m_musicScannerThreadController ) { m_musicScannerThreadController->quit(); - + while( !m_musicScannerThreadController->isFinished() ) { QCoreApplication::processEvents( QEventLoop::AllEvents, 200 ); @@ -94,7 +94,7 @@ ScanManager::~ScanManager() delete m_scanner; m_scanner = 0; } - + delete m_musicScannerThreadController; m_musicScannerThreadController = 0; } @@ -112,7 +112,7 @@ ScanManager::onSettingsChanged() m_dirWatcher->addPaths( m_currScannerPaths ); runManualScan( m_currScannerPaths ); } - + if( TomahawkSettings::instance()->watchForChanges() && !m_queuedChangedDirs.isEmpty() ) runManualScan( m_queuedChangedDirs, false ); @@ -123,13 +123,13 @@ void ScanManager::startupWatchPaths() { qDebug() << Q_FUNC_INFO; - + if( !Database::instance() || ( Database::instance() && !Database::instance()->isReady() ) ) { QTimer::singleShot( 1000, this, SLOT( startupWatchPaths() ) ); return; } - + DatabaseCommand_DirMtimes* cmd = new DatabaseCommand_DirMtimes( m_currScannerPaths ); connect( cmd, SIGNAL( done( QMap< QString, unsigned int > ) ), SLOT( setInitialPaths( QMap< QString, unsigned int > ) ) ); @@ -155,7 +155,7 @@ void ScanManager::runManualScan( const QStringList& paths, bool recursive ) { qDebug() << Q_FUNC_INFO; - + if ( !m_musicScannerThreadController && !m_scanner ) //still running if these are not zero { m_musicScannerThreadController = new QThread( this ); @@ -185,7 +185,7 @@ ScanManager::runManualScan( const QStringList& paths, bool recursive ) m_deferredDirs[recursive] << path; } } - } + } } @@ -244,7 +244,7 @@ ScanManager::deferredScanTimeout() runManualScan( m_deferredDirs[true], true ); } else if( !m_deferredDirs[false].isEmpty() ) - { + { qDebug() << "Running scan for deferred non-recursive paths"; runManualScan( m_deferredDirs[false], false ); } diff --git a/src/scrobbler.cpp b/src/scrobbler.cpp index 2968d5196..74ad40ae8 100644 --- a/src/scrobbler.cpp +++ b/src/scrobbler.cpp @@ -1,5 +1,5 @@ /* === This file is part of Tomahawk Player - === - * + * * Copyright 2010-2011, Christian Muehlhaeuser * * Tomahawk is free software: you can redistribute it and/or modify @@ -26,10 +26,9 @@ #include "typedefs.h" #include "audio/audioengine.h" #include "tomahawksettings.h" -#include "tomahawk/tomahawkapp.h" -#include "tomahawk/infosystem.h" +#include "infosystem/infosystem.h" -static QString s_infoIdentifier = QString("SCROBBLER"); +static QString s_scInfoIdentifier = QString( "SCROBBLER" ); Scrobbler::Scrobbler( QObject* parent ) : QObject( parent ) @@ -37,13 +36,11 @@ Scrobbler::Scrobbler( QObject* parent ) { connect( AudioEngine::instance(), SIGNAL( timerSeconds( unsigned int ) ), SLOT( engineTick( unsigned int ) ), Qt::QueuedConnection ); - - connect( TomahawkApp::instance()->infoSystem(), + + connect( Tomahawk::InfoSystem::InfoSystem::instance(), SIGNAL( info( QString, Tomahawk::InfoSystem::InfoType, QVariant, QVariant, Tomahawk::InfoSystem::InfoCustomData ) ), SLOT( infoSystemInfo( QString, Tomahawk::InfoSystem::InfoType, QVariant, QVariant, Tomahawk::InfoSystem::InfoCustomData ) ) ); - connect( TomahawkApp::instance()->infoSystem(), SIGNAL( finished( QString ) ), SLOT( infoSystemFinished( QString ) ) ); - connect( AudioEngine::instance(), SIGNAL( started( const Tomahawk::result_ptr& ) ), SLOT( trackStarted( const Tomahawk::result_ptr& ) ), Qt::QueuedConnection ); @@ -55,6 +52,8 @@ Scrobbler::Scrobbler( QObject* parent ) connect( AudioEngine::instance(), SIGNAL( stopped() ), SLOT( trackStopped() ), Qt::QueuedConnection ); + + connect( Tomahawk::InfoSystem::InfoSystem::instance(), SIGNAL( finished( QString ) ), SLOT( infoSystemFinished( QString ) ) ); } @@ -63,7 +62,7 @@ Scrobbler::~Scrobbler() } -void +void Scrobbler::trackStarted( const Tomahawk::result_ptr& track ) { Q_ASSERT( QThread::currentThread() == thread() ); @@ -76,34 +75,34 @@ Scrobbler::trackStarted( const Tomahawk::result_ptr& track ) } Tomahawk::InfoSystem::InfoCustomData trackInfo; - + trackInfo["title"] = QVariant::fromValue< QString >( track->track() ); trackInfo["artist"] = QVariant::fromValue< QString >( track->artist()->name() ); trackInfo["album"] = QVariant::fromValue< QString >( track->album()->name() ); trackInfo["duration"] = QVariant::fromValue< uint >( track->duration() ); - TomahawkApp::instance()->infoSystem()->getInfo( - s_infoIdentifier, Tomahawk::InfoSystem::InfoMiscSubmitNowPlaying, + Tomahawk::InfoSystem::InfoSystem::instance()->getInfo( + s_scInfoIdentifier, Tomahawk::InfoSystem::InfoMiscSubmitNowPlaying, QVariant::fromValue< Tomahawk::InfoSystem::InfoCustomData >( trackInfo ), Tomahawk::InfoSystem::InfoCustomData() ); - + m_scrobblePoint = ScrobblePoint( track->duration() / 2 ); } -void +void Scrobbler::trackPaused() { Q_ASSERT( QThread::currentThread() == thread() ); } -void +void Scrobbler::trackResumed() { Q_ASSERT( QThread::currentThread() == thread() ); } -void +void Scrobbler::trackStopped() { Q_ASSERT( QThread::currentThread() == thread() ); @@ -128,19 +127,20 @@ void Scrobbler::scrobble() { Q_ASSERT( QThread::currentThread() == thread() ); - - TomahawkApp::instance()->infoSystem()->getInfo( - s_infoIdentifier, Tomahawk::InfoSystem::InfoMiscSubmitScrobble, + + Tomahawk::InfoSystem::InfoSystem::instance()->getInfo( + s_scInfoIdentifier, Tomahawk::InfoSystem::InfoMiscSubmitScrobble, QVariant(), Tomahawk::InfoSystem::InfoCustomData() ); } + void Scrobbler::infoSystemInfo( QString caller, Tomahawk::InfoSystem::InfoType type, QVariant input, QVariant output, Tomahawk::InfoSystem::InfoCustomData customData ) { Q_UNUSED( input ); Q_UNUSED( output ); Q_UNUSED( customData ); - if ( caller == s_infoIdentifier ) + if ( caller == s_scInfoIdentifier ) { qDebug() << Q_FUNC_INFO; if ( type == Tomahawk::InfoSystem::InfoMiscSubmitNowPlaying ) @@ -150,10 +150,11 @@ Scrobbler::infoSystemInfo( QString caller, Tomahawk::InfoSystem::InfoType type, } } -void + +void Scrobbler::infoSystemFinished( QString target ) { - if ( target == s_infoIdentifier ) + if ( target == s_scInfoIdentifier ) { qDebug() << Q_FUNC_INFO; qDebug() << "Scrobbler received done signal from InfoSystem"; diff --git a/src/scrobbler.h b/src/scrobbler.h index 11741beec..f2c484aa9 100644 --- a/src/scrobbler.h +++ b/src/scrobbler.h @@ -1,5 +1,5 @@ /* === This file is part of Tomahawk Player - === - * + * * Copyright 2010-2011, Christian Muehlhaeuser * * Tomahawk is free software: you can redistribute it and/or modify @@ -23,7 +23,7 @@ #include "lastfm/ScrobblePoint" -#include "tomahawk/infosystem.h" +#include "infosystem/infosystem.h" #include @@ -37,20 +37,20 @@ class Scrobbler : public QObject public: Scrobbler( QObject* parent = 0 ); virtual ~Scrobbler(); - + public slots: void trackStarted( const Tomahawk::result_ptr& ); void trackPaused(); void trackResumed(); void trackStopped(); void engineTick( unsigned int secondsElapsed ); - + void infoSystemInfo( QString caller, Tomahawk::InfoSystem::InfoType type, QVariant input, QVariant output, Tomahawk::InfoSystem::InfoCustomData customData ); void infoSystemFinished( QString target ); - + private: void scrobble(); - + bool m_reachedScrobblePoint; ScrobblePoint m_scrobblePoint; }; diff --git a/src/tomahawkapp.cpp b/src/tomahawkapp.cpp index 365414b81..1c64fa8de 100644 --- a/src/tomahawkapp.cpp +++ b/src/tomahawkapp.cpp @@ -32,7 +32,7 @@ #include "artist.h" #include "album.h" #include "collection.h" -#include "tomahawk/infosystem.h" +#include "infosystem/infosystem.h" #include "database/database.h" #include "database/databasecollection.h" #include "database/databasecommand_collectionstats.h" @@ -152,7 +152,6 @@ TomahawkApp::TomahawkApp( int& argc, char *argv[] ) , m_shortcutHandler( 0 ) , m_scrubFriendlyName( false ) , m_mainwindow( 0 ) - , m_infoSystem( 0 ) { qDebug() << "TomahawkApp thread:" << this->thread(); setOrganizationName( QLatin1String( ORGANIZATION_NAME ) ); @@ -176,7 +175,6 @@ TomahawkApp::init() #endif registerMetaTypes(); - setupLogfile(); Echonest::Config::instance()->setAPIKey( "JRIHWEP6GPOER2QQ6" ); @@ -217,7 +215,7 @@ TomahawkApp::init() } qDebug() << "Init InfoSystem."; - m_infoSystem = new Tomahawk::InfoSystem::InfoSystem( this ); + new Tomahawk::InfoSystem::InfoSystem( this ); #ifdef LIBLASTFM_FOUND qDebug() << "Init Scrobbler."; @@ -304,7 +302,7 @@ TomahawkApp::~TomahawkApp() delete m_mainwindow; delete m_audioEngine; #endif - delete m_infoSystem; +// delete m_infoSystem; delete m_database; } diff --git a/src/web/api_v1.h b/src/web/api_v1.h index f7476990e..cc065a41a 100644 --- a/src/web/api_v1.h +++ b/src/web/api_v1.h @@ -1,5 +1,5 @@ /* === This file is part of Tomahawk Player - === - * + * * Copyright 2010-2011, Christian Muehlhaeuser * * Tomahawk is free software: you can redistribute it and/or modify @@ -38,8 +38,7 @@ #include #include "network/servent.h" -#include "tomahawkutils.h" -#include "tomahawk/tomahawkapp.h" +#include "utils/tomahawkutils.h" #include #include #include diff --git a/src/xmppbot/xmppbot.cpp b/src/xmppbot/xmppbot.cpp index 464e09ede..404d64fd2 100644 --- a/src/xmppbot/xmppbot.cpp +++ b/src/xmppbot/xmppbot.cpp @@ -1,5 +1,5 @@ /* === This file is part of Tomahawk Player - === - * + * * Copyright 2010-2011, Christian Muehlhaeuser * * Tomahawk is free software: you can redistribute it and/or modify @@ -18,8 +18,7 @@ #include "xmppbot.h" -#include "tomahawk/tomahawkapp.h" -#include "tomahawk/infosystem.h" +#include "infosystem/infosystem.h" #include "album.h" #include "typedefs.h" #include "tomahawksettings.h" @@ -36,7 +35,7 @@ using namespace gloox; using namespace Tomahawk::InfoSystem; -static QString s_infoIdentifier = QString("XMPPBot"); +static QString s_botInfoIdentifier = QString( "XMPPBot" ); XMPPBot::XMPPBot(QObject *parent) : QObject(parent) @@ -50,7 +49,7 @@ XMPPBot::XMPPBot(QObject *parent) int port = settings->xmppBotPort(); if (jidstring.isEmpty() || password.isEmpty()) return; - + JID jid(jidstring.toStdString()); jid.setResource( QString( "tomahawkbot%1" ).arg( qrand() ).toStdString() ); @@ -66,11 +65,11 @@ XMPPBot::XMPPBot(QObject *parent) connect(AudioEngine::instance(), SIGNAL(started(const Tomahawk::result_ptr &)), SLOT(newTrackSlot(const Tomahawk::result_ptr &))); - connect(TomahawkApp::instance()->infoSystem(), + connect(InfoSystem::instance(), SIGNAL(info(QString, Tomahawk::InfoSystem::InfoType, QVariant, QVariant, Tomahawk::InfoSystem::InfoCustomData)), SLOT(infoReturnedSlot(QString, Tomahawk::InfoSystem::InfoType, QVariant, QVariant, Tomahawk::InfoSystem::InfoCustomData))); - - connect(TomahawkApp::instance()->infoSystem(), SIGNAL(finished(QString)), SLOT(infoFinishedSlot(QString))); + + connect(InfoSystem::instance(), SIGNAL(finished(QString)), SLOT(infoFinishedSlot(QString))); bool success = m_client.data()->gloox::Client::connect(false); if (success) @@ -149,7 +148,7 @@ void XMPPBot::handleMessage(const Message& msg, MessageSession* session) //TODO: implement "properly" with MessageSessions, if the bot is to be multi-user if (msg.subtype() != Message::Chat || msg.from().full().empty() || msg.to().full().empty()) return; - + QString body = QString::fromStdString( msg.body() ).toLower().trimmed(); QString originatingJid = QString::fromStdString( msg.from().full() ); @@ -192,7 +191,7 @@ void XMPPBot::handleMessage(const Message& msg, MessageSession* session) qDebug() << "jid from:" << QString::fromStdString(msg.from().full()) << ", jid to:" << QString::fromStdString(msg.to().full()); qDebug() << "Operating on tokens:" << tokens; - + if (m_currTrack.isNull() || m_currTrack->artist()->name().isEmpty() || m_currTrack->track().isEmpty()) { qDebug() << "XMPPBot can't figure out track"; @@ -201,8 +200,8 @@ void XMPPBot::handleMessage(const Message& msg, MessageSession* session) m_client.data()->send(retMsg); return; } - - InfoMap infoMap; + + InfoMap infoMap; Q_FOREACH(QString token, tokens) { if (token == "biography") @@ -221,7 +220,7 @@ void XMPPBot::handleMessage(const Message& msg, MessageSession* session) infoMap[InfoTrackLyrics] = QVariant::fromValue(myhash); } } - + if (infoMap.isEmpty()) { qDebug() << "XMPPBot can't figure out track"; @@ -229,38 +228,38 @@ void XMPPBot::handleMessage(const Message& msg, MessageSession* session) Message retMsg(Message::Chat, JID(originatingJid.toStdString()), m_currReturnMessage.toStdString()); m_client.data()->send(retMsg); return; - } - + } + m_currInfoMap.unite(infoMap); QString waitMsg("Please wait..."); Message retMsg(Message::Chat, JID(originatingJid.toStdString()), waitMsg.toStdString()); m_client.data()->send(retMsg); Tomahawk::InfoSystem::InfoCustomData hash; hash["XMPPBotSendToJID"] = originatingJid; - TomahawkApp::instance()->infoSystem()->getInfo(s_infoIdentifier, infoMap, hash); + InfoSystem::instance()->getInfo(s_botInfoIdentifier, infoMap, hash); } void XMPPBot::infoReturnedSlot(QString caller, Tomahawk::InfoSystem::InfoType type, QVariant input, QVariant output, Tomahawk::InfoSystem::InfoCustomData customData) { qDebug() << Q_FUNC_INFO; - - if (caller != s_infoIdentifier || + + if (caller != s_botInfoIdentifier || input.isNull() || !input.isValid() || !customData.contains("XMPPBotSendToJID") ) { qDebug() << "Not the right object, custom data is null, or don't have a set JID"; return; - } - + } + if (!m_currInfoMap.contains(type)) { qDebug() << "not in currInfoMap"; return; } - else + else m_currInfoMap.remove(type); - + QString jid = customData["XMPPBotSendToJID"].toString(); if (!m_currReturnJid.isEmpty() && m_currReturnJid != jid && !m_currReturnMessage.isEmpty()) { @@ -269,7 +268,7 @@ void XMPPBot::infoReturnedSlot(QString caller, Tomahawk::InfoSystem::InfoType ty m_currReturnMessage = QString("\n"); } m_currReturnJid = jid; - + switch(type) { case InfoArtistBiography: @@ -287,7 +286,7 @@ void XMPPBot::infoReturnedSlot(QString caller, Tomahawk::InfoSystem::InfoType ty m_currReturnMessage += QString("\nBiographies for %1\n").arg(artist); Q_FOREACH(QString source, bmap.keys()) { - m_currReturnMessage += (bmap[source]["attribution"].isEmpty() ? + m_currReturnMessage += (bmap[source]["attribution"].isEmpty() ? QString("From %1:\n").arg(bmap[source]["site"]) : QString("From %1 at %2:\n").arg(bmap[source]["attribution"]).arg(bmap[source]["site"])); m_currReturnMessage += bmap[source]["text"] + QString("\n"); @@ -381,15 +380,15 @@ void XMPPBot::infoReturnedSlot(QString caller, Tomahawk::InfoSystem::InfoType ty default: break; } - + if (m_currReturnMessage.isEmpty()) { qDebug() << "Empty message, not sending anything back"; return; } - + qDebug() << "Going to send message: " << m_currReturnMessage << " to " << jid; - + //gloox::Message msg(Message::Chat, JID(jid.toStdString()), m_currReturnMessage.toStdString()); //m_client.data()->send(msg); } @@ -398,10 +397,10 @@ void XMPPBot::infoFinishedSlot(QString caller) { qDebug() << Q_FUNC_INFO; qDebug() << "current return message is" << m_currReturnMessage; - qDebug() << "id is" << caller << "and our id is" << s_infoIdentifier; - if (m_currReturnMessage.isEmpty() || caller != s_infoIdentifier) + qDebug() << "id is" << caller << "and our id is" << s_botInfoIdentifier; + if (m_currReturnMessage.isEmpty() || caller != s_botInfoIdentifier) return; - + qDebug() << "Sending message to JID" << m_currReturnJid; gloox::Message msg(Message::Chat, JID(m_currReturnJid.toStdString()), m_currReturnMessage.toStdString()); m_client.data()->send(msg); diff --git a/src/xmppbot/xmppbot.h b/src/xmppbot/xmppbot.h index ee5a2b38a..e22a564ae 100644 --- a/src/xmppbot/xmppbot.h +++ b/src/xmppbot/xmppbot.h @@ -1,5 +1,5 @@ /* === This file is part of Tomahawk Player - === - * + * * Copyright 2010-2011, Christian Muehlhaeuser * * Tomahawk is free software: you can redistribute it and/or modify @@ -20,7 +20,7 @@ #define XMPPBOT_H #include -#include +#include #include #include @@ -42,12 +42,12 @@ class XMPPBotClient public: XMPPBotClient(QObject* parent, gloox::JID &jid, std::string password, int port); virtual ~XMPPBotClient(); - + void run(); - + private slots: void recvSlot(); - + private: QTimer m_timer; }; @@ -59,7 +59,7 @@ class XMPPBot , public gloox::MessageHandler { Q_OBJECT - + public: XMPPBot(QObject *parent); virtual ~XMPPBot(); @@ -68,16 +68,16 @@ public slots: virtual void newTrackSlot(const Tomahawk::result_ptr &track); virtual void infoReturnedSlot(QString caller, Tomahawk::InfoSystem::InfoType type, QVariant input, QVariant output, Tomahawk::InfoSystem::InfoCustomData customData); virtual void infoFinishedSlot(QString caller); - + protected: // ConnectionListener virtual void onConnect(); virtual void onDisconnect(gloox::ConnectionError e); virtual bool onTLSConnect(const gloox::CertInfo &info); - + // SubscriptionHandler virtual void handleSubscription(const gloox::Subscription &subscription); - + // MessageHandler virtual void handleMessage(const gloox::Message &msg, gloox::MessageSession *session = 0); From e3c90784e1077bcb78ebcafde01a403089241463 Mon Sep 17 00:00:00 2001 From: Christian Muehlhaeuser Date: Thu, 14 Apr 2011 04:40:04 +0200 Subject: [PATCH 312/329] * Properly shutdown InfoSystem again. * Default sizes & restoring of TreeHeader columns. * Fix square cover painting in TreeItemDelegate. --- include/tomahawk/tomahawkapp.h | 5 +++++ src/libtomahawk/infosystem/infosystem.h | 6 ++++-- src/libtomahawk/playlist/albummodel.cpp | 1 - src/libtomahawk/playlist/artistview.cpp | 8 ++++++++ src/libtomahawk/playlist/artistview.h | 4 ++++ src/libtomahawk/playlist/treeheader.cpp | 8 ++++---- src/libtomahawk/playlist/treeitemdelegate.cpp | 2 +- src/libtomahawk/playlist/treemodel.cpp | 2 +- src/libtomahawk/source.cpp | 12 +++++++++++- src/libtomahawk/source.h | 8 ++++---- src/tomahawkapp.cpp | 4 ++-- 11 files changed, 44 insertions(+), 16 deletions(-) diff --git a/include/tomahawk/tomahawkapp.h b/include/tomahawk/tomahawkapp.h index 5e7a7db7f..b03e17ab6 100644 --- a/include/tomahawk/tomahawkapp.h +++ b/include/tomahawk/tomahawkapp.h @@ -52,6 +52,10 @@ class XMPPBot; namespace Tomahawk { class ShortcutHandler; + namespace InfoSystem + { + class InfoSystem; + } } #ifdef LIBLASTFM_FOUND @@ -119,6 +123,7 @@ private: AudioEngine* m_audioEngine; SipHandler* m_sipHandler; Servent* m_servent; + Tomahawk::InfoSystem::InfoSystem* m_infoSystem; XMPPBot* m_xmppBot; Tomahawk::ShortcutHandler* m_shortcutHandler; bool m_scrubFriendlyName; diff --git a/src/libtomahawk/infosystem/infosystem.h b/src/libtomahawk/infosystem/infosystem.h index 7f883e20f..75e674f49 100644 --- a/src/libtomahawk/infosystem/infosystem.h +++ b/src/libtomahawk/infosystem/infosystem.h @@ -29,6 +29,8 @@ #include #include +#include "dllmacro.h" + namespace Tomahawk { namespace InfoSystem { @@ -97,7 +99,7 @@ typedef QMap< QString, QMap< QString, QString > > InfoGenericMap; typedef QHash< QString, QVariant > InfoCustomData; typedef QHash< QString, QString > InfoCacheCriteria; -class InfoPlugin : public QObject +class DLLEXPORT InfoPlugin : public QObject { Q_OBJECT @@ -134,7 +136,7 @@ protected: typedef QWeakPointer< InfoPlugin > InfoPluginPtr; -class InfoSystem : public QObject +class DLLEXPORT InfoSystem : public QObject { Q_OBJECT diff --git a/src/libtomahawk/playlist/albummodel.cpp b/src/libtomahawk/playlist/albummodel.cpp index bf4ea4c05..7e28d655d 100644 --- a/src/libtomahawk/playlist/albummodel.cpp +++ b/src/libtomahawk/playlist/albummodel.cpp @@ -274,7 +274,6 @@ AlbumModel::addFilteredCollection( const collection_ptr& collection, unsigned in void AlbumModel::onAlbumsAdded( const QList& albums ) { - Q_UNUSED( collection ); if ( !albums.count() ) return; diff --git a/src/libtomahawk/playlist/artistview.cpp b/src/libtomahawk/playlist/artistview.cpp index 88f83dc08..2c8412057 100644 --- a/src/libtomahawk/playlist/artistview.cpp +++ b/src/libtomahawk/playlist/artistview.cpp @@ -149,6 +149,14 @@ ArtistView::paintEvent( QPaintEvent* event ) } +void +ArtistView::resizeEvent( QResizeEvent* event ) +{ + QTreeView::resizeEvent( event ); + m_header->checkState(); +} + + void ArtistView::onFilterChanged( const QString& ) { diff --git a/src/libtomahawk/playlist/artistview.h b/src/libtomahawk/playlist/artistview.h index d272df314..6f9b94b1f 100644 --- a/src/libtomahawk/playlist/artistview.h +++ b/src/libtomahawk/playlist/artistview.h @@ -52,10 +52,13 @@ public: virtual QString title() const { return m_model->title(); } virtual QString description() const { return m_model->description(); } + virtual bool showStatsBar() const { return false; } virtual bool showModes() const { return true; } virtual bool jumpToCurrentTrack() { return false; } + QString guid() const { return QString( "ArtistView" ); } + public slots: void onItemActivated( const QModelIndex& index ); @@ -64,6 +67,7 @@ protected: virtual void dragEnterEvent( QDragEnterEvent* event ); virtual void dragMoveEvent( QDragMoveEvent* event ); virtual void dropEvent( QDropEvent* event ); + virtual void resizeEvent( QResizeEvent* event ); void paintEvent( QPaintEvent* event ); diff --git a/src/libtomahawk/playlist/treeheader.cpp b/src/libtomahawk/playlist/treeheader.cpp index 12ca97c04..72bc75192 100644 --- a/src/libtomahawk/playlist/treeheader.cpp +++ b/src/libtomahawk/playlist/treeheader.cpp @@ -60,7 +60,7 @@ TreeHeader::onSectionResized() if ( !m_init ) return; -// TomahawkSettings::instance()->setPlaylistColumnSizes( m_parent->guid(), saveState() ); + TomahawkSettings::instance()->setPlaylistColumnSizes( m_parent->guid(), saveState() ); } @@ -77,13 +77,13 @@ TreeHeader::checkState() if ( !count() || m_init ) return; -/* QByteArray state = TomahawkSettings::instance()->playlistColumnSizes( m_parent->guid() ); + QByteArray state = TomahawkSettings::instance()->playlistColumnSizes( m_parent->guid() ); if ( !state.isEmpty() ) restoreState( state ); else { QList< double > m_columnWeights; - m_columnWeights << 0.21 << 0.22 << 0.20 << 0.05 << 0.05 << 0.05 << 0.05 << 0.05; // << 0.12; + m_columnWeights << 0.50 << 0.07 << 0.07 << 0.07 << 0.07 << 0.07; // << 0.12; for ( int i = 0; i < count() - 1; i++ ) { @@ -96,7 +96,7 @@ TreeHeader::checkState() } } - m_init = true;*/ + m_init = true; } diff --git a/src/libtomahawk/playlist/treeitemdelegate.cpp b/src/libtomahawk/playlist/treeitemdelegate.cpp index 51704d7e8..fa080c980 100644 --- a/src/libtomahawk/playlist/treeitemdelegate.cpp +++ b/src/libtomahawk/playlist/treeitemdelegate.cpp @@ -85,7 +85,7 @@ TreeItemDelegate::paint( QPainter* painter, const QStyleOptionViewItem& option, painter->setRenderHint( QPainter::Antialiasing ); painter->setPen( opt.palette.color( QPalette::Text ) ); - QRect r = option.rect.adjusted( 4, 4, -option.rect.width() + option.rect.height() - 8, -4 ); + QRect r = option.rect.adjusted( 4, 4, -option.rect.width() + option.rect.height() - 4, -4 ); // painter->drawPixmap( option.rect.adjusted( 4, 4, -4, -38 ), QPixmap( RESPATH "images/cover-shadow.png" ) ); painter->drawPixmap( r, item->cover ); diff --git a/src/libtomahawk/playlist/treemodel.cpp b/src/libtomahawk/playlist/treemodel.cpp index d95f10240..ea8feb1c7 100644 --- a/src/libtomahawk/playlist/treemodel.cpp +++ b/src/libtomahawk/playlist/treemodel.cpp @@ -396,7 +396,7 @@ TreeModel::addCollection( const collection_ptr& collection ) Database::instance()->enqueue( QSharedPointer( cmd ) ); - m_title = tr( "All artists from %1" ).arg( collection->source()->friendlyName() ); + m_title = tr( "All Artists from %1" ).arg( collection->source()->friendlyName() ); } diff --git a/src/libtomahawk/source.cpp b/src/libtomahawk/source.cpp index 29715c966..d825a7724 100644 --- a/src/libtomahawk/source.cpp +++ b/src/libtomahawk/source.cpp @@ -1,5 +1,5 @@ /* === This file is part of Tomahawk Player - === - * + * * Copyright 2010-2011, Christian Muehlhaeuser * * Tomahawk is free software: you can redistribute it and/or modify @@ -222,6 +222,16 @@ Source::onStateChanged( DBSyncConnection::State newstate, DBSyncConnection::Stat } +unsigned int +Source::trackCount() const +{ + if ( m_stats.contains( "numfiles" ) ) + return m_stats.value( "numfiles" ).toUInt(); + else + return 0; +} + + void Source::onPlaybackStarted( const Tomahawk::query_ptr& query ) { diff --git a/src/libtomahawk/source.h b/src/libtomahawk/source.h index 8ab4de2e5..ed48dfd1b 100644 --- a/src/libtomahawk/source.h +++ b/src/libtomahawk/source.h @@ -1,5 +1,5 @@ /* === This file is part of Tomahawk Player - === - * + * * Copyright 2010-2011, Christian Muehlhaeuser * * Tomahawk is free software: you can redistribute it and/or modify @@ -65,11 +65,11 @@ public: void scanningProgress( unsigned int files ); void scanningFinished( unsigned int files ); - + void setOffline(); void setOnline(); - unsigned int trackCount() const { return m_stats.value( "numfiles" ).toUInt(); } + unsigned int trackCount() const; Tomahawk::query_ptr currentTrack() const { return m_currentTrack; } QString textStatus() const { return m_textStatus; } @@ -102,7 +102,7 @@ private slots: void onStateChanged( DBSyncConnection::State newstate, DBSyncConnection::State oldstate, const QString& info ); void onPlaybackStarted( const Tomahawk::query_ptr& query ); void onPlaybackFinished( const Tomahawk::query_ptr& query ); - + private: bool m_isLocal; bool m_online; diff --git a/src/tomahawkapp.cpp b/src/tomahawkapp.cpp index 1c64fa8de..541068403 100644 --- a/src/tomahawkapp.cpp +++ b/src/tomahawkapp.cpp @@ -215,7 +215,7 @@ TomahawkApp::init() } qDebug() << "Init InfoSystem."; - new Tomahawk::InfoSystem::InfoSystem( this ); + m_infoSystem = new Tomahawk::InfoSystem::InfoSystem( this ); #ifdef LIBLASTFM_FOUND qDebug() << "Init Scrobbler."; @@ -302,7 +302,7 @@ TomahawkApp::~TomahawkApp() delete m_mainwindow; delete m_audioEngine; #endif -// delete m_infoSystem; + delete m_infoSystem; delete m_database; } From f5219525b4c60784f3bb24730279101d745fd4cf Mon Sep 17 00:00:00 2001 From: Christian Muehlhaeuser Date: Thu, 14 Apr 2011 05:00:27 +0200 Subject: [PATCH 313/329] * Added alphaBlend method to TomahawkUtils. DRY. --- .../playlist/playlistitemdelegate.cpp | 18 +++++------------ src/libtomahawk/playlist/treeitemdelegate.cpp | 16 +++++++++++++-- src/libtomahawk/utils/tomahawkutils.cpp | 20 ++++++++++++++++--- src/libtomahawk/utils/tomahawkutils.h | 8 +++++--- 4 files changed, 41 insertions(+), 21 deletions(-) diff --git a/src/libtomahawk/playlist/playlistitemdelegate.cpp b/src/libtomahawk/playlist/playlistitemdelegate.cpp index 174001fc7..e06bdb9c4 100644 --- a/src/libtomahawk/playlist/playlistitemdelegate.cpp +++ b/src/libtomahawk/playlist/playlistitemdelegate.cpp @@ -76,24 +76,16 @@ PlaylistItemDelegate::paint( QPainter* painter, const QStyleOptionViewItem& opti return; float opacity = 0.0; - painter->save(); if ( item->query()->results().count() ) opacity = item->query()->results().first()->score(); - QColor textcol, bgcol; - textcol = option.palette.color( QPalette::Foreground ); - bgcol = option.palette.color( QPalette::Background ); - opacity = qMax( (float)0.3, opacity ); - int r = textcol.red(), g = textcol.green(), b = textcol.blue(); - r = opacity * r + ( 1 - opacity ) * bgcol.red(); - g = opacity * g + ( 1 - opacity ) * bgcol.green(); - b = opacity * b + ( 1 - opacity ) * bgcol.blue(); - textcol = QColor( r, g, b ); + QColor textColor = TomahawkUtils::alphaBlend( option.palette.color( QPalette::Foreground ), option.palette.color( QPalette::Background ), opacity ); if ( item->isPlaying() ) { // painter->setRenderHint( QPainter::Antialiasing ); + painter->save(); { QRect r = option.rect.adjusted( 3, 0, 0, 0 ); @@ -120,18 +112,18 @@ PlaylistItemDelegate::paint( QPainter* painter, const QStyleOptionViewItem& opti painter->setPen( pen ); painter->drawRoundedRect( r, 3.0, 3.0 ); } + + painter->restore(); } else { if ( const QStyleOptionViewItem *vioption = qstyleoption_cast(&option)) { QStyleOptionViewItemV4 o( *vioption ); - o.palette.setColor( QPalette::Text, textcol ); + o.palette.setColor( QPalette::Text, textColor ); QStyledItemDelegate::paint( painter, o, index ); } else QStyledItemDelegate::paint( painter, option, index ); } - - painter->restore(); } diff --git a/src/libtomahawk/playlist/treeitemdelegate.cpp b/src/libtomahawk/playlist/treeitemdelegate.cpp index fa080c980..fcb453f73 100644 --- a/src/libtomahawk/playlist/treeitemdelegate.cpp +++ b/src/libtomahawk/playlist/treeitemdelegate.cpp @@ -66,7 +66,19 @@ TreeItemDelegate::paint( QPainter* painter, const QStyleOptionViewItem& option, } else if ( !item->result().isNull() ) { - return QStyledItemDelegate::paint( painter, option, index ); + float opacity = item->result()->score(); + + opacity = qMax( (float)0.3, opacity ); + QColor textColor = TomahawkUtils::alphaBlend( option.palette.color( QPalette::Foreground ), option.palette.color( QPalette::Background ), opacity ); + + if ( const QStyleOptionViewItem *vioption = qstyleoption_cast(&option)) + { + QStyleOptionViewItemV4 o( *vioption ); + o.palette.setColor( QPalette::Text, textColor ); + return QStyledItemDelegate::paint( painter, o, index ); + } + else + return QStyledItemDelegate::paint( painter, option, index ); } QStyleOptionViewItemV4 opt = option; @@ -99,7 +111,7 @@ TreeItemDelegate::paint( QPainter* painter, const QStyleOptionViewItem& option, text = painter->fontMetrics().elidedText( text, Qt::ElideRight, r.width() ); painter->drawText( r, text, to ); - painter->setFont( boldFont ); +// painter->setFont( boldFont ); painter->restore(); } diff --git a/src/libtomahawk/utils/tomahawkutils.cpp b/src/libtomahawk/utils/tomahawkutils.cpp index a551e39c6..e652f5e43 100644 --- a/src/libtomahawk/utils/tomahawkutils.cpp +++ b/src/libtomahawk/utils/tomahawkutils.cpp @@ -1,5 +1,5 @@ /* === This file is part of Tomahawk Player - === - * + * * Copyright 2010-2011, Christian Muehlhaeuser * * Tomahawk is free software: you can redistribute it and/or modify @@ -19,6 +19,7 @@ #include "tomahawkutils.h" #include +#include #include #include #include @@ -265,6 +266,19 @@ extensionToMimetype( const QString& extension ) } +QColor +alphaBlend( const QColor& colorFrom, const QColor& colorTo, float opacity ) +{ + opacity = qMax( (float)0.3, opacity ); + int r = colorFrom.red(), g = colorFrom.green(), b = colorFrom.blue(); + r = opacity * r + ( 1 - opacity ) * colorTo.red(); + g = opacity * g + ( 1 - opacity ) * colorTo.green(); + b = opacity * b + ( 1 - opacity ) * colorTo.blue(); + + return QColor( r, g, b ); +} + + QPixmap createDragPixmap( int itemCount ) { @@ -366,7 +380,7 @@ dnsResolver() { if( !s_dnsResolver ) s_dnsResolver = new DNSResolver(); - + return s_dnsResolver; } @@ -387,7 +401,7 @@ DNSResolver::resolve( QString &host, QString type ) { // For the moment, assume we are looking for XMPP... QString fullHost( "_xmpp-client._tcp." + host ); - + qDebug() << "Looking up SRV record for " << fullHost.toUtf8(); m_dnsSharedRequest->query( fullHost.toUtf8(), QJDns::Srv ); diff --git a/src/libtomahawk/utils/tomahawkutils.h b/src/libtomahawk/utils/tomahawkutils.h index 103e379ce..8490829f4 100644 --- a/src/libtomahawk/utils/tomahawkutils.h +++ b/src/libtomahawk/utils/tomahawkutils.h @@ -1,5 +1,5 @@ /* === This file is part of Tomahawk Player - === - * + * * Copyright 2010-2011, Christian Muehlhaeuser * * Tomahawk is free software: you can redistribute it and/or modify @@ -25,6 +25,7 @@ #define RESPATH ":/data/" +class QColor; class QDir; class QDateTime; class QString; @@ -70,11 +71,11 @@ namespace TomahawkUtils { QThread::sleep( secs ); } - static void msleep( unsigned long msecs ) + static void msleep( unsigned long msecs ) { QThread::msleep( msecs ); } - static void usleep( unsigned long usecs ) + static void usleep( unsigned long usecs ) { QThread::usleep( usecs ); } @@ -89,6 +90,7 @@ namespace TomahawkUtils DLLEXPORT QString filesizeToString( unsigned int size ); DLLEXPORT QString extensionToMimetype( const QString& extension ); + DLLEXPORT QColor alphaBlend( const QColor& colorFrom, const QColor& colorTo, float opacity ); DLLEXPORT QPixmap createDragPixmap( int itemCount = 1 ); DLLEXPORT QNetworkAccessManager* nam(); From 3e05151008a4005debce0158e31d7c9c3a6e173b Mon Sep 17 00:00:00 2001 From: Christian Muehlhaeuser Date: Thu, 14 Apr 2011 07:26:27 +0200 Subject: [PATCH 314/329] * Support showing tracks without an album tag in the TreeView. --- .../database/databasecommand_allalbums.cpp | 16 +++++++++---- .../database/databasecommand_alltracks.cpp | 6 +++++ src/libtomahawk/playlist/treeitemdelegate.cpp | 8 ++++--- src/libtomahawk/playlist/treemodel.cpp | 23 +++++++++++-------- 4 files changed, 36 insertions(+), 17 deletions(-) diff --git a/src/libtomahawk/database/databasecommand_allalbums.cpp b/src/libtomahawk/database/databasecommand_allalbums.cpp index 15aca3ed3..e0ab9b97e 100644 --- a/src/libtomahawk/database/databasecommand_allalbums.cpp +++ b/src/libtomahawk/database/databasecommand_allalbums.cpp @@ -44,10 +44,11 @@ DatabaseCommand_AllAlbums::execForArtist( DatabaseImpl* dbi ) QString sql = QString( "SELECT DISTINCT album.id, album.name " - "FROM album, file, file_join " + "FROM file, file_join " + "LEFT OUTER JOIN album " + "ON file_join.album = album.id " "WHERE file.id = file_join.file " - "AND file_join.album = album.id " - "AND album.artist = %1 " + "AND file_join.artist = %1 " "%2 " "%3 %4 %5" ).arg( m_artist->id() ) @@ -61,7 +62,14 @@ DatabaseCommand_AllAlbums::execForArtist( DatabaseImpl* dbi ) while( query.next() ) { - Tomahawk::album_ptr album = Tomahawk::Album::get( query.value( 0 ).toUInt(), query.value( 1 ).toString(), m_artist ); + unsigned int albumId = query.value( 0 ).toUInt(); + QString albumName; + if ( query.value( 0 ).isNull() ) + { + albumName = tr( "Unknown" ); + } + + Tomahawk::album_ptr album = Tomahawk::Album::get( albumId, albumName, m_artist ); al << album; } diff --git a/src/libtomahawk/database/databasecommand_alltracks.cpp b/src/libtomahawk/database/databasecommand_alltracks.cpp index 57ebf3afa..a6bcb7151 100644 --- a/src/libtomahawk/database/databasecommand_alltracks.cpp +++ b/src/libtomahawk/database/databasecommand_alltracks.cpp @@ -54,6 +54,12 @@ DatabaseCommand_AllTracks::exec( DatabaseImpl* dbi ) if ( !m_collection.isNull() ) sourceToken = QString( "AND file.source %1" ).arg( m_collection->source()->isLocal() ? "IS NULL" : QString( "= %1" ).arg( m_collection->source()->id() ) ); + if ( m_album && m_album->id() == 0 ) + { + m_artist = m_album->artist().data(); + m_album = 0; + } + QString sql = QString( "SELECT file.id, artist.name, album.name, track.name, file.size, " "file.duration, file.bitrate, file.url, file.source, file.mtime, file.mimetype, file_join.albumpos, artist.id, album.id, track.id " diff --git a/src/libtomahawk/playlist/treeitemdelegate.cpp b/src/libtomahawk/playlist/treeitemdelegate.cpp index fcb453f73..50ac99979 100644 --- a/src/libtomahawk/playlist/treeitemdelegate.cpp +++ b/src/libtomahawk/playlist/treeitemdelegate.cpp @@ -67,7 +67,6 @@ TreeItemDelegate::paint( QPainter* painter, const QStyleOptionViewItem& option, else if ( !item->result().isNull() ) { float opacity = item->result()->score(); - opacity = qMax( (float)0.3, opacity ); QColor textColor = TomahawkUtils::alphaBlend( option.palette.color( QPalette::Foreground ), option.palette.color( QPalette::Background ), opacity ); @@ -77,9 +76,12 @@ TreeItemDelegate::paint( QPainter* painter, const QStyleOptionViewItem& option, o.palette.setColor( QPalette::Text, textColor ); return QStyledItemDelegate::paint( painter, o, index ); } - else - return QStyledItemDelegate::paint( painter, option, index ); } + else + return; + + if ( text.trimmed().isEmpty() ) + text = tr( "Unknown" ); QStyleOptionViewItemV4 opt = option; initStyleOption( &opt, QModelIndex() ); diff --git a/src/libtomahawk/playlist/treemodel.cpp b/src/libtomahawk/playlist/treemodel.cpp index ea8feb1c7..247735999 100644 --- a/src/libtomahawk/playlist/treemodel.cpp +++ b/src/libtomahawk/playlist/treemodel.cpp @@ -96,8 +96,6 @@ TreeModel::canFetchMore( const QModelIndex& parent ) const void TreeModel::fetchMore( const QModelIndex& parent ) { - qDebug() << Q_FUNC_INFO; - TreeModelItem* parentItem = itemFromIndex( parent ); if ( !parentItem || parentItem->fetchingMore ) return; @@ -105,16 +103,16 @@ TreeModel::fetchMore( const QModelIndex& parent ) parentItem->fetchingMore = true; if ( !parentItem->artist().isNull() ) { - qDebug() << "Artist" << parentItem->artist()->name(); + qDebug() << Q_FUNC_INFO << "Loading Artist:" << parentItem->artist()->name(); addAlbums( parentItem->artist(), parent ); } else if ( !parentItem->album().isNull() ) { - qDebug() << "Artist" << parentItem->album()->name(); + qDebug() << Q_FUNC_INFO << "Loading Album:" << parentItem->album()->name(); addTracks( parentItem->album(), parent ); } else - qDebug() << "Something else"; + Q_ASSERT( false ); } @@ -183,8 +181,12 @@ TreeModel::data( const QModelIndex& index, int role ) const { return QSize( 128, 32 ); } + else if ( !entry->artist().isNull() ) + { + return QSize( 128, 44 ); + } - return QSize( 128, 44 ); + return QSize( 128, 0 ); } if ( role != Qt::DisplayRole ) // && role != Qt::ToolTipRole ) @@ -371,7 +373,6 @@ TreeModel::addTracks( const album_ptr& album, const QModelIndex& parent ) QList< QVariant > rows; rows << parent.row(); rows << parent.parent().row(); - cmd->setData( QVariant( rows ) ); connect( cmd, SIGNAL( tracks( QList, QVariant ) ), @@ -438,7 +439,6 @@ TreeModel::onArtistsAdded( const QList& artists ) TreeModelItem* artistitem; foreach( const artist_ptr& artist, artists ) { - qDebug() << artist->name(); artistitem = new TreeModelItem( artist, m_rootItem ); artistitem->cover = m_defaultCover; artistitem->index = createIndex( m_rootItem->children.count() - 1, 0, artistitem ); @@ -470,7 +470,7 @@ TreeModel::onAlbumsAdded( const QList& albums, const QVaria if ( crows.second > 0 ) emit beginInsertRows( parent, crows.first + 1, crows.second ); - TreeModelItem* albumitem; + TreeModelItem* albumitem = 0; foreach( const album_ptr& album, albums ) { albumitem = new TreeModelItem( album, parentItem ); @@ -518,7 +518,7 @@ TreeModel::onTracksAdded( const QList& tracks, const QVaria if ( crows.second > 0 ) emit beginInsertRows( parent, crows.first + 1, crows.second ); - TreeModelItem* item; + TreeModelItem* item = 0; foreach( const query_ptr& query, tracks ) { qDebug() << query->toString(); @@ -541,7 +541,9 @@ TreeModel::onTracksAdded( const QList& tracks, const QVaria void TreeModel::infoSystemInfo( QString caller, Tomahawk::InfoSystem::InfoType type, QVariant input, QVariant output, Tomahawk::InfoSystem::InfoCustomData customData ) { + Q_UNUSED( customData ); qDebug() << Q_FUNC_INFO; + if ( caller != s_tmInfoIdentifier || type != Tomahawk::InfoSystem::InfoAlbumCoverArt ) { qDebug() << "Info of wrong type or not with our identifier"; @@ -578,6 +580,7 @@ TreeModel::infoSystemInfo( QString caller, Tomahawk::InfoSystem::InfoType type, void TreeModel::infoSystemFinished( QString target ) { + Q_UNUSED( target ); qDebug() << Q_FUNC_INFO; } From 8231127a3b44c78ab3a78047cd93659120c22d05 Mon Sep 17 00:00:00 2001 From: Christian Muehlhaeuser Date: Thu, 14 Apr 2011 07:47:57 +0200 Subject: [PATCH 315/329] * Only show tracks without an album tag below the 'Unknown' album node in TreeModel. --- .../database/databasecommand_allalbums.cpp | 2 +- .../database/databasecommand_alltracks.cpp | 14 ++++++++++---- 2 files changed, 11 insertions(+), 5 deletions(-) diff --git a/src/libtomahawk/database/databasecommand_allalbums.cpp b/src/libtomahawk/database/databasecommand_allalbums.cpp index e0ab9b97e..55b2b7139 100644 --- a/src/libtomahawk/database/databasecommand_allalbums.cpp +++ b/src/libtomahawk/database/databasecommand_allalbums.cpp @@ -63,7 +63,7 @@ DatabaseCommand_AllAlbums::execForArtist( DatabaseImpl* dbi ) while( query.next() ) { unsigned int albumId = query.value( 0 ).toUInt(); - QString albumName; + QString albumName = query.value( 1 ).toString(); if ( query.value( 0 ).isNull() ) { albumName = tr( "Unknown" ); diff --git a/src/libtomahawk/database/databasecommand_alltracks.cpp b/src/libtomahawk/database/databasecommand_alltracks.cpp index a6bcb7151..01117751c 100644 --- a/src/libtomahawk/database/databasecommand_alltracks.cpp +++ b/src/libtomahawk/database/databasecommand_alltracks.cpp @@ -54,10 +54,16 @@ DatabaseCommand_AllTracks::exec( DatabaseImpl* dbi ) if ( !m_collection.isNull() ) sourceToken = QString( "AND file.source %1" ).arg( m_collection->source()->isLocal() ? "IS NULL" : QString( "= %1" ).arg( m_collection->source()->id() ) ); - if ( m_album && m_album->id() == 0 ) + QString albumToken; + if ( m_album ) { - m_artist = m_album->artist().data(); - m_album = 0; + if ( m_album->id() == 0 ) + { + m_artist = m_album->artist().data(); + albumToken = QString( "AND album.id IS NULL" ); + } + else + albumToken = QString( "AND album.id = %1" ).arg( m_album->id() ); } QString sql = QString( @@ -74,7 +80,7 @@ DatabaseCommand_AllTracks::exec( DatabaseImpl* dbi ) "%4 %5 %6" ).arg( sourceToken ) .arg( !m_artist ? QString() : QString( "AND artist.id = %1" ).arg( m_artist->id() ) ) - .arg( !m_album ? QString() : QString( "AND album.id = %1" ).arg( m_album->id() ) ) + .arg( !m_album ? QString() : albumToken ) .arg( m_sortOrder > 0 ? QString( "ORDER BY %1" ).arg( m_orderToken ) : QString() ) .arg( m_sortDescending ? "DESC" : QString() ) .arg( m_amount > 0 ? QString( "LIMIT 0, %1" ).arg( m_amount ) : QString() ); From 3ce41c50bc46b53e87f274bcb702bcdeac5ec212 Mon Sep 17 00:00:00 2001 From: Leo Franchi Date: Thu, 14 Apr 2011 10:42:16 -0400 Subject: [PATCH 316/329] Don't assert if a scrobble failed, that's no fun. --- thirdparty/liblastfm2/src/scrobble/Audioscrobbler.cpp | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/thirdparty/liblastfm2/src/scrobble/Audioscrobbler.cpp b/thirdparty/liblastfm2/src/scrobble/Audioscrobbler.cpp index 3606bd1a4..0a98b7994 100644 --- a/thirdparty/liblastfm2/src/scrobble/Audioscrobbler.cpp +++ b/thirdparty/liblastfm2/src/scrobble/Audioscrobbler.cpp @@ -194,7 +194,8 @@ lastfm::Audioscrobbler::onTrackScrobbleReturn() } else { - Q_ASSERT(false); + qWarning() << "Got error in scrobble submission:" << lfm[ "error" ] << "and silently ignoring. Submission is cached."; + //Q_ASSERT(false); } } From 4b13467e4cae66fe990eaab74b19bcbaaba31110 Mon Sep 17 00:00:00 2001 From: Jeff Mitchell Date: Thu, 14 Apr 2011 16:50:38 -0400 Subject: [PATCH 317/329] Don't add directories to the watcher if watch for changes is disabled --- src/scanmanager.cpp | 49 ++++++++++++++++----------------------------- src/scanmanager.h | 3 +-- 2 files changed, 18 insertions(+), 34 deletions(-) diff --git a/src/scanmanager.cpp b/src/scanmanager.cpp index acca5dc90..577da910d 100644 --- a/src/scanmanager.cpp +++ b/src/scanmanager.cpp @@ -67,10 +67,12 @@ ScanManager::ScanManager( QObject* parent ) connect( m_dirWatcher, SIGNAL( directoryChanged( const QString & ) ), SLOT( handleChangedDir( const QString & ) ) ); if ( TomahawkSettings::instance()->hasScannerPaths() ) + { m_currScannerPaths = TomahawkSettings::instance()->scannerPaths(); - - qDebug() << "loading initial directories to watch"; - QTimer::singleShot( 1000, this, SLOT( startupWatchPaths() ) ); + if ( TomahawkSettings::instance()->watchForChanges() ) + QTimer::singleShot( 1000, this, SLOT( runStartupScan() ) ); + } + m_deferredScanTimer->start(); } @@ -109,7 +111,6 @@ ScanManager::onSettingsChanged() { m_currScannerPaths = TomahawkSettings::instance()->scannerPaths(); m_dirWatcher->removePaths( m_dirWatcher->directories() ); - m_dirWatcher->addPaths( m_currScannerPaths ); runManualScan( m_currScannerPaths ); } @@ -119,35 +120,13 @@ ScanManager::onSettingsChanged() } -void -ScanManager::startupWatchPaths() +void ScanManager::runStartupScan() { qDebug() << Q_FUNC_INFO; - if( !Database::instance() || ( Database::instance() && !Database::instance()->isReady() ) ) - { - QTimer::singleShot( 1000, this, SLOT( startupWatchPaths() ) ); - return; - } - - DatabaseCommand_DirMtimes* cmd = new DatabaseCommand_DirMtimes( m_currScannerPaths ); - connect( cmd, SIGNAL( done( QMap< QString, unsigned int > ) ), - SLOT( setInitialPaths( QMap< QString, unsigned int > ) ) ); - Database::instance()->enqueue( QSharedPointer< DatabaseCommand >( cmd ) ); -} - - -void -ScanManager::setInitialPaths( QMap< QString, unsigned int > pathMap ) -{ - qDebug() << Q_FUNC_INFO; - foreach( QString path, pathMap.keys() ) - { - qDebug() << "Adding " << path << " to watcher"; - m_dirWatcher->addPath( path ); - } - if( TomahawkSettings::instance()->hasScannerPaths() && TomahawkSettings::instance()->watchForChanges() ) - runManualScan( TomahawkSettings::instance()->scannerPaths() ); + QTimer::singleShot( 1000, this, SLOT( runStartupScan() ) ); + else + runManualScan( m_currScannerPaths ); } @@ -156,6 +135,9 @@ ScanManager::runManualScan( const QStringList& paths, bool recursive ) { qDebug() << Q_FUNC_INFO; + if( !Database::instance() || ( Database::instance() && !Database::instance()->isReady() ) ) + return; + if ( !m_musicScannerThreadController && !m_scanner ) //still running if these are not zero { m_musicScannerThreadController = new QThread( this ); @@ -193,10 +175,13 @@ void ScanManager::addWatchedDirs( const QStringList& paths ) { qDebug() << Q_FUNC_INFO; + if ( !TomahawkSettings::instance()->watchForChanges() ) + return; + QStringList currentWatchedPaths = m_dirWatcher->directories(); - foreach( QString path, paths ) + foreach ( QString path, paths ) { - if( !currentWatchedPaths.contains( path ) ) + if ( !currentWatchedPaths.contains( path ) ) { qDebug() << "adding " << path << " to watched dirs"; m_dirWatcher->addPath( path ); diff --git a/src/scanmanager.h b/src/scanmanager.h index fdec0cf15..3cd57fd09 100644 --- a/src/scanmanager.h +++ b/src/scanmanager.h @@ -49,14 +49,13 @@ public slots: void handleChangedDir( const QString& path ); void addWatchedDirs( const QStringList& paths ); void removeWatchedDir( const QString& path ); - void setInitialPaths( QMap< QString, unsigned int > pathMap ); private slots: void scannerQuit(); void scannerFinished(); void scannerDestroyed( QObject* scanner ); - void startupWatchPaths(); + void runStartupScan(); void queuedScanTimeout(); void deferredScanTimeout(); From 714c306032cc82494909df9c26f16cd0bbcf78d9 Mon Sep 17 00:00:00 2001 From: Leo Franchi Date: Tue, 12 Apr 2011 06:56:19 -0400 Subject: [PATCH 318/329] Refactor the resolver config UI for More Prettiness Allow resolvers to configure themselves and whatnot --- data/images/configure.png | Bin 0 -> 717 bytes include/tomahawk/tomahawkapp.h | 8 +- resources.qrc | 1 + src/CMakeLists.txt | 5 + src/libtomahawk/CMakeLists.txt | 2 + src/libtomahawk/functimeout.h | 10 +- src/libtomahawk/pipeline.cpp | 10 +- src/libtomahawk/resolver.cpp | 116 ++++++++++++++++++ src/libtomahawk/resolver.h | 17 ++- src/libtomahawk/tomahawksettings.cpp | 33 +++-- src/libtomahawk/tomahawksettings.h | 51 ++++---- src/resolverconfigdelegate.cpp | 176 +++++++++++++++++++++++++++ src/resolverconfigdelegate.h | 42 +++++++ src/resolverconfigwrapper.cpp | 0 src/resolverconfigwrapper.h | 68 +++++++++++ src/resolvers/qtscriptresolver.cpp | 5 +- src/resolvers/qtscriptresolver.h | 7 +- src/resolvers/scriptresolver.cpp | 53 +++++++- src/resolvers/scriptresolver.h | 6 + src/resolversmodel.cpp | 154 +++++++++++++++++++++++ src/resolversmodel.h | 55 +++++++++ src/settingsdialog.cpp | 80 ++++++------ src/settingsdialog.h | 9 +- src/settingsdialog.ui | 36 ++---- src/tomahawkapp.cpp | 34 +++--- 25 files changed, 835 insertions(+), 143 deletions(-) create mode 100644 data/images/configure.png create mode 100644 src/libtomahawk/resolver.cpp create mode 100644 src/resolverconfigdelegate.cpp create mode 100644 src/resolverconfigdelegate.h create mode 100644 src/resolverconfigwrapper.cpp create mode 100644 src/resolverconfigwrapper.h create mode 100644 src/resolversmodel.cpp create mode 100644 src/resolversmodel.h diff --git a/data/images/configure.png b/data/images/configure.png new file mode 100644 index 0000000000000000000000000000000000000000..5ce478b1adfefacc19d35d61e88a6c02ebe97454 GIT binary patch literal 717 zcmV;;0y6!HP)Px#24YJ`L;(K){{a7>y{D4^000SaNLh0L02{9W02{9XUK)`c00007bV*G`2iXAx z12+)F5m%`I00K=(L_t(|+KrN3NRx3C#*f?DM%ip+yqb-i;h^3{#n{|Ose_>C#@NE# z7GW2nLRnqZRk>l z!E^IGoWld>6hQ%)Ob;ZrwY8lTqK?F3D=U#m#6CX$#%MA3twKK3jp{=S3-bwzqR{g1 zWn_6|PHT0V4^ml84HUxVD;?&gUq4YYxrThci3GMzVQs_8wx>aE7+A7!I1H&&3b@>z z8%!qi9rhAf2rzWW0VzB{sMRWCC={ZTLLqRr7qhDNRaNlz?0&5`==X~y;%hk@XtmlC z)z#Ho0)bh$({nG|qLN>Ej>p>@6bJ+}b93`2b1k5YM55;Lw?1I9w}RI{3DeUbfmgwU zk`fL#FdNvgKDCcf%4E{WJ?36(INk6~Szl-8a=AAFK#9fNG~C_Y#rJsJGrheQ zBv;6hcfyN?hOCn~Qef@KUgduny7b2#PFv(s$3;Yx5y)(Lh@2zNPdMU39%!A08&{uL z2UAU&vxvjtpqADa^wRZWsl2>Q3i-Hgymh^|--2{HEh0%0sm`d7%`u#mNW_}#x8;FB z5JX#iH3q4425MvyP%7)Gf&RxmQ&aDUagOpXV6s?qkSO9GJthS m_collections; - QList m_scriptResolvers; + QHash m_scriptResolvers; Database* m_database; ScanManager *m_scanManager; @@ -143,3 +144,4 @@ private: }; #endif // TOMAHAWKAPP_H + diff --git a/resources.qrc b/resources.qrc index ddbc46b96..9996e5cb0 100644 --- a/resources.qrc +++ b/resources.qrc @@ -74,6 +74,7 @@ ./data/images/back.png ./data/images/forward.png ./data/images/music-icon.png +./data/images/configure.png ./data/topbar-radiobuttons.css ./data/icons/tomahawk-icon-16x16.png ./data/icons/tomahawk-icon-32x32.png diff --git a/src/CMakeLists.txt b/src/CMakeLists.txt index 1b9a95e65..067558b19 100644 --- a/src/CMakeLists.txt +++ b/src/CMakeLists.txt @@ -63,6 +63,8 @@ SET( tomahawkSourcesGui ${tomahawkSourcesGui} tomahawktrayicon.cpp audiocontrols.cpp settingsdialog.cpp + resolverconfigdelegate.cpp + resolversmodel.cpp tomahawkwindow.cpp ) @@ -99,6 +101,9 @@ SET( tomahawkHeadersGui ${tomahawkHeadersGui} tomahawktrayicon.h audiocontrols.h settingsdialog.h + resolverconfigdelegate.h + resolversmodel.h + resolverconfigwrapper.h tomahawkwindow.h ) diff --git a/src/libtomahawk/CMakeLists.txt b/src/libtomahawk/CMakeLists.txt index ae7dc0350..33aaff411 100644 --- a/src/libtomahawk/CMakeLists.txt +++ b/src/libtomahawk/CMakeLists.txt @@ -4,6 +4,7 @@ SET( QT_USE_QTGUI TRUE ) SET( QT_USE_QTSQL TRUE ) SET( QT_USE_QTNETWORK TRUE ) SET( QT_USE_QTXML TRUE ) +SET(QT_USE_QTUITOOLS TRUE) include( ${QT_USE_FILE} ) @@ -21,6 +22,7 @@ set( libSources album.cpp collection.cpp playlist.cpp + resolver.cpp query.cpp result.cpp source.cpp diff --git a/src/libtomahawk/functimeout.h b/src/libtomahawk/functimeout.h index c66134311..bbb632f59 100644 --- a/src/libtomahawk/functimeout.h +++ b/src/libtomahawk/functimeout.h @@ -1,5 +1,5 @@ /* === This file is part of Tomahawk Player - === - * + * * Copyright 2010-2011, Christian Muehlhaeuser * * Tomahawk is free software: you can redistribute it and/or modify @@ -22,6 +22,7 @@ #include #include #include +#include #include "boost/function.hpp" #include "boost/bind.hpp" @@ -43,8 +44,9 @@ class DLLEXPORT FuncTimeout : public QObject Q_OBJECT public: - FuncTimeout( int ms, boost::function func ) + FuncTimeout( int ms, boost::function func, QObject* besafe ) : m_func( func ) + , m_watch( QWeakPointer< QObject >( besafe ) ) { //qDebug() << Q_FUNC_INFO; QTimer::singleShot( ms, this, SLOT( exec() ) ); @@ -58,12 +60,14 @@ public: public slots: void exec() { - m_func(); + if( !m_watch.isNull() ) + m_func(); this->deleteLater(); }; private: boost::function m_func; + QWeakPointer< QObject > m_watch; }; }; // ns diff --git a/src/libtomahawk/pipeline.cpp b/src/libtomahawk/pipeline.cpp index b7d538a37..c3bab44d6 100644 --- a/src/libtomahawk/pipeline.cpp +++ b/src/libtomahawk/pipeline.cpp @@ -1,5 +1,5 @@ /* === This file is part of Tomahawk Player - === - * + * * Copyright 2010-2011, Christian Muehlhaeuser * * Tomahawk is free software: you can redistribute it and/or modify @@ -134,7 +134,7 @@ Pipeline::resolve( const query_ptr& q, bool prioritized ) { if ( q.isNull() ) return; - + QList< query_ptr > qlist; qlist << q; resolve( qlist, prioritized ); @@ -292,7 +292,7 @@ Pipeline::shunt( const query_ptr& q ) { incQIDState( q ); // qDebug() << "Shunting in" << lasttimeout << "ms, q:" << q->toString(); - new FuncTimeout( lasttimeout, boost::bind( &Pipeline::shunt, this, q ) ); + new FuncTimeout( lasttimeout, boost::bind( &Pipeline::shunt, this, q ), this ); } } else @@ -311,8 +311,8 @@ Pipeline::shunt( const query_ptr& q ) bool Pipeline::resolverSorter( const Resolver* left, const Resolver* right ) { - if( left->weight() == right->weight() ) - return left->preference() > right->preference(); + if( left->weight() == right->weight() ) // TODO dispatch in parallel + return left; else return left->weight() > right->weight(); } diff --git a/src/libtomahawk/resolver.cpp b/src/libtomahawk/resolver.cpp new file mode 100644 index 000000000..4e6a757fe --- /dev/null +++ b/src/libtomahawk/resolver.cpp @@ -0,0 +1,116 @@ +/* === This file is part of Tomahawk Player - === + * + * Copyright 2010-2011, Christian Muehlhaeuser + * + * Tomahawk is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * Tomahawk is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with Tomahawk. If not, see . + */ + +#include "resolver.h" + +#include +#include +#include +#include +#include +#include +#include +#include + +QVariant +Tomahawk::ExternalResolver::configMsgFromWidget( QWidget* w ) +{ + if( !w ) + return QVariant(); + + // generate a qvariantmap of all the widgets in the hierarchy, and for each one include the list of properties and values + QVariantMap widgetMap; + addChildProperties( w, widgetMap ); +// qDebug() << "Generated widget variant:" << widgetMap; + return widgetMap; +} + + +void +Tomahawk::ExternalResolver::addChildProperties( QObject* widget, QVariantMap& m ) +{ + // recursively add all properties of this widget to the map, then repeat on all children. + // bare QWidgets are boring---so skip them! They have no input that the user can set. + if( !widget || !widget->isWidgetType() ) + return; + + if( qstrcmp( widget->metaObject()->className(), "QWidget" ) != 0 ) + { +// qDebug() << "Adding properties for this:" << widget->metaObject()->className(); + // add this widget's properties + QVariantMap props; + for( int i = widget->metaObject()->propertyOffset(); i < widget->metaObject()->propertyCount(); i++ ) + { + QString prop = widget->metaObject()->property( i ).name(); + QVariant val = widget->property( prop.toLatin1() ); + // clean up for QJson.... + if( val.canConvert< QPixmap >() || val.canConvert< QImage >() || val.canConvert< QIcon >() ) + continue; + props[ prop ] = val; +// qDebug() << QString( "%1: %2" ).arg( prop ).arg( props[ prop ].toString() ); + } + m[ widget->objectName() ] = props; + } + // and recurse + foreach( QObject* child, widget->children() ) + addChildProperties( child, m ); +} + + +QWidget* +Tomahawk::ExternalResolver::widgetFromData( QByteArray& data, QWidget* parent ) +{ + if( data.isEmpty() ) + return 0; + + QUiLoader l; + QBuffer b( &data ); + QWidget* w = l.load( &b, parent ); + + return w; +} + +QByteArray +Tomahawk::ExternalResolver::fixDataImagePaths( const QByteArray& data, bool compressed, const QVariantMap& images ) +{ + // with a list of images and image data, write each to a temp file, replace the path in the .ui file with the temp file path + QString uiFile = QString::fromUtf8( data ); + foreach( const QString& filename, images.keys() ) + { + if( !uiFile.contains( filename ) ) // make sure the image is used + continue; + + QString fullPath = QDir::tempPath() + "/" + filename; + QFile imgF( fullPath ); + if( !imgF.open( QIODevice::WriteOnly ) ) + { + qWarning() << "Failed to write to temporary image in UI file:" << filename << fullPath; + continue; + } + QByteArray data = images[ filename ].toByteArray(); + qDebug() << "expanding data:" << data << compressed; + data = compressed ? qUncompress( QByteArray::fromBase64( data ) ) : QByteArray::fromBase64( data ); + imgF.write( data ); + imgF.close(); + + // replace the path to the image with the real path + uiFile.replace( filename, fullPath ); + } + return uiFile.toUtf8(); +} + diff --git a/src/libtomahawk/resolver.h b/src/libtomahawk/resolver.h index d1b526ffe..c3a3bae7e 100644 --- a/src/libtomahawk/resolver.h +++ b/src/libtomahawk/resolver.h @@ -1,5 +1,5 @@ /* === This file is part of Tomahawk Player - === - * + * * Copyright 2010-2011, Christian Muehlhaeuser * * Tomahawk is free software: you can redistribute it and/or modify @@ -33,6 +33,9 @@ weighted resolver */ + +class QWidget; + namespace Tomahawk { @@ -45,12 +48,8 @@ public: virtual QString name() const = 0; virtual unsigned int weight() const = 0; - virtual unsigned int preference() const { return 100; }; virtual unsigned int timeout() const = 0; - //virtual QWidget * configUI() { return 0; }; - //etc - public slots: virtual void resolve( const Tomahawk::query_ptr& query ) = 0; }; @@ -64,10 +63,18 @@ public: virtual QString filePath() const { return m_filePath; } + virtual QWidget* configUI() const = 0; + virtual void saveConfig() = 0; public slots: virtual void stop() = 0; +protected: + QWidget* widgetFromData( QByteArray& data, QWidget* parent = 0 ); + QVariant configMsgFromWidget( QWidget* w ); + QByteArray fixDataImagePaths( const QByteArray& data, bool compressed, const QVariantMap& images ); private: + void addChildProperties( QObject* parent, QVariantMap& m ); + QString m_filePath; }; diff --git a/src/libtomahawk/tomahawksettings.cpp b/src/libtomahawk/tomahawksettings.cpp index 37a905cad..22ce4f548 100644 --- a/src/libtomahawk/tomahawksettings.cpp +++ b/src/libtomahawk/tomahawksettings.cpp @@ -1,5 +1,5 @@ /* === This file is part of Tomahawk Player - === - * + * * Copyright 2010-2011, Christian Muehlhaeuser * * Tomahawk is free software: you can redistribute it and/or modify @@ -52,7 +52,7 @@ TomahawkSettings::TomahawkSettings( QObject* parent ) qDebug() << "Config version outdated, old:" << value( "configversion" ).toUInt() << "new:" << VERSION << "Doing upgrade, if any..."; - + // insert upgrade code here as required setValue( "configversion", VERSION ); } @@ -415,7 +415,7 @@ int TomahawkSettings::defaultPort() const { return 50210; -} +} int TomahawkSettings::externalPort() const @@ -485,7 +485,7 @@ TomahawkSettings::setTwitterScreenName( const QString& screenName ) { setValue( "twitter/ScreenName", screenName ); } - + QString TomahawkSettings::twitterOAuthToken() const { @@ -627,20 +627,33 @@ TomahawkSettings::setXmppBotPort( const int port ) setValue( "xmppBot/port", port ); } -void +void TomahawkSettings::addScriptResolver(const QString& resolver) { - setValue( "script/resolvers", scriptResolvers() << resolver ); + setValue( "script/resolvers", allScriptResolvers() << resolver ); } -QStringList -TomahawkSettings::scriptResolvers() const +QStringList +TomahawkSettings::allScriptResolvers() const { return value( "script/resolvers" ).toStringList(); } -void -TomahawkSettings::setScriptResolvers( const QStringList& resolver ) +void +TomahawkSettings::setAllScriptResolvers( const QStringList& resolver ) { setValue( "script/resolvers", resolver ); } + + +QStringList +TomahawkSettings::enabledScriptResolvers() const +{ + return value( "script/loadedresolvers" ).toStringList(); +} + +void +TomahawkSettings::setEnabledScriptResolvers( const QStringList& resolvers ) +{ + setValue( "script/loadedresolvers", resolvers ); +} diff --git a/src/libtomahawk/tomahawksettings.h b/src/libtomahawk/tomahawksettings.h index 42daa91e3..0b47ca685 100644 --- a/src/libtomahawk/tomahawksettings.h +++ b/src/libtomahawk/tomahawksettings.h @@ -1,5 +1,5 @@ /* === This file is part of Tomahawk Player - === - * + * * Copyright 2010-2011, Christian Muehlhaeuser * * Tomahawk is free software: you can redistribute it and/or modify @@ -47,14 +47,14 @@ public: bool watchForChanges() const; void setWatchForChanges( bool watch ); - + bool acceptedLegalWarning() const; void setAcceptedLegalWarning( bool accept ); - + /// UI settings QByteArray mainWindowGeometry() const; void setMainWindowGeometry( const QByteArray& geom ); - + QByteArray mainWindowState() const; void setMainWindowState( const QByteArray& state ); @@ -71,24 +71,24 @@ public: /// Jabber settings bool jabberAutoConnect() const; /// true by default void setJabberAutoConnect( bool autoconnect = false ); - + QString jabberUsername() const; void setJabberUsername( const QString& username ); - + QString jabberPassword() const; void setJabberPassword( const QString& pw ); - + QString jabberServer() const; void setJabberServer( const QString& server ); - + unsigned int jabberPort() const; // default is 5222 void setJabberPort( int port ); - + /// Network settings enum ExternalAddressMode { Lan, Upnp }; ExternalAddressMode externalAddressMode() const; void setExternalAddressMode( ExternalAddressMode externalAddressMode ); - + bool preferStaticHostPort() const; void setPreferStaticHostPort( bool prefer ); @@ -120,42 +120,42 @@ public: /// ACL settings QStringList aclEntries() const; void setAclEntries( const QStringList &entries ); - + /// Last.fm settings bool scrobblingEnabled() const; /// false by default void setScrobblingEnabled( bool enable ); - + QString lastFmUsername() const; void setLastFmUsername( const QString& username ); - + QString lastFmPassword() const; void setLastFmPassword( const QString& password ); - + QByteArray lastFmSessionKey() const; void setLastFmSessionKey( const QByteArray& key ); - + /// Twitter settings QString twitterScreenName() const; void setTwitterScreenName( const QString& screenName ); - + QString twitterOAuthToken() const; void setTwitterOAuthToken( const QString& oauthtoken ); - + QString twitterOAuthTokenSecret() const; void setTwitterOAuthTokenSecret( const QString& oauthtokensecret ); qint64 twitterCachedFriendsSinceId() const; void setTwitterCachedFriendsSinceId( qint64 sinceid ); - + qint64 twitterCachedMentionsSinceId() const; void setTwitterCachedMentionsSinceId( qint64 sinceid ); - + qint64 twitterCachedDirectMessagesSinceId() const; void setTwitterCachedDirectMessagesSinceId( qint64 sinceid ); - + QHash twitterCachedPeers() const; void setTwitterCachedPeers( const QHash &cachedPeers ); - + /// XMPP Component Settings QString xmppBotServer() const; void setXmppBotServer( const QString &server ); @@ -168,11 +168,14 @@ public: int xmppBotPort() const; void setXmppBotPort( const int port ); - + /// Script resolver settings - QStringList scriptResolvers() const; - void setScriptResolvers( const QStringList& resolver ); + QStringList allScriptResolvers() const; + void setAllScriptResolvers( const QStringList& resolvers ); void addScriptResolver( const QString& resolver ); + QStringList enabledScriptResolvers() const; + void setEnabledScriptResolvers( const QStringList& resolvers ); + signals: void changed(); diff --git a/src/resolverconfigdelegate.cpp b/src/resolverconfigdelegate.cpp new file mode 100644 index 000000000..fb983c8ae --- /dev/null +++ b/src/resolverconfigdelegate.cpp @@ -0,0 +1,176 @@ +/* === This file is part of Tomahawk Player - === + * + * Copyright 2010-2011, Christian Muehlhaeuser + * + * Tomahawk is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * Tomahawk is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with Tomahawk. If not, see . + */ + + +#include "resolverconfigdelegate.h" + +#include "resolversmodel.h" +#include "tomahawk/tomahawkapp.h" + +#include +#include +#include + +#define PADDING 4 + +ResolverConfigDelegate::ResolverConfigDelegate( QObject* parent ) + : QStyledItemDelegate( parent ) + , m_configPressed( false ) +{ + +} + +void +ResolverConfigDelegate::paint( QPainter* painter, const QStyleOptionViewItem& option, const QModelIndex& index ) const +{ + QStyleOptionViewItemV4 opt = option; + initStyleOption( &opt, index ); + QRect itemRect = opt.rect; + int top = itemRect.top(); + + QFont name = opt.font; + name.setPointSize( name.pointSize() + 2 ); + name.setBold( true ); + + QFont path = opt.font; + path.setItalic( true ); + path.setPointSize( path.pointSize() - 1 ); + + + QFontMetrics bfm( name ); + QFontMetrics sfm( path ); + + // draw the background + const QWidget* w = opt.widget; + QStyle* style = w ? w->style() : QApplication::style(); + style->drawPrimitive( QStyle::PE_PanelItemViewItem, &opt, painter, w ); + + int rightSplit = itemRect.width(); + int rectW = opt.rect.height() - 4 * PADDING; + QRect confRect = QRect( rightSplit - rectW - 2 * PADDING, 2 * PADDING + top, rectW, rectW ); + // if the resolver has a config widget, paint it first (right-aligned) + if( index.data( ResolversModel::HasConfig ).toBool() ) { + // draw it the same size as the check belox + QStyleOptionToolButton topt; + topt.font = opt.font; + topt.icon = QIcon( RESPATH "images/configure.png" ); + topt.iconSize = QSize( 16, 16 ); + topt.rect = confRect; + topt.subControls = QStyle::SC_ToolButton; + topt.activeSubControls = QStyle::SC_None; + topt.features = QStyleOptionToolButton::None; + topt.pos = confRect.topLeft(); + topt.state = m_configPressed ? QStyle::State_On : QStyle::State_Raised; + if( opt.state & QStyle::State_MouseOver || m_configPressed ) + topt.state |= QStyle::State_HasFocus; + style->drawComplexControl( QStyle::CC_ToolButton, &topt, painter, w ); + } + + // draw check + confRect.moveTo( 2 * PADDING, 2 * PADDING + top ); + opt.rect = confRect; + opt.checkState == Qt::Checked ? opt.state |= QStyle::State_On : opt.state |= QStyle::State_Off; + style->drawPrimitive( QStyle::PE_IndicatorViewItemCheck, &opt, painter, w ); + itemRect.setX( opt.rect.topRight().x() + PADDING ); + + QString nameStr = bfm.elidedText( index.data( ResolversModel::ResolverName ).toString(),Qt::ElideRight, rightSplit ); + painter->save(); + painter->setFont( name ); + QRect textRect = itemRect.adjusted( PADDING, PADDING, -PADDING, -PADDING ); + textRect.setBottom( itemRect.height() / 2 + top ); + painter->drawText( textRect, nameStr ); + painter->restore(); + + QString pathStr = sfm.elidedText( index.data( ResolversModel::ResolverPath ).toString(),Qt::ElideMiddle, rightSplit ); + painter->save(); + painter->setFont( path ); + painter->setBrush( Qt::gray ); + textRect.moveTop( itemRect.height() / 2 + top ); + painter->drawText( textRect, pathStr ); + painter->restore(); + +} + +QSize +ResolverConfigDelegate::sizeHint( const QStyleOptionViewItem& option, const QModelIndex& index ) const +{ + int width = QStyledItemDelegate::sizeHint( option, index ).width(); + + QStyleOptionViewItemV4 opt = option; + initStyleOption( &opt, index ); + + + QFont name = opt.font; + name.setPointSize( name.pointSize() + 2 ); + name.setBold( true ); + + QFont path = opt.font; + path.setItalic( true ); + path.setPointSize( path.pointSize() - 1 ); + + + QFontMetrics bfm( name ); + QFontMetrics sfm( path ); + return QSize( width, 3 * PADDING + bfm.height() + sfm.height() ); +} + +bool +ResolverConfigDelegate::editorEvent(QEvent* event, QAbstractItemModel* model, const QStyleOptionViewItem& option, const QModelIndex& index) +{ +// qDebug() << "EDITOR EVENT!" << ( event->type() == QEvent::MouseButtonRelease ); + + QStyleOptionViewItemV4 viewOpt( option ); + initStyleOption( &viewOpt, index ); + const QWidget* w = viewOpt.widget; + QStyle* style = w ? w->style() : QApplication::style(); + int top = viewOpt.rect.top(); + + if( event->type() == QEvent::MouseButtonRelease || event->type() == QEvent::MouseButtonDblClick ) { + m_configPressed = false; + + int rectW = option.rect.height() - 4 * PADDING; + QRect checkRect = QRect( 2 * PADDING, 2 * PADDING + top, rectW, rectW ); + QMouseEvent* me = static_cast< QMouseEvent* >( event ); + if( me->button() != Qt::LeftButton || !checkRect.contains( me->pos() ) ) + return false; + + // eat the double click events inside the check rect + if( event->type() == QEvent::MouseButtonDblClick ) { + return true; + } + + Qt::CheckState curState = static_cast< Qt::CheckState >( index.data( Qt::CheckStateRole ).toInt() ); + Qt::CheckState newState = curState == Qt::Checked ? Qt::Unchecked : Qt::Checked; + return model->setData( index, newState, Qt::CheckStateRole ); + + } else if( event->type() == QEvent::MouseButtonPress ) { + int rightSplit = viewOpt.rect.width(); + int rectW = viewOpt.rect.height() - 4 * PADDING; + QRect confRect = QRect( rightSplit - rectW - 2 * PADDING, 2 * PADDING + top, rectW, rectW ); + + QMouseEvent* me = static_cast< QMouseEvent* >( event ); + if( me->button() == Qt::LeftButton && confRect.contains( me->pos() ) ) { + m_configPressed = true; + + emit openConfig( index.data( ResolversModel::ResolverPath ).toString() ); + return true; + } + } + + return QStyledItemDelegate::editorEvent( event, model, option, index ); +} diff --git a/src/resolverconfigdelegate.h b/src/resolverconfigdelegate.h new file mode 100644 index 000000000..280e63ccb --- /dev/null +++ b/src/resolverconfigdelegate.h @@ -0,0 +1,42 @@ +/* === This file is part of Tomahawk Player - === + * + * Copyright 2010-2011, Christian Muehlhaeuser + * + * Tomahawk is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * Tomahawk is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with Tomahawk. If not, see . + */ + + +#ifndef RESOLVERCONFIGDELEGATE_H +#define RESOLVERCONFIGDELEGATE_H + +#include + + +class ResolverConfigDelegate : public QStyledItemDelegate +{ + Q_OBJECT +public: + explicit ResolverConfigDelegate(QObject* parent = 0); + virtual void paint(QPainter* painter, const QStyleOptionViewItem& option, const QModelIndex& index) const; + virtual QSize sizeHint(const QStyleOptionViewItem& option, const QModelIndex& index) const; + virtual bool editorEvent( QEvent* event, QAbstractItemModel* model, const QStyleOptionViewItem& option, const QModelIndex& index ); + +signals: + void openConfig( const QString& resolverPath ); + +private: + bool m_configPressed; +}; + +#endif // RESOLVERCONFIGDELEGATE_H diff --git a/src/resolverconfigwrapper.cpp b/src/resolverconfigwrapper.cpp new file mode 100644 index 000000000..e69de29bb diff --git a/src/resolverconfigwrapper.h b/src/resolverconfigwrapper.h new file mode 100644 index 000000000..e43700e38 --- /dev/null +++ b/src/resolverconfigwrapper.h @@ -0,0 +1,68 @@ +/* === This file is part of Tomahawk Player - === + * + * Copyright 2010-2011, Christian Muehlhaeuser + * + * Tomahawk is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * Tomahawk is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with Tomahawk. If not, see . + */ +#ifndef RESOLVER_CONFIG_WRAPPER +#define RESOLVER_CONFIG_WRAPPER + +#include +#include +#include + +class ResolverConfigWrapper : public QDialog +{ + Q_OBJECT +public: + ResolverConfigWrapper( QWidget* conf, const QString& title, QWidget* parent ) : QDialog( parent ), m_widget( conf ) + { + setWindowTitle( title ); + + QVBoxLayout* v = new QVBoxLayout( this ); + v->addWidget( m_widget ); + + QDialogButtonBox* buttons = new QDialogButtonBox( QDialogButtonBox::Ok | QDialogButtonBox::Cancel, Qt::Horizontal, this ); + connect( buttons, SIGNAL( clicked( QAbstractButton*) ), this, SLOT( closed( QAbstractButton* ) ) ); + connect( this, SIGNAL( rejected() ), this, SLOT( rejected() ) ); + v->addWidget( buttons ); + + setLayout( v ); + } +public slots: + void closed( QAbstractButton* b ) + { + // let the config widget live to see another day + layout()->removeWidget( m_widget ); + m_widget->setParent( 0 ); + + QDialogButtonBox* buttons = qobject_cast< QDialogButtonBox* >( sender() ); + if( buttons->standardButton( b ) == QDialogButtonBox::Ok ) + done( QDialog::Accepted ); + else + done( QDialog::Rejected ); + } + + // we get a rejected() signal emitted if the user presses escape (and no clicked() signal ) + void rejected() + { + layout()->removeWidget( m_widget ); + m_widget->setParent( 0 ); + } + +private: + QWidget* m_widget; +}; + +#endif diff --git a/src/resolvers/qtscriptresolver.cpp b/src/resolvers/qtscriptresolver.cpp index 3d1773fe8..78375c522 100644 --- a/src/resolvers/qtscriptresolver.cpp +++ b/src/resolvers/qtscriptresolver.cpp @@ -1,5 +1,5 @@ /* === This file is part of Tomahawk Player - === - * + * * Copyright 2010-2011, Christian Muehlhaeuser * * Tomahawk is free software: you can redistribute it and/or modify @@ -49,9 +49,8 @@ QtScriptResolver::QtScriptResolver( const QString& scriptPath ) m_name = m.value( "name" ).toString(); m_weight = m.value( "weight", 0 ).toUInt(); m_timeout = m.value( "timeout", 25 ).toUInt() * 1000; - m_preference = m.value( "preference", 0 ).toUInt(); - qDebug() << Q_FUNC_INFO << m_name << m_weight << m_timeout << m_preference; + qDebug() << Q_FUNC_INFO << m_name << m_weight << m_timeout; m_ready = true; Tomahawk::Pipeline::instance()->addResolver( this ); diff --git a/src/resolvers/qtscriptresolver.h b/src/resolvers/qtscriptresolver.h index a6850ac96..ed90f8261 100644 --- a/src/resolvers/qtscriptresolver.h +++ b/src/resolvers/qtscriptresolver.h @@ -1,5 +1,5 @@ /* === This file is part of Tomahawk Player - === - * + * * Copyright 2010-2011, Christian Muehlhaeuser * * Tomahawk is free software: you can redistribute it and/or modify @@ -68,9 +68,10 @@ public: virtual QString name() const { return m_name; } virtual unsigned int weight() const { return m_weight; } - virtual unsigned int preference() const { return m_preference; } virtual unsigned int timeout() const { return m_timeout; } + virtual QWidget* configUI() const { return 0; } // TODO support properly for qtscript resolvers too! + virtual void saveConfig() {} public slots: virtual void resolve( const Tomahawk::query_ptr& query ); virtual void stop(); @@ -82,7 +83,7 @@ private: ScriptEngine* m_engine; QString m_name; - unsigned int m_weight, m_preference, m_timeout; + unsigned int m_weight, m_timeout; bool m_ready, m_stopped; }; diff --git a/src/resolvers/scriptresolver.cpp b/src/resolvers/scriptresolver.cpp index f84acf492..a713f186c 100644 --- a/src/resolvers/scriptresolver.cpp +++ b/src/resolvers/scriptresolver.cpp @@ -49,6 +49,9 @@ ScriptResolver::~ScriptResolver() stop(); Tomahawk::Pipeline::instance()->removeResolver( this ); + + if( !m_configWidget.isNull() ) + delete m_configWidget.data(); } @@ -94,8 +97,6 @@ ScriptResolver::sendMsg( const QByteArray& msg ) { qDebug() << Q_FUNC_INFO << m_ready << msg << msg.length(); - if( !m_ready ) return; - quint32 len; qToBigEndian( msg.length(), (uchar*) &len ); m_proc.write( (const char*) &len, 4 ); @@ -121,6 +122,9 @@ ScriptResolver::handleMsg( const QByteArray& msg ) { doSetup( m ); return; + } else if( msgtype == "confwidget" ) { + setupConfWidget( m ); + return; } if( msgtype == "results" ) @@ -212,7 +216,7 @@ ScriptResolver::resolve( const Tomahawk::query_ptr& query ) sendMsg( msg ); m_queryState.insert( query->id(), 1 ); - new Tomahawk::FuncTimeout( m_timeout, boost::bind( &ScriptResolver::onTimeout, this, query ) ); + new Tomahawk::FuncTimeout( m_timeout, boost::bind( &ScriptResolver::onTimeout, this, query ), this ); } @@ -223,17 +227,54 @@ ScriptResolver::doSetup( const QVariantMap& m ) m_name = m.value( "name" ).toString(); m_weight = m.value( "weight", 0 ).toUInt(); m_timeout = m.value( "timeout", 25 ).toUInt() * 1000; - m_preference = m.value( "preference", 0 ).toUInt(); qDebug() << "SCRIPT" << filePath() << "READY," << endl << "name" << m_name << endl << "weight" << m_weight << endl - << "timeout" << m_timeout << endl - << "preference" << m_preference; + << "timeout" << m_timeout; m_ready = true; Tomahawk::Pipeline::instance()->addResolver( this ); } +void +ScriptResolver::setupConfWidget( const QVariantMap& m ) +{ + bool compressed = m.value( "compressed", "false" ).toString() == "true"; + qDebug() << "Resolver has a preferences widget! compressed?" << compressed << m; + + QByteArray uiData = m[ "widget" ].toByteArray(); + if( compressed ) + uiData = qUncompress( QByteArray::fromBase64( uiData ) ); + else + uiData = QByteArray::fromBase64( uiData ); + + if( m.contains( "images" ) ) + uiData = fixDataImagePaths( uiData, compressed, m[ "images" ].toMap() ); + m_configWidget = QWeakPointer< QWidget >( widgetFromData( uiData, 0 ) ); +} + + +void +ScriptResolver::saveConfig() +{ + Q_ASSERT( !m_configWidget.isNull() ); + + QVariantMap m; + m.insert( "_msgtype", "setpref" ); + QVariant widgets = configMsgFromWidget( m_configWidget.data() ); + m.insert( "widgets", widgets ); + QByteArray data = m_serializer.serialize( m ); + sendMsg( data ); +} + +QWidget* ScriptResolver::configUI() const +{ + if( m_configWidget.isNull() ) + return 0; + else + return m_configWidget.data(); +} + void ScriptResolver::stop() diff --git a/src/resolvers/scriptresolver.h b/src/resolvers/scriptresolver.h index 60d636352..34e29fdd2 100644 --- a/src/resolvers/scriptresolver.h +++ b/src/resolvers/scriptresolver.h @@ -29,6 +29,7 @@ #include "query.h" #include "result.h" +class QWidget; class ScriptResolver : public Tomahawk::ExternalResolver { Q_OBJECT @@ -42,6 +43,9 @@ public: virtual unsigned int preference() const { return m_preference; } virtual unsigned int timeout() const { return m_timeout; } + virtual QWidget* configUI() const; + virtual void saveConfig(); + signals: void finished(); @@ -60,10 +64,12 @@ private: void handleMsg( const QByteArray& msg ); void sendMsg( const QByteArray& msg ); void doSetup( const QVariantMap& m ); + void setupConfWidget( const QVariantMap& m ); QProcess m_proc; QString m_name; unsigned int m_weight, m_preference, m_timeout, m_num_restarts; + QWeakPointer< QWidget > m_configWidget; quint32 m_msgsize; QByteArray m_msg; diff --git a/src/resolversmodel.cpp b/src/resolversmodel.cpp new file mode 100644 index 000000000..07e7c8aca --- /dev/null +++ b/src/resolversmodel.cpp @@ -0,0 +1,154 @@ +/* === This file is part of Tomahawk Player - === + * + * Copyright 2010-2011, Christian Muehlhaeuser + * + * Tomahawk is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * Tomahawk is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with Tomahawk. If not, see . + */ + + +#include "resolversmodel.h" +#include +#include +#include + + +ResolversModel::ResolversModel( const QStringList& allResolvers, const QStringList& enabledResolvers, QObject* parent ) + : QAbstractListModel( parent ) + , m_allResolvers( allResolvers ) + , m_enabledResolvers( enabledResolvers ) +{ + // do some sanity checking just in case + bool changed = false; + foreach( const QString& l, m_enabledResolvers ) { + if( !m_allResolvers.contains( l ) ) { + m_enabledResolvers.removeAll( l ); + changed = true; + } + } + if( changed ) + TomahawkSettings::instance()->setEnabledScriptResolvers( m_enabledResolvers ); + +} + + +ResolversModel::~ResolversModel() +{ + +} + +QVariant +ResolversModel::data( const QModelIndex& index, int role ) const +{ + if( !index.isValid() ) + return QVariant(); + + switch( role ) + { + case Qt::DisplayRole: + case ResolversModel::ResolverName: + { + QFileInfo info( m_allResolvers.at( index.row() ) ); + return info.baseName(); + } + case ResolversModel::ResolverPath: + return m_allResolvers.at( index.row() ); + case ResolversModel::HasConfig: + if( Tomahawk::ExternalResolver* r = TomahawkApp::instance()->resolverForPath( m_allResolvers.at( index.row() ) ) ) // if we have one, it means we are loaded too! + return r->configUI() != 0; + return false; + case Qt::CheckStateRole: + return m_enabledResolvers.contains( m_allResolvers.at( index.row() ) ) ? Qt::Checked : Qt::Unchecked; + default: + return QVariant(); + } +} + +bool +ResolversModel::setData( const QModelIndex& index, const QVariant& value, int role ) +{ + if( role == Qt::CheckStateRole ) { + Qt::CheckState state = static_cast< Qt::CheckState >( value.toInt() ); + QString resolver = m_allResolvers.at( index.row() ); + + if( state == Qt::Checked && !m_enabledResolvers.contains( resolver ) ) { + m_enabledResolvers.append( resolver ); + + TomahawkApp::instance()->enableScriptResolver( resolver ); + } else if( state == Qt::Unchecked ) { + m_enabledResolvers.removeAll( resolver ); + + TomahawkApp::instance()->disableScriptResolver( resolver ); + } + dataChanged( index, index ); + + return true; + } + return false; +} + + +int +ResolversModel::rowCount( const QModelIndex& parent ) const +{ + return m_allResolvers.size(); +} + +int +ResolversModel::columnCount(const QModelIndex& parent) const +{ + return 1; +} + +Qt::ItemFlags +ResolversModel::flags( const QModelIndex& index ) const +{ + return QAbstractItemModel::flags(index) | Qt::ItemIsUserCheckable; +} + + +void +ResolversModel::addResolver( const QString& resolver, bool enable ) +{ + beginInsertRows( QModelIndex(), m_allResolvers.count(), m_allResolvers.count() ); + m_allResolvers << resolver; + if( enable ) + m_enabledResolvers << resolver; + endInsertRows(); +} + +void +ResolversModel::removeResolver( const QString& resolver ) +{ + for( int i = 0; i < m_allResolvers.count(); i++ ) { + if( m_allResolvers.at( i ) == resolver ) { + beginRemoveRows( QModelIndex(), i, i ); + m_allResolvers.takeAt( i ); + endRemoveRows(); + } + } + m_enabledResolvers.removeAll( resolver ); +} + +QStringList +ResolversModel::allResolvers() const +{ + return m_allResolvers; +} + +QStringList +ResolversModel::enabledResolvers() const +{ + return m_enabledResolvers; +} + diff --git a/src/resolversmodel.h b/src/resolversmodel.h new file mode 100644 index 000000000..c6cf8fa73 --- /dev/null +++ b/src/resolversmodel.h @@ -0,0 +1,55 @@ +/* === This file is part of Tomahawk Player - === + * + * Copyright 2010-2011, Christian Muehlhaeuser + * + * Tomahawk is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * Tomahawk is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with Tomahawk. If not, see . + */ + + +#ifndef RESOLVERSMODEL_H +#define RESOLVERSMODEL_H + +#include +#include + + +class ResolversModel : public QAbstractListModel +{ +public: + enum Roles { + ResolverName = Qt::UserRole + 15, + ResolverPath = Qt::UserRole + 16, + HasConfig = Qt::UserRole + 17 + }; + + explicit ResolversModel( const QStringList& allResolvers, const QStringList& enabledResolvers, QObject* parent = 0 ); + virtual ~ResolversModel(); + + virtual QVariant data( const QModelIndex& index, int role = Qt::DisplayRole ) const; + virtual int rowCount( const QModelIndex& parent = QModelIndex() ) const; + virtual int columnCount( const QModelIndex& parent ) const; + virtual Qt::ItemFlags flags(const QModelIndex& index) const; + virtual bool setData(const QModelIndex& index, const QVariant& value, int role = Qt::EditRole); + + void addResolver( const QString& resolver, bool enable = false ); + void removeResolver( const QString& resolver ); + + QStringList allResolvers() const; + QStringList enabledResolvers() const; +private: + QStringList m_allResolvers; + QStringList m_enabledResolvers; +}; + +#endif // RESOLVERSMODEL_H diff --git a/src/settingsdialog.cpp b/src/settingsdialog.cpp index 72bc4edbe..966f193d1 100644 --- a/src/settingsdialog.cpp +++ b/src/settingsdialog.cpp @@ -1,5 +1,5 @@ /* === This file is part of Tomahawk Player - === - * + * * Copyright 2010-2011, Christian Muehlhaeuser * * Tomahawk is free software: you can redistribute it and/or modify @@ -24,6 +24,7 @@ #include #include #include +#include #ifdef LIBLASTFM_FOUND #include @@ -39,6 +40,9 @@ #include "sip/SipHandler.h" #include #include "scanmanager.h" +#include "resolverconfigdelegate.h" +#include "resolversmodel.h" +#include "resolverconfigwrapper.h" static QString md5( const QByteArray& src ) @@ -47,7 +51,6 @@ md5( const QByteArray& src ) return QString::fromLatin1( digest.toHex() ).rightJustified( 32, '0' ); } - SettingsDialog::SettingsDialog( QWidget *parent ) : QDialog( parent ) , ui( new Ui::SettingsDialog ) @@ -96,18 +99,19 @@ SettingsDialog::SettingsDialog( QWidget *parent ) ui->lineEditLastfmUsername->setText( s->lastFmUsername() ); ui->lineEditLastfmPassword->setText(s->lastFmPassword() ); connect( ui->pushButtonTestLastfmLogin, SIGNAL( clicked( bool) ), this, SLOT( testLastFmLogin() ) ); - + // SCRIPT RESOLVER ui->removeScript->setEnabled( false ); - foreach( const QString& resolver, s->scriptResolvers() ) { - QFileInfo info( resolver ); - ui->scriptList->addTopLevelItem( new QTreeWidgetItem( QStringList() << info.baseName() << resolver ) ); - - } - connect( ui->scriptList, SIGNAL( itemClicked( QTreeWidgetItem*, int ) ), this, SLOT( scriptSelectionChanged() ) ); + ResolverConfigDelegate* del = new ResolverConfigDelegate( this ); + connect( del, SIGNAL( openConfig( QString ) ), this, SLOT( openResolverConfig( QString ) ) ); + ui->scriptList->setItemDelegate( del ); + m_resolversModel = new ResolversModel( s->allScriptResolvers(), s->enabledScriptResolvers(), this ); + ui->scriptList->setModel( m_resolversModel ); + + 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( ui->buttonBrowse, SIGNAL( clicked() ), SLOT( showPathSelector() ) ); connect( ui->proxyButton, SIGNAL( clicked() ), SLOT( showProxySettings() ) ); connect( ui->checkBoxStaticPreferred, SIGNAL( toggled(bool) ), SLOT( toggleUpnp(bool) ) ); @@ -132,23 +136,19 @@ SettingsDialog::~SettingsDialog() s->setJabberPassword( ui->jabberPassword->text() ); s->setJabberServer( ui->jabberServer->text() ); s->setJabberPort( ui->jabberPort->value() ); - + s->setExternalHostname( ui->staticHostName->text() ); s->setExternalPort( ui->staticPort->value() ); s->setScannerPaths( QStringList( ui->lineEditMusicPath->text() ) ); s->setWatchForChanges( ui->checkBoxWatchForChanges->isChecked() ); - + s->setScrobblingEnabled( ui->checkBoxEnableLastfm->isChecked() ); s->setLastFmUsername( ui->lineEditLastfmUsername->text() ); s->setLastFmPassword( ui->lineEditLastfmPassword->text() ); - QStringList resolvers; - for( int i = 0; i < ui->scriptList->topLevelItemCount(); i++ ) - { - resolvers << ui->scriptList->topLevelItem( i )->data( 1, Qt::DisplayRole ).toString(); - } - s->setScriptResolvers( resolvers ); + s->setAllScriptResolvers( m_resolversModel->allResolvers() ); + s->setEnabledScriptResolvers( m_resolversModel->enabledResolvers() ); s->applyChanges(); } @@ -260,13 +260,13 @@ SettingsDialog::onLastFmFinished() ui->pushButtonTestLastfmLogin->setEnabled( false ); } break; - + case QNetworkReply::ContentOperationNotPermittedError: case QNetworkReply::AuthenticationRequiredError: ui->pushButtonTestLastfmLogin->setText( tr( "Failed" ) ); ui->pushButtonTestLastfmLogin->setEnabled( true ); break; - + default: qDebug() << "Couldn't get last.fm auth result"; ui->pushButtonTestLastfmLogin->setText( tr( "Could not contact server" ) ); @@ -336,38 +336,50 @@ ProxyDialog::saveSettings() } -void +void SettingsDialog::addScriptResolver() { QString resolver = QFileDialog::getOpenFileName( this, tr( "Load script resolver file" ), qApp->applicationDirPath() ); if( !resolver.isEmpty() ) { - QFileInfo info( resolver ); - ui->scriptList->addTopLevelItem( new QTreeWidgetItem( QStringList() << info.baseName() << resolver ) ); - - TomahawkApp::instance()->addScriptResolver( resolver ); + m_resolversModel->addResolver( resolver, true ); + TomahawkApp::instance()->enableScriptResolver( resolver ); } } -void +void SettingsDialog::removeScriptResolver() { // only one selection - if( !ui->scriptList->selectedItems().isEmpty() ) { - QString resolver = ui->scriptList->selectedItems().first()->data( 1, Qt::DisplayRole ).toString(); - delete ui->scriptList->takeTopLevelItem( ui->scriptList->indexOfTopLevelItem( ui->scriptList->selectedItems().first() ) ); - - TomahawkApp::instance()->removeScriptResolver( resolver ); + if( !ui->scriptList->selectionModel()->selectedIndexes().isEmpty() ) { + QString resolver = ui->scriptList->selectionModel()->selectedIndexes().first().data( ResolversModel::ResolverPath ).toString(); + m_resolversModel->removeResolver( resolver ); + + TomahawkApp::instance()->disableScriptResolver( resolver ); } } - -void +void SettingsDialog::scriptSelectionChanged() { - if( !ui->scriptList->selectedItems().isEmpty() ) { + if( !ui->scriptList->selectionModel()->selectedIndexes().isEmpty() ) { ui->removeScript->setEnabled( true ); } else { ui->removeScript->setEnabled( false ); } } + +void +SettingsDialog::openResolverConfig( const QString& resolver ) +{ + Tomahawk::ExternalResolver* r = TomahawkApp::instance()->resolverForPath( resolver ); + if( r && r->configUI() ) { + ResolverConfigWrapper dialog( r->configUI(), "Resolver Config", this ); + QWeakPointer< ResolverConfigWrapper > watcher( &dialog ); + int ret = dialog.exec(); + if( !watcher.isNull() && ret == QDialog::Accepted ) { + // send changed config to resolver + r->saveConfig(); + } + } +} diff --git a/src/settingsdialog.h b/src/settingsdialog.h index 02a3c608f..880c319da 100644 --- a/src/settingsdialog.h +++ b/src/settingsdialog.h @@ -1,5 +1,5 @@ /* === This file is part of Tomahawk Player - === - * + * * Copyright 2010-2011, Christian Muehlhaeuser * * Tomahawk is free software: you can redistribute it and/or modify @@ -21,6 +21,7 @@ #include +class ResolversModel; class QNetworkReply; namespace Ui @@ -53,7 +54,7 @@ public: signals: void settingsChanged(); - + protected: void changeEvent( QEvent* e ); @@ -71,13 +72,15 @@ private slots: void addScriptResolver(); void scriptSelectionChanged(); void removeScriptResolver(); - + void openResolverConfig( const QString& ); + private: Ui::SettingsDialog* ui; ProxyDialog m_proxySettings; bool m_rejected; QNetworkReply* m_testLastFmQuery; + ResolversModel* m_resolversModel; }; #endif // SETTINGSDIALOG_H diff --git a/src/settingsdialog.ui b/src/settingsdialog.ui index 0558568b6..292e7cf59 100644 --- a/src/settingsdialog.ui +++ b/src/settingsdialog.ui @@ -23,7 +23,7 @@ - 0 + 4 @@ -590,7 +590,7 @@ - + true @@ -600,40 +600,18 @@ false + + true + false true - - 2 + + true - - false - - - true - - - 150 - - - true - - - true - - - - 1 - - - - - 2 - - diff --git a/src/tomahawkapp.cpp b/src/tomahawkapp.cpp index 541068403..ea5311844 100644 --- a/src/tomahawkapp.cpp +++ b/src/tomahawkapp.cpp @@ -289,7 +289,7 @@ TomahawkApp::~TomahawkApp() qDebug() << Q_FUNC_INFO; // stop script resolvers - foreach( Tomahawk::ExternalResolver* r, m_scriptResolvers ) + foreach( Tomahawk::ExternalResolver* r, m_scriptResolvers.values() ) { delete r; } @@ -415,37 +415,41 @@ TomahawkApp::setupPipeline() Pipeline::instance()->addResolver( new DatabaseResolver( 100 ) ); // load script resolvers - foreach( QString resolver, TomahawkSettings::instance()->scriptResolvers() ) - addScriptResolver( resolver ); + foreach( QString resolver, TomahawkSettings::instance()->enabledScriptResolvers() ) + enableScriptResolver( resolver ); } void -TomahawkApp::addScriptResolver( const QString& path ) +TomahawkApp::enableScriptResolver( const QString& path ) { const QFileInfo fi( path ); if ( fi.suffix() == "js" || fi.suffix() == "script" ) - m_scriptResolvers << new QtScriptResolver( path ); + m_scriptResolvers.insert( path, new QtScriptResolver( path ) ); else - m_scriptResolvers << new ScriptResolver( path ); + m_scriptResolvers.insert( path, new ScriptResolver( path ) ); } void -TomahawkApp::removeScriptResolver( const QString& path ) +TomahawkApp::disableScriptResolver( const QString& path ) { - foreach( Tomahawk::ExternalResolver* r, m_scriptResolvers ) + if( m_scriptResolvers.contains( path ) ) { - if( r->filePath() == path ) - { - m_scriptResolvers.removeAll( r ); - connect( r, SIGNAL( finished() ), r, SLOT( deleteLater() ) ); - r->stop(); - return; - } + Tomahawk::ExternalResolver* r = m_scriptResolvers.take( path ); + + connect( r, SIGNAL( finished() ), r, SLOT( deleteLater() ) ); + r->stop(); + return; } } +Tomahawk::ExternalResolver* +TomahawkApp::resolverForPath( const QString& scriptPath ) +{ + return m_scriptResolvers.value( scriptPath, 0 ); +} + void TomahawkApp::initLocalCollection() From fe43c1dd01f1fc92f963d81913e4c4005437aa13 Mon Sep 17 00:00:00 2001 From: Leo Franchi Date: Thu, 14 Apr 2011 20:06:34 -0400 Subject: [PATCH 319/329] Add config migration --- src/libtomahawk/tomahawksettings.cpp | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/src/libtomahawk/tomahawksettings.cpp b/src/libtomahawk/tomahawksettings.cpp index 22ce4f548..f1670839f 100644 --- a/src/libtomahawk/tomahawksettings.cpp +++ b/src/libtomahawk/tomahawksettings.cpp @@ -26,7 +26,7 @@ #include #include -#define VERSION 1 +#define VERSION 2 TomahawkSettings* TomahawkSettings::s_instance = 0; @@ -55,6 +55,9 @@ TomahawkSettings::TomahawkSettings( QObject* parent ) // insert upgrade code here as required setValue( "configversion", VERSION ); + if( contains( "script/resolvers") ) { + setValue( "script/loadedresolvers", value( "script/resolvers" ) ); + } } } From ef1ded8636cb211360fc89bfa38b506358a5b213 Mon Sep 17 00:00:00 2001 From: Leo Franchi Date: Thu, 14 Apr 2011 20:11:29 -0400 Subject: [PATCH 320/329] remove useless file --- src/resolverconfigwrapper.cpp | 0 1 file changed, 0 insertions(+), 0 deletions(-) delete mode 100644 src/resolverconfigwrapper.cpp diff --git a/src/resolverconfigwrapper.cpp b/src/resolverconfigwrapper.cpp deleted file mode 100644 index e69de29bb..000000000 From ab964957d241340e09c05e89100cdc61e5b62be1 Mon Sep 17 00:00:00 2001 From: Leo Franchi Date: Thu, 14 Apr 2011 20:26:08 -0400 Subject: [PATCH 321/329] elide --- src/resolverconfigdelegate.cpp | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/resolverconfigdelegate.cpp b/src/resolverconfigdelegate.cpp index fb983c8ae..0f98e49f8 100644 --- a/src/resolverconfigdelegate.cpp +++ b/src/resolverconfigdelegate.cpp @@ -88,19 +88,19 @@ ResolverConfigDelegate::paint( QPainter* painter, const QStyleOptionViewItem& op style->drawPrimitive( QStyle::PE_IndicatorViewItemCheck, &opt, painter, w ); itemRect.setX( opt.rect.topRight().x() + PADDING ); - QString nameStr = bfm.elidedText( index.data( ResolversModel::ResolverName ).toString(),Qt::ElideRight, rightSplit ); painter->save(); painter->setFont( name ); QRect textRect = itemRect.adjusted( PADDING, PADDING, -PADDING, -PADDING ); textRect.setBottom( itemRect.height() / 2 + top ); + QString nameStr = bfm.elidedText( index.data( ResolversModel::ResolverName ).toString(),Qt::ElideRight, textRect.width() ); painter->drawText( textRect, nameStr ); painter->restore(); - QString pathStr = sfm.elidedText( index.data( ResolversModel::ResolverPath ).toString(),Qt::ElideMiddle, rightSplit ); painter->save(); painter->setFont( path ); painter->setBrush( Qt::gray ); textRect.moveTop( itemRect.height() / 2 + top ); + QString pathStr = sfm.elidedText( index.data( ResolversModel::ResolverPath ).toString(),Qt::ElideMiddle, textRect.width() ); painter->drawText( textRect, pathStr ); painter->restore(); From f16277ac5f4d3db2bad120163c571c3405f549c4 Mon Sep 17 00:00:00 2001 From: Leo Franchi Date: Thu, 14 Apr 2011 20:40:00 -0400 Subject: [PATCH 322/329] more assert fixes --- src/libtomahawk/widgets/newplaylistwidget.cpp | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/src/libtomahawk/widgets/newplaylistwidget.cpp b/src/libtomahawk/widgets/newplaylistwidget.cpp index 869bfdb3e..bc633e6a2 100644 --- a/src/libtomahawk/widgets/newplaylistwidget.cpp +++ b/src/libtomahawk/widgets/newplaylistwidget.cpp @@ -1,5 +1,5 @@ /* === This file is part of Tomahawk Player - === - * + * * Copyright 2010-2011, Christian Muehlhaeuser * * Tomahawk is free software: you can redistribute it and/or modify @@ -54,7 +54,7 @@ NewPlaylistWidget::NewPlaylistWidget( QWidget* parent ) connect( ui->buttonBox, SIGNAL( rejected() ), SLOT( cancel() ) ); m_suggestionsModel = new PlaylistModel( ui->suggestionsView ); - ui->suggestionsView->setModel( m_suggestionsModel ); + ui->suggestionsView->setPlaylistModel( m_suggestionsModel ); ui->suggestionsView->overlay()->setEnabled( false ); connect( &m_filterTimer, SIGNAL( timeout() ), SLOT( updateSuggestions() ) ); @@ -123,7 +123,7 @@ NewPlaylistWidget::suggestionsFound() delete m_suggestionsModel; m_suggestionsModel = new PlaylistModel( ui->suggestionsView ); - ui->suggestionsView->setModel( m_suggestionsModel ); + ui->suggestionsView->setPlaylistModel( m_suggestionsModel ); QList ql; foreach( const Tomahawk::plentry_ptr& entry, m_entries ) From df8a333aeeb7c836b9ccc7eb6214be0bbbf5e903 Mon Sep 17 00:00:00 2001 From: Frank Osterfeld Date: Thu, 14 Apr 2011 21:14:13 +0800 Subject: [PATCH 323/329] Convert QSet to QVector. This one possibly leaked, there is no need to use pointers here. --- src/sip/zeroconf/zeroconf.cpp | 13 +++++-------- src/sip/zeroconf/zeroconf.h | 2 +- 2 files changed, 6 insertions(+), 9 deletions(-) diff --git a/src/sip/zeroconf/zeroconf.cpp b/src/sip/zeroconf/zeroconf.cpp index 9625f9abc..7e6bbf955 100644 --- a/src/sip/zeroconf/zeroconf.cpp +++ b/src/sip/zeroconf/zeroconf.cpp @@ -49,15 +49,12 @@ ZeroconfPlugin::connectPlugin( bool /*startup*/ ) m_zeroconf->advertise(); m_isOnline = true; - foreach( QStringList *currNode, m_cachedNodes ) + foreach( const QStringList& nodeSet, m_cachedNodes ) { - QStringList nodeSet = *currNode; if ( !Servent::instance()->connectedToSession( nodeSet[3] ) ) Servent::instance()->connectToPeer( nodeSet[0], nodeSet[1].toInt(), "whitelist", nodeSet[2], nodeSet[3] ); - - delete currNode; } - + m_cachedNodes.clear(); return true; } @@ -81,9 +78,9 @@ ZeroconfPlugin::lanHostFound( const QString& host, int port, const QString& name if ( !m_isOnline ) { qDebug() << "Not online, so not connecting."; - QStringList *nodeSet = new QStringList(); - *nodeSet << host << QString::number( port ) << name << nodeid; - m_cachedNodes.insert( nodeSet ); + QStringList nodeSet; + nodeSet << host << QString::number( port ) << name << nodeid; + m_cachedNodes.append( nodeSet ); return; } diff --git a/src/sip/zeroconf/zeroconf.h b/src/sip/zeroconf/zeroconf.h index 2d971ebb0..059f4727a 100644 --- a/src/sip/zeroconf/zeroconf.h +++ b/src/sip/zeroconf/zeroconf.h @@ -78,7 +78,7 @@ private slots: private: TomahawkZeroconf* m_zeroconf; bool m_isOnline; - QSet< QStringList* > m_cachedNodes; + QVector m_cachedNodes; }; #endif From 04d30913f9e02b2da39d013e7e94af0bfa6e230e Mon Sep 17 00:00:00 2001 From: Christian Muehlhaeuser Date: Fri, 15 Apr 2011 07:35:42 +0200 Subject: [PATCH 324/329] * Show artist images in ArtistView (lazy-loading). * Added artist image api to LastFmPlugin. --- src/audiocontrols.cpp | 4 +- .../infosystem/infoplugins/lastfmplugin.cpp | 168 +++++++++++++++--- .../infosystem/infoplugins/lastfmplugin.h | 10 +- src/libtomahawk/playlist/artistview.cpp | 59 +++++- src/libtomahawk/playlist/artistview.h | 4 + src/libtomahawk/playlist/treemodel.cpp | 9 +- 6 files changed, 219 insertions(+), 35 deletions(-) diff --git a/src/audiocontrols.cpp b/src/audiocontrols.cpp index 93bfd71e7..d80efd846 100644 --- a/src/audiocontrols.cpp +++ b/src/audiocontrols.cpp @@ -28,8 +28,6 @@ #include "album.h" -#define LASTFM_DEFAULT_COVER "http://cdn.last.fm/flatness/catalogue/noimage" - static QString s_acInfoIdentifier = QString( "AUDIOCONTROLS" ); @@ -257,7 +255,7 @@ AudioControls::infoSystemInfo( QString caller, Tomahawk::InfoSystem::InfoType ty QPixmap pm; pm.loadFromData( ba ); - if ( pm.isNull() || returnedData["url"].toString().startsWith( LASTFM_DEFAULT_COVER ) ) + if ( pm.isNull() ) ui->coverImage->setPixmap( m_defaultCover ); else ui->coverImage->setPixmap( pm.scaled( ui->coverImage->size(), Qt::IgnoreAspectRatio, Qt::SmoothTransformation ) ); diff --git a/src/libtomahawk/infosystem/infoplugins/lastfmplugin.cpp b/src/libtomahawk/infosystem/infoplugins/lastfmplugin.cpp index 0cb840265..f3a8f8b49 100644 --- a/src/libtomahawk/infosystem/infoplugins/lastfmplugin.cpp +++ b/src/libtomahawk/infosystem/infoplugins/lastfmplugin.cpp @@ -47,7 +47,7 @@ LastFmPlugin::LastFmPlugin( QObject* parent ) , m_authJob( 0 ) { QSet< InfoType > supportedTypes; - supportedTypes << InfoMiscSubmitScrobble << InfoMiscSubmitNowPlaying << InfoAlbumCoverArt; + supportedTypes << InfoMiscSubmitScrobble << InfoMiscSubmitNowPlaying << InfoAlbumCoverArt << InfoArtistImages; qobject_cast< InfoSystem* >(parent)->registerInfoTypes(this, supportedTypes); /* @@ -81,6 +81,10 @@ LastFmPlugin::LastFmPlugin( QObject* parent ) } #endif + + m_badUrls << QUrl( "http://cdn.last.fm/flatness/catalogue/noimage" ) + << QUrl( "http://cdn.last.fm/flatness/catalogue/noimage/2/default_artist_medium.png" ); + connect( TomahawkSettings::instance(), SIGNAL( changed() ), SLOT( settingsChanged() ), Qt::QueuedConnection ); } @@ -91,6 +95,7 @@ LastFmPlugin::~LastFmPlugin() delete m_scrobbler; } + void LastFmPlugin::dataError( const QString &caller, const InfoType type, const QVariant& data, Tomahawk::InfoSystem::InfoCustomData &customData ) { @@ -98,20 +103,36 @@ LastFmPlugin::dataError( const QString &caller, const InfoType type, const QVari return; } + void LastFmPlugin::getInfo( const QString &caller, const InfoType type, const QVariant& data, Tomahawk::InfoSystem::InfoCustomData customData ) { qDebug() << Q_FUNC_INFO; - if ( type == InfoMiscSubmitNowPlaying ) - nowPlaying( caller, type, data, customData ); - else if ( type == InfoMiscSubmitScrobble ) - scrobble( caller, type, data, customData ); - else if ( type == InfoAlbumCoverArt ) - fetchCoverArt( caller, type, data, customData ); - else - dataError( caller, type, data, customData ); + + switch ( type ) + { + case InfoMiscSubmitNowPlaying: + nowPlaying( caller, type, data, customData ); + break; + + case InfoMiscSubmitScrobble: + scrobble( caller, type, data, customData ); + break; + + case InfoArtistImages: + fetchArtistImages( caller, type, data, customData ); + break; + + case InfoAlbumCoverArt: + fetchCoverArt( caller, type, data, customData ); + break; + + default: + dataError( caller, type, data, customData ); + } } + void LastFmPlugin::nowPlaying( const QString &caller, const InfoType type, const QVariant& data, Tomahawk::InfoSystem::InfoCustomData &customData ) { @@ -140,6 +161,7 @@ LastFmPlugin::nowPlaying( const QString &caller, const InfoType type, const QVar emit info( caller, type, data, QVariant(), customData ); } + void LastFmPlugin::scrobble( const QString &caller, const InfoType type, const QVariant& data, Tomahawk::InfoSystem::InfoCustomData &customData ) { @@ -158,6 +180,7 @@ LastFmPlugin::scrobble( const QString &caller, const InfoType type, const QVaria emit info( caller, type, data, QVariant(), customData ); } + void LastFmPlugin::fetchCoverArt( const QString &caller, const InfoType type, const QVariant& data, Tomahawk::InfoSystem::InfoCustomData &customData ) { @@ -181,30 +204,76 @@ LastFmPlugin::fetchCoverArt( const QString &caller, const InfoType type, const Q emit getCachedInfo( criteria, 2419200000, caller, type, data, customData ); } + +void +LastFmPlugin::fetchArtistImages( const QString &caller, const InfoType type, const QVariant& data, Tomahawk::InfoSystem::InfoCustomData &customData ) +{ + qDebug() << Q_FUNC_INFO; + if ( !data.canConvert< Tomahawk::InfoSystem::InfoCustomData >() ) + { + dataError( caller, type, data, customData ); + return; + } + InfoCustomData hash = data.value< Tomahawk::InfoSystem::InfoCustomData >(); + if ( !hash.contains( "artist" ) ) + { + dataError( caller, type, data, customData ); + return; + } + + Tomahawk::InfoSystem::InfoCacheCriteria criteria; + criteria["artist"] = hash["artist"].toString(); + + emit getCachedInfo( criteria, 2419200000, caller, type, data, customData ); +} + + void LastFmPlugin::notInCacheSlot( QHash criteria, QString caller, Tomahawk::InfoSystem::InfoType type, QVariant input, Tomahawk::InfoSystem::InfoCustomData customData ) { qDebug() << Q_FUNC_INFO; - if ( type == InfoAlbumCoverArt ) + + switch ( type ) { - QString artistName = criteria["artist"]; - QString albumName = criteria["album"]; + case InfoAlbumCoverArt: + { + QString artistName = criteria["artist"]; + QString albumName = criteria["album"]; - QString imgurl = "http://ws.audioscrobbler.com/2.0/?method=album.imageredirect&artist=%1&album=%2&size=medium&api_key=7a90f6672a04b809ee309af169f34b8b"; - QNetworkRequest req( imgurl.arg( artistName ).arg( albumName ) ); - QNetworkReply* reply = TomahawkUtils::nam()->get( req ); - reply->setProperty( "customData", QVariant::fromValue( customData ) ); - reply->setProperty( "origData", input ); - reply->setProperty( "caller", caller ); - reply->setProperty( "type", (uint)(type) ); + QString imgurl = "http://ws.audioscrobbler.com/2.0/?method=album.imageredirect&artist=%1&album=%2&autocorrect=1&size=medium&api_key=7a90f6672a04b809ee309af169f34b8b"; + QNetworkRequest req( imgurl.arg( artistName ).arg( albumName ) ); + QNetworkReply* reply = TomahawkUtils::nam()->get( req ); + reply->setProperty( "customData", QVariant::fromValue( customData ) ); + reply->setProperty( "origData", input ); + reply->setProperty( "caller", caller ); + reply->setProperty( "type", (uint)(type) ); - connect( reply, SIGNAL( finished() ), SLOT( coverArtReturned() ) ); - return; + connect( reply, SIGNAL( finished() ), SLOT( coverArtReturned() ) ); + return; + } + + case InfoArtistImages: + { + QString artistName = criteria["artist"]; + + QString imgurl = "http://ws.audioscrobbler.com/2.0/?method=artist.imageredirect&artist=%1&autocorrect=1&size=medium&api_key=7a90f6672a04b809ee309af169f34b8b"; + QNetworkRequest req( imgurl.arg( artistName ) ); + QNetworkReply* reply = TomahawkUtils::nam()->get( req ); + reply->setProperty( "customData", QVariant::fromValue( customData ) ); + reply->setProperty( "origData", input ); + reply->setProperty( "caller", caller ); + reply->setProperty( "type", (uint)(type) ); + + connect( reply, SIGNAL( finished() ), SLOT( artistImagesReturned() ) ); + return; + } + + default: + qDebug() << "Couldn't figure out what to do with this type of request after cache miss"; } - else - qDebug() << "Couldn't figure out what to do with this type of request after cache miss"; } + void LastFmPlugin::coverArtReturned() { @@ -213,7 +282,10 @@ LastFmPlugin::coverArtReturned() QUrl redir = reply->attribute( QNetworkRequest::RedirectionTargetAttribute ).toUrl(); if ( redir.isEmpty() ) { - const QByteArray ba = reply->readAll(); + QByteArray ba = reply->readAll(); + if ( m_badUrls.contains( reply->url() ) ) + ba = QByteArray(); + InfoCustomData returnedData; returnedData["imgbytes"] = ba; returnedData["url"] = reply->url().toString(); @@ -249,6 +321,54 @@ LastFmPlugin::coverArtReturned() reply->deleteLater(); } + +void +LastFmPlugin::artistImagesReturned() +{ + qDebug() << Q_FUNC_INFO; + QNetworkReply* reply = qobject_cast( sender() ); + QUrl redir = reply->attribute( QNetworkRequest::RedirectionTargetAttribute ).toUrl(); + if ( redir.isEmpty() ) + { + QByteArray ba = reply->readAll(); + if ( m_badUrls.contains( reply->url() ) ) + ba = QByteArray(); + + InfoCustomData returnedData; + returnedData["imgbytes"] = ba; + returnedData["url"] = reply->url().toString(); + + InfoCustomData customData = reply->property( "customData" ).value< Tomahawk::InfoSystem::InfoCustomData >(); + InfoType type = (Tomahawk::InfoSystem::InfoType)(reply->property( "type" ).toUInt()); + emit info( + reply->property( "caller" ).toString(), + type, + reply->property( "origData" ), + returnedData, + customData + ); + + InfoCustomData origData = reply->property( "origData" ).value< Tomahawk::InfoSystem::InfoCustomData >(); + Tomahawk::InfoSystem::InfoCacheCriteria criteria; + criteria["artist"] = origData["artist"].toString(); + emit updateCache( criteria, 2419200000, type, returnedData ); + } + else + { + // Follow HTTP redirect + QNetworkRequest req( redir ); + QNetworkReply* newReply = TomahawkUtils::nam()->get( req ); + newReply->setProperty( "origData", reply->property( "origData" ) ); + newReply->setProperty( "customData", reply->property( "customData" ) ); + newReply->setProperty( "caller", reply->property( "caller" ) ); + newReply->setProperty( "type", reply->property( "type" ) ); + connect( newReply, SIGNAL( finished() ), SLOT( artistImagesReturned() ) ); + } + + reply->deleteLater(); +} + + void LastFmPlugin::settingsChanged() { diff --git a/src/libtomahawk/infosystem/infoplugins/lastfmplugin.h b/src/libtomahawk/infosystem/infoplugins/lastfmplugin.h index 00b27b15e..1b97b0e0c 100644 --- a/src/libtomahawk/infosystem/infoplugins/lastfmplugin.h +++ b/src/libtomahawk/infosystem/infoplugins/lastfmplugin.h @@ -47,21 +47,29 @@ public: public slots: void settingsChanged(); + void onAuthenticated(); void coverArtReturned(); + void artistImagesReturned(); + virtual void notInCacheSlot( Tomahawk::InfoSystem::InfoCacheCriteria criteria, QString caller, Tomahawk::InfoSystem::InfoType type, QVariant input, Tomahawk::InfoSystem::InfoCustomData customData ); private: void fetchCoverArt( const QString &caller, const InfoType type, const QVariant& data, Tomahawk::InfoSystem::InfoCustomData &customData ); - void scrobble( const QString &caller, const InfoType type, const QVariant& data, InfoCustomData &customData ); + void fetchArtistImages( const QString &caller, const InfoType type, const QVariant& data, Tomahawk::InfoSystem::InfoCustomData &customData ); + void createScrobbler(); + void scrobble( const QString &caller, const InfoType type, const QVariant& data, InfoCustomData &customData ); void nowPlaying( const QString &caller, const InfoType type, const QVariant& data, InfoCustomData &customData ); + void dataError( const QString &caller, const InfoType type, const QVariant& data, InfoCustomData &customData ); lastfm::MutableTrack m_track; lastfm::Audioscrobbler* m_scrobbler; QString m_pw; + QList< QUrl > m_badUrls; + QNetworkReply* m_authJob; }; diff --git a/src/libtomahawk/playlist/artistview.cpp b/src/libtomahawk/playlist/artistview.cpp index 2c8412057..126f99ecd 100644 --- a/src/libtomahawk/playlist/artistview.cpp +++ b/src/libtomahawk/playlist/artistview.cpp @@ -31,14 +31,18 @@ #include "treeitemdelegate.h" #include "playlistmanager.h" +static QString s_tmInfoIdentifier = QString( "TREEMODEL" ); + +#define SCROLL_TIMEOUT 280 + using namespace Tomahawk; ArtistView::ArtistView( QWidget* parent ) : QTreeView( parent ) + , m_header( new TreeHeader( this ) ) , m_model( 0 ) , m_proxyModel( 0 ) - , m_header( new TreeHeader( this ) ) // , m_delegate( 0 ) { setAlternatingRowColors( true ); @@ -67,6 +71,12 @@ ArtistView::ArtistView( QWidget* parent ) setFont( f ); #endif + m_timer.setInterval( SCROLL_TIMEOUT ); + + connect( verticalScrollBar(), SIGNAL( rangeChanged( int, int ) ), SLOT( onViewChanged() ) ); + connect( verticalScrollBar(), SIGNAL( valueChanged( int ) ), SLOT( onViewChanged() ) ); + connect( &m_timer, SIGNAL( timeout() ), SLOT( onScrollTimeout() ) ); + connect( this, SIGNAL( doubleClicked( QModelIndex ) ), SLOT( onItemActivated( QModelIndex ) ) ); } @@ -168,12 +178,57 @@ ArtistView::onFilterChanged( const QString& ) void ArtistView::startDrag( Qt::DropActions supportedActions ) { + Q_UNUSED( supportedActions ); } -// Inspired from dolphin's draganddrophelper.cpp QPixmap ArtistView::createDragPixmap( int itemCount ) const { + Q_UNUSED( itemCount ); return QPixmap(); } + + +void +ArtistView::onViewChanged() +{ + if ( m_timer.isActive() ) + m_timer.stop(); + + m_timer.start(); +} + + +void +ArtistView::onScrollTimeout() +{ + qDebug() << Q_FUNC_INFO; + if ( m_timer.isActive() ) + m_timer.stop(); + + QModelIndex left = indexAt( viewport()->rect().topLeft() ); + while ( left.isValid() && left.parent().isValid() ) + left = left.parent(); + + QModelIndex right = indexAt( viewport()->rect().bottomLeft() ); + while ( right.isValid() && right.parent().isValid() ) + right = right.parent(); + + int max = m_proxyModel->trackCount(); + if ( right.isValid() ) + max = right.row() + 1; + + for ( int i = left.row(); i < max; i++ ) + { + TreeModelItem* item = m_model->itemFromIndex( m_proxyModel->mapToSource( m_proxyModel->index( i, 0 ) ) ); + + Tomahawk::InfoSystem::InfoCustomData trackInfo; + trackInfo["artist"] = QVariant::fromValue< QString >( item->artist()->name() ); + trackInfo["pptr"] = QVariant::fromValue< qlonglong >( (qlonglong)item ); + + Tomahawk::InfoSystem::InfoSystem::instance()->getInfo( + s_tmInfoIdentifier, Tomahawk::InfoSystem::InfoArtistImages, + QVariant::fromValue< Tomahawk::InfoSystem::InfoCustomData >( trackInfo ), Tomahawk::InfoSystem::InfoCustomData() ); + } +} diff --git a/src/libtomahawk/playlist/artistview.h b/src/libtomahawk/playlist/artistview.h index 6f9b94b1f..e02107cbb 100644 --- a/src/libtomahawk/playlist/artistview.h +++ b/src/libtomahawk/playlist/artistview.h @@ -73,6 +73,8 @@ protected: private slots: void onFilterChanged( const QString& filter ); + void onViewChanged(); + void onScrollTimeout(); private: QPixmap createDragPixmap( int itemCount ) const; @@ -81,6 +83,8 @@ private: TreeModel* m_model; TreeProxyModel* m_proxyModel; // PlaylistItemDelegate* m_delegate; + + QTimer m_timer; }; #endif // ARTISTVIEW_H diff --git a/src/libtomahawk/playlist/treemodel.cpp b/src/libtomahawk/playlist/treemodel.cpp index 247735999..7d7974974 100644 --- a/src/libtomahawk/playlist/treemodel.cpp +++ b/src/libtomahawk/playlist/treemodel.cpp @@ -28,8 +28,6 @@ #include "database/database.h" #include "utils/tomahawkutils.h" -#define LASTFM_DEFAULT_COVER "http://cdn.last.fm/flatness/catalogue/noimage" - static QString s_tmInfoIdentifier = QString( "TREEMODEL" ); using namespace Tomahawk; @@ -369,6 +367,7 @@ TreeModel::addTracks( const album_ptr& album, const QModelIndex& parent ) DatabaseCommand_AllTracks* cmd = new DatabaseCommand_AllTracks( m_collection ); cmd->setAlbum( album.data() ); +// cmd->setArtist( album->artist().data() ); QList< QVariant > rows; rows << parent.row(); @@ -442,7 +441,6 @@ TreeModel::onArtistsAdded( const QList& artists ) artistitem = new TreeModelItem( artist, m_rootItem ); artistitem->cover = m_defaultCover; artistitem->index = createIndex( m_rootItem->children.count() - 1, 0, artistitem ); - connect( artistitem, SIGNAL( dataChanged() ), SLOT( onDataChanged() ) ); } @@ -544,7 +542,8 @@ TreeModel::infoSystemInfo( QString caller, Tomahawk::InfoSystem::InfoType type, Q_UNUSED( customData ); qDebug() << Q_FUNC_INFO; - if ( caller != s_tmInfoIdentifier || type != Tomahawk::InfoSystem::InfoAlbumCoverArt ) + if ( caller != s_tmInfoIdentifier || + ( type != Tomahawk::InfoSystem::InfoAlbumCoverArt && type != Tomahawk::InfoSystem::InfoArtistImages ) ) { qDebug() << "Info of wrong type or not with our identifier"; return; @@ -567,7 +566,7 @@ TreeModel::infoSystemInfo( QString caller, Tomahawk::InfoSystem::InfoType type, qlonglong p = pptr["pptr"].toLongLong(); TreeModelItem* ai = reinterpret_cast(p); - if ( pm.isNull() || returnedData["url"].toString().startsWith( LASTFM_DEFAULT_COVER ) ) + if ( pm.isNull() ) ai->cover = m_defaultCover; else ai->cover = pm; From 9af6ff7911474490631016810bd87f6d53b4dec0 Mon Sep 17 00:00:00 2001 From: Frank Osterfeld Date: Wed, 13 Apr 2011 04:51:49 +0800 Subject: [PATCH 325/329] Do not crash when the collection folders contains cycles (symlinks). Example: /my/collection/subfolder/link -> /my/collection Use canonicalFilePath to detect cycles and also check m_newdirtimes for an existing timestamp before recursing into subfolders. --- src/musicscanner.cpp | 37 ++++++++++++++++++++----------------- src/musicscanner.h | 2 +- 2 files changed, 21 insertions(+), 18 deletions(-) diff --git a/src/musicscanner.cpp b/src/musicscanner.cpp index 601d89288..7c38d37af 100644 --- a/src/musicscanner.cpp +++ b/src/musicscanner.cpp @@ -65,7 +65,7 @@ DirLister::go() void DirLister::scanDir( QDir dir, int depth, DirLister::Mode mode ) { - qDebug() << "DirLister::scanDir scanning: " << dir.absolutePath() << " with mode " << mode; + qDebug() << "DirLister::scanDir scanning: " << dir.canonicalPath() << " with mode " << mode; if( !dir.exists() ) { @@ -74,16 +74,16 @@ DirLister::scanDir( QDir dir, int depth, DirLister::Mode mode ) } QFileInfoList dirs; - const uint mtime = QFileInfo( dir.absolutePath() ).lastModified().toUTC().toTime_t(); - m_newdirmtimes.insert( dir.absolutePath(), mtime ); + const uint mtime = QFileInfo( dir.canonicalPath() ).lastModified().toUTC().toTime_t(); + m_newdirmtimes.insert( dir.canonicalPath(), mtime ); - if ( m_dirmtimes.contains( dir.absolutePath() ) && mtime == m_dirmtimes.value( dir.absolutePath() ) ) + if ( m_dirmtimes.contains( dir.canonicalPath() ) && mtime == m_dirmtimes.value( dir.canonicalPath() ) ) { // dont scan this dir, unchanged since last time. } else { - if( m_dirmtimes.contains( dir.absolutePath() ) || !m_recursive ) + if( m_dirmtimes.contains( dir.canonicalPath() ) || !m_recursive ) Database::instance()->enqueue( QSharedPointer( new DatabaseCommand_DeleteFiles( dir, SourceList::instance()->getLocal() ) ) ); dir.setFilter( QDir::Files | QDir::Readable | QDir::NoDotAndDotDot ); @@ -99,10 +99,13 @@ DirLister::scanDir( QDir dir, int depth, DirLister::Mode mode ) foreach( const QFileInfo& di, dirs ) { - qDebug() << "Considering dir " << di.absoluteFilePath(); - qDebug() << "m_dirmtimes contains it? " << (m_dirmtimes.contains( di.absoluteFilePath() ) ? "true" : "false"); - if( mode == DirLister::Recursive || !m_dirmtimes.contains( di.absoluteFilePath() ) ) - scanDir( di.absoluteFilePath(), depth + 1, DirLister::Recursive ); + const QString canonical = di.canonicalFilePath(); + qDebug() << "Considering dir " << canonical; + const bool haveDi = m_dirmtimes.contains( canonical ); + qDebug() << "m_dirmtimes contains it?" << haveDi; + if( !m_newdirmtimes.contains( canonical ) && ( mode == DirLister::Recursive || !haveDi ) ) { + scanDir( di.canonicalFilePath(), depth + 1, DirLister::Recursive ); + } } } @@ -317,20 +320,20 @@ MusicScanner::readFile( const QFileInfo& fi ) if( m_scanned % 3 == 0 ) SourceList::instance()->getLocal()->scanningProgress( m_scanned ); if( m_scanned % 100 == 0 ) - qDebug() << "SCAN" << m_scanned << fi.absoluteFilePath(); + qDebug() << "SCAN" << m_scanned << fi.canonicalFilePath(); #ifdef COMPLEX_TAGLIB_FILENAME - const wchar_t *encodedName = reinterpret_cast< const wchar_t * >( fi.absoluteFilePath().utf16() ); + const wchar_t *encodedName = reinterpret_cast< const wchar_t * >( fi.canonicalFilePath().utf16() ); #else - QByteArray fileName = QFile::encodeName( fi.absoluteFilePath() ); + QByteArray fileName = QFile::encodeName( fi.canonicalFilePath() ); const char *encodedName = fileName.constData(); #endif TagLib::FileRef f( encodedName ); if ( f.isNull() || !f.tag() ) { - // qDebug() << "Doesn't seem to be a valid audiofile:" << fi.absoluteFilePath(); - m_skippedFiles << fi.absoluteFilePath(); + // qDebug() << "Doesn't seem to be a valid audiofile:" << fi.canonicalFilePath(); + m_skippedFiles << fi.canonicalFilePath(); m_skipped++; return QVariantMap(); } @@ -351,8 +354,8 @@ MusicScanner::readFile( const QFileInfo& fi ) if ( artist.isEmpty() || track.isEmpty() ) { // FIXME: do some clever filename guessing - // qDebug() << "No tags found, skipping" << fi.absoluteFilePath(); - m_skippedFiles << fi.absoluteFilePath(); + // qDebug() << "No tags found, skipping" << fi.canonicalFilePath(); + m_skippedFiles << fi.canonicalFilePath(); m_skipped++; return QVariantMap(); } @@ -361,7 +364,7 @@ MusicScanner::readFile( const QFileInfo& fi ) QString url( "file://%1" ); QVariantMap m; - m["url"] = url.arg( fi.absoluteFilePath() ); + m["url"] = url.arg( fi.canonicalFilePath() ); m["mtime"] = fi.lastModified().toUTC().toTime_t(); m["size"] = (unsigned int)fi.size(); m["mimetype"] = mimetype; diff --git a/src/musicscanner.h b/src/musicscanner.h index 77fb62071..0944c2f90 100644 --- a/src/musicscanner.h +++ b/src/musicscanner.h @@ -46,7 +46,7 @@ public: MTimeOnly }; - DirLister( QStringList dirs, QMap& mtimes, bool recursive ) + DirLister( const QStringList& dirs, const QMap& mtimes, bool recursive ) : QObject(), m_dirs( dirs ), m_dirmtimes( mtimes ), m_recursive( recursive ) { qDebug() << Q_FUNC_INFO; From 66e3d07aee1ef0145191b170a2d947efa553ca31 Mon Sep 17 00:00:00 2001 From: Christian Muehlhaeuser Date: Fri, 15 Apr 2011 07:50:38 +0200 Subject: [PATCH 326/329] * Use canonicalPath() wherever dealing with file- & dir-paths. --- .../database/databasecommand_deletefiles.cpp | 16 ++++++++-------- .../database/databasecommand_dirmtimes.cpp | 4 ++-- 2 files changed, 10 insertions(+), 10 deletions(-) diff --git a/src/libtomahawk/database/databasecommand_deletefiles.cpp b/src/libtomahawk/database/databasecommand_deletefiles.cpp index 503d3887b..f3675369f 100644 --- a/src/libtomahawk/database/databasecommand_deletefiles.cpp +++ b/src/libtomahawk/database/databasecommand_deletefiles.cpp @@ -1,5 +1,5 @@ /* === This file is part of Tomahawk Player - === - * + * * Copyright 2010-2011, Christian Muehlhaeuser * * Tomahawk is free software: you can redistribute it and/or modify @@ -78,18 +78,18 @@ DatabaseCommand_DeleteFiles::exec( DatabaseImpl* dbi ) delquery.prepare( QString( "DELETE FROM file WHERE source %1 AND id = ?" ) .arg( source()->isLocal() ? "IS NULL" : QString( "= %1" ).arg( source()->id() ) ) ); - dirquery.bindValue( 0, "file://" + m_dir.absolutePath() + "/%" ); + dirquery.bindValue( 0, "file://" + m_dir.canonicalPath() + "/%" ); dirquery.exec(); while ( dirquery.next() ) { QFileInfo fi( dirquery.value( 1 ).toString().mid( 7 ) ); // remove file:// - if ( fi.absolutePath() != m_dir.absolutePath() ) + if ( fi.canonicalPath() != m_dir.canonicalPath() ) { - if ( lastPath != fi.absolutePath() ) - qDebug() << "Skipping subdir:" << fi.absolutePath(); + if ( lastPath != fi.canonicalPath() ) + qDebug() << "Skipping subdir:" << fi.canonicalPath(); - lastPath = fi.absolutePath(); + lastPath = fi.canonicalPath(); continue; } @@ -133,11 +133,11 @@ DatabaseCommand_DeleteFiles::exec( DatabaseImpl* dbi ) << delquery.boundValues(); continue; } - + deleted++; } } - + qDebug() << "Deleted" << deleted << m_ids << m_files; emit done( m_files, source()->collection() ); diff --git a/src/libtomahawk/database/databasecommand_dirmtimes.cpp b/src/libtomahawk/database/databasecommand_dirmtimes.cpp index 07fbad637..c0ef4ffc5 100644 --- a/src/libtomahawk/database/databasecommand_dirmtimes.cpp +++ b/src/libtomahawk/database/databasecommand_dirmtimes.cpp @@ -1,5 +1,5 @@ /* === This file is part of Tomahawk Player - === - * + * * Copyright 2010-2011, Christian Muehlhaeuser * * Tomahawk is free software: you can redistribute it and/or modify @@ -60,7 +60,7 @@ DatabaseCommand_DirMtimes::execSelectPath( DatabaseImpl *dbi, const QDir& path, "FROM dirs_scanned " "WHERE name LIKE :prefix" ) ); - query.bindValue( ":prefix", path.absolutePath() + "%" ); + query.bindValue( ":prefix", path.canonicalPath() + "%" ); query.exec(); while( query.next() ) From abd5c3c567d59a9bbd6b13486ba336e4714f92b9 Mon Sep 17 00:00:00 2001 From: Christian Muehlhaeuser Date: Fri, 15 Apr 2011 09:09:20 +0200 Subject: [PATCH 327/329] * Fixed bad url detection in last.fm plugin. --- .../infosystem/infoplugins/lastfmplugin.cpp | 10 ++++++---- 1 file changed, 6 insertions(+), 4 deletions(-) diff --git a/src/libtomahawk/infosystem/infoplugins/lastfmplugin.cpp b/src/libtomahawk/infosystem/infoplugins/lastfmplugin.cpp index f3a8f8b49..9b96164a4 100644 --- a/src/libtomahawk/infosystem/infoplugins/lastfmplugin.cpp +++ b/src/libtomahawk/infosystem/infoplugins/lastfmplugin.cpp @@ -82,8 +82,7 @@ LastFmPlugin::LastFmPlugin( QObject* parent ) #endif - m_badUrls << QUrl( "http://cdn.last.fm/flatness/catalogue/noimage" ) - << QUrl( "http://cdn.last.fm/flatness/catalogue/noimage/2/default_artist_medium.png" ); + m_badUrls << QUrl( "http://cdn.last.fm/flatness/catalogue/noimage" ); connect( TomahawkSettings::instance(), SIGNAL( changed() ), SLOT( settingsChanged() ), Qt::QueuedConnection ); @@ -283,8 +282,11 @@ LastFmPlugin::coverArtReturned() if ( redir.isEmpty() ) { QByteArray ba = reply->readAll(); - if ( m_badUrls.contains( reply->url() ) ) - ba = QByteArray(); + foreach ( const QUrl& url, m_badUrls ) + { + if ( reply->url().toString().startsWith( url.toString() ) ) + ba = QByteArray(); + } InfoCustomData returnedData; returnedData["imgbytes"] = ba; From 5e38fc572549e080e7fec4725dc7ffcfa57c16ad Mon Sep 17 00:00:00 2001 From: Christian Muehlhaeuser Date: Fri, 15 Apr 2011 09:15:45 +0200 Subject: [PATCH 328/329] * Updated ChangeLog. --- ChangeLog | 1 + 1 file changed, 1 insertion(+) diff --git a/ChangeLog b/ChangeLog index 3a6c32a6c..f33fe5ebf 100644 --- a/ChangeLog +++ b/ChangeLog @@ -1,4 +1,5 @@ Version 0.1.0: + * You can now browse and play collections in a tree-view. * Watch folders for changes and automatically update your collection. This is on by default; you can turn it off on the Local Music tab in the settings dialog. Note that this triggers only on files or folders being From aad9dd4d759218a236a798a87505eca0856abb9b Mon Sep 17 00:00:00 2001 From: Christian Muehlhaeuser Date: Fri, 15 Apr 2011 10:25:32 +0200 Subject: [PATCH 329/329] * Fixed rejecting default covers for artist images, too. --- src/libtomahawk/infosystem/infoplugins/lastfmplugin.cpp | 8 +++++--- src/settingsdialog.cpp | 2 +- 2 files changed, 6 insertions(+), 4 deletions(-) diff --git a/src/libtomahawk/infosystem/infoplugins/lastfmplugin.cpp b/src/libtomahawk/infosystem/infoplugins/lastfmplugin.cpp index 9b96164a4..407031f1e 100644 --- a/src/libtomahawk/infosystem/infoplugins/lastfmplugin.cpp +++ b/src/libtomahawk/infosystem/infoplugins/lastfmplugin.cpp @@ -81,7 +81,6 @@ LastFmPlugin::LastFmPlugin( QObject* parent ) } #endif - m_badUrls << QUrl( "http://cdn.last.fm/flatness/catalogue/noimage" ); connect( TomahawkSettings::instance(), SIGNAL( changed() ), @@ -333,8 +332,11 @@ LastFmPlugin::artistImagesReturned() if ( redir.isEmpty() ) { QByteArray ba = reply->readAll(); - if ( m_badUrls.contains( reply->url() ) ) - ba = QByteArray(); + foreach ( const QUrl& url, m_badUrls ) + { + if ( reply->url().toString().startsWith( url.toString() ) ) + ba = QByteArray(); + } InfoCustomData returnedData; returnedData["imgbytes"] = ba; diff --git a/src/settingsdialog.cpp b/src/settingsdialog.cpp index 966f193d1..ca481749c 100644 --- a/src/settingsdialog.cpp +++ b/src/settingsdialog.cpp @@ -105,7 +105,7 @@ SettingsDialog::SettingsDialog( QWidget *parent ) ResolverConfigDelegate* del = new ResolverConfigDelegate( this ); connect( del, SIGNAL( openConfig( QString ) ), this, SLOT( openResolverConfig( QString ) ) ); ui->scriptList->setItemDelegate( del ); - m_resolversModel = new ResolversModel( s->allScriptResolvers(), s->enabledScriptResolvers(), this ); + m_resolversModel = new ResolversModel( s->allScriptResolvers(), s->enabledScriptResolvers(), this ); ui->scriptList->setModel( m_resolversModel ); connect( ui->scriptList->selectionModel(), SIGNAL( selectionChanged( QItemSelection,QItemSelection ) ), this, SLOT( scriptSelectionChanged() ) );

    ;x{S5M z-VR#fcA&Laxf8Ep+=)xd99)g^Vs5p3MYuzgyS@wnidZ6R@s{`J@5N4^b~!7pn-plDN&i3vZZ{2_ilYRFk@4#eDM;A zaVWovoc7MZAVGSeE4a*H-TkJH(dGKJlCflEe$V+lq3T;uc{$r$rT$1EjE386kx~@W zVN6;8Cun?M@EDio`^`!`Trpw>@yFBnhUzSS{zr@F9+bl;BY{LK$KJKoP>C(3*`p6o z&*fD^%kQJ*SUk1C$GoAkFt!p3zcvHXju`{afofAkD5_y_On(xa`SEfy5iv17FZ*Jv zG>iLNzqBm8`@k#IkV<63esTt4>=iy2WK=N$FfulK#^dhAfu;4q@kg+Gi{}&&vg=3G{tDhn93AqMa)UfN&TY_xtdp2PUwvLq=%X+wH5o1qo`1{fu_f zQ;0~2)eQ`kKg3((AOAz2-os@;_b{B7IK~$I8)8QaQ5@R1=ZJoLgLS~$--xKvb}QcP zzz1fA0xk@tYumkolw|l4zfst67&Nzmh!n;N7Wkb6cfq49wb@w7kP96MX0Nj`IfNZv zWm}s#jiYd`e?HkFFtU0Cn&&OME`8bB-L17-5q@jyp7l3wFW=%buFVrWBtYc5?T1kL3Y6ya|J|0HeVcBtZX^BN2-*O^@$MaE)&ed_WxNqKpaJP~B zWh2tmwT~UouKc0eiMkw=Pl^gH<0C+%jh2_)WdVgX zFgvd{pgm!GRM#1wx-n$%`GWgDFu!kxM5xF(v9bdDXhBN8x5qMXck&ce2uSA8$cVxz zUY`lKwatN*optxNGr^igpAu$?|A^U-zk6S>=KxhwkP@XUkh6yvGY|V@PEpTa-*z>p z>pjL#Pzwm_U+pxCxe~o^+dCpIhieHn#a4U92ODp#bpx3Cx;c@NrSYFH?p)exx8wV? zXbonjXQ?Rf(xRNZEsLnP6XhAVfZ`Kd0-U80Lg>T>?)k7BYVjB9hV?VXNB$$ zY;<5+5fRL)kJ!mrA-yxRRC(>~@pXLsgtR^w+z_Dfkf>;KaD_N^V646M4!{q9JCigR z)cfW1xb?*8biLar;;l8Sf5S1}9l0G(o3dW8qOYte36+={g)r3lHqTn`M&VaNs?@}9 zl`NllzN)l$yD9*Y$)GB2piucoTqM>>G4@S$`G*4vxO!atj_J^0$*t(|s+CMPSJjuh z!1KkOSiKN8|16`VzZhWZI^D^a==|(Uoq=y)84>_kwy>f0h7wD*X=tH2zTv|6e0WOY zG{otu9T$JEt1EKBkr^?#N#&Fy4@u#zfCKS{*9}biYT6+Sjt;oB9H}rzNA9wZRXY!B zFX9zntlvsoSwVmZFQuRZsOY$FH^eM=|}Cm47%?N%LWAC%ZwU2DAiWSeEtqfv&!RIKIfz^IUxXG6cK@ zVp*I99HNKp3$OAK%#if6O7BUYufpbU=Q}&YBF0&zKK;l@P=x987Fxw-WO(i?N-|eL zn)x4OXbb3jlX|9vriXsZAH8|3vJ>yBd~@~2BTdEc!T42M?8wmcrTzUq^SiOqMIV0{14BxQ0f$IuB)Ske`o_sAs?O7-PXkRQ zje>{Tn^Q5!_S|NfoZ4@p7t~wQ^>QpsK^y~Hlk}gFgxH7%h@%KDliuPqjmu7$!N#Y6 z!c*1Md?#y;qw@y^Yv=hrx?NIy8E1-$n%#AE4^5QppEes-k9f=!1FeBXcHO3vjt9YS zw+>KCewgX`=YERig$emm&I(+rB&L38b6MevFGn-80(fDvM+<&kM@}2c>QHq;LB5K$ zApBILi|YzXQ762l>9F2y5S3PiXYAbE9R5j!it2Mu7Y0%HJ|Og;W)M#(4?Pn;@}CC0 zd`oE*ppjGzo8L5XQV@r+XHYU%F_1;3UsA8e#YG=e%c{>$-V~PYbtH{vNI4oL?ib>H z55BfDxHZBRiJ6J{tUDPr{Ik_(BsW1P-~_9W#4* zep&|Z%Hc|D<2aqGa0VP+yp!E9_jc;TG(n_9OHO=jrmUA76E^I(_EZh#ca0z;#c-xr z0yxmA2+`Efg)lnMV>6>(CRgno6XVofOk-Z-V&Jun@_c&<%5jqVa{#=CQ9^>ID^9&q zayAQ>?2ZJg>Rq1T>tV~^srv*L%RwtCp*Bw-CJgJlb>GFe-v1QZKT45-VN>E?9vg6)bw1j%q^BNXS zDa5mwV4cxr)Aa@#S^-5HsbQm_p!hq_7#{BF<$uECmw*qQnA+G)VqM!fon$<2^MFi8 zTnPGJ)3cOP+~&6>F1C;;hJPIzQtaL4@!M!d=#6E@W~ZWelfglTe8HXEG%;7e--WUC z968ha9UCS8&YC&hc(!f{*yt0ZL=@|iRv(rlGtQSFi2w%{OBSFTf{sCzgVbtn!FY(m zW9z#aM9TD-4iHsD*FSbX;`0Vx?1^S)`(;%GSg+39zkS{&!V;WNS5)vUjWPSrivJd1 zE-_s`uX?aV(&_im^ZcU@%p4sS=s3e(^?5i#2x1ozNn2jldtC8>`qLU@%~KCCaIq_J z5h;o)1I_8(0fQl0hy02;8y{`X_f}k!f0oUlEt$2d?fcj^Pa36ciUp7; zW*aVYxfHy(&`N5(NZm#I38>Tdcc34LI5{>tiS_KbZ)W&rKFMaTlqKn^#j+i;zwoo{ zyGB+>ZV#K&*Qqm)6?C5XTZ{f2bSayntipzFa*w~?Pn-mklhgVShX{YcRNhSoX5d?y z_GV&4&onL~Oc?8svYKdH2 z`~+BHaRaZTdMQ!^Wo4W{fBtA_X_=&ua1#u8SAd>xoG->hW~G z>U5_N>DyjyM3cJ~IwmJaUPDJv=|1DNuX;eQnu=#?Qn?Zh`JO?Wfq-37-bXN+l%_r* zmA=?@YIRkfM!SFN#A$N|k(mWg0dvgvQ9jCGsTsZ!n`09j4{Wbe3ber7@E}X~4SN*o zAL`E(#F#>mg{2>4r}(N!=>newzL3F@ur2=8DpR~!K_to>_=Ys>WZ?UB)uhJ(Y?7pl z3qPpDDy2#9N#r^oKFXff!|>GtJN29piih<|=V{JnE0M%u+oCczC&%C3`#`tcpxslR zRm8XldD%|j>gq}mI{|Tq@5`?SO@G?{BXU=kpM_SoYqjw}sDR6K0L!lJOqMPdXge?s zOmx7_q%%OlNk@CvctjPbsvAVIj*#uX{(!n6AL-WU({rs-s>5f&Gg6g27{nW{lb)pT z^*vMlRYhI>gO&BM%rr7b5>nEL|KL&IHs6GER1}Rbl%C?l1>w;lFbAHVb@si1xiH{E z=X+`Xaps~dXekNGkc(C`SW43>#;iGxh@ z2F2PTaP&|IGZ*AnSN|w@pF|MRuU%^%i7l!Gu`}Ln^n+U5_aNq(^~$i-g+UPm5mKKyQBm?W+|X|qbqLi$ComIcVXwob2sV- zr0dHKE?5A#^X+LTEk)Rc<+Xs_R)*Uud4-n)QNoT$3F2!J#%jd+Aoo!EcVc+LEdk{# zby=`h*5ps=BW9$^7L)1fj0q&FZ(&7wy(+q10{Sabh+<%25ieUFElgC33k#+0J%@Ei zq0<)q{3bm?s$iBGjCaLW$N4QK8PYE7lr{&9c;R@p<+19?I>lcmUS7QgI+9ruP11WH z_#%kM?3)Q?2x7ExCt~{jB~wROAW=XAbAYX|rhuEC{Pqh4F)^|2QUf*!Ha|3SFjB!( z(6{O2_?zJZGD_U{yjvYs!QQq$7pM(#V= z0vCL1BKn_2N~{)zA=^o+?|8B-l8H1vs)S}_%~YTK;(k88H&;C8P2n&2vDfdR#4^gr zD}f|k?D4A53Lh<46Qo&+Sm?kf%SBMIvi9!11upiHPpa@EpW8jU9E^^8_J=bfl4y&n z*JTz$eUXCu+iY3SHFx1vQFKlP$W6x1I2kck3CQ;M9uf(?gzpeT(z) z2Hq#}rV=D9EG&PPpS?$sNV$APfTqVm+9H1WnaO|s2>V1`O@C;<bF{K_OrdWyUXUfCjvU&p;J?kgOkcBnYgOd>1q zTR2gARj-JSj(ni8%!v%>?{b|{d-~WSlCAKg`|wf))pAhHt{QW`r&eKrdy1rQ<5^zO zNL?LeRnlcVb!|dJKXepXAohBKioLh#|FJ*oG^tL4Y?qs2E%`7I^^{42dt=dXbkxGnyJ3-PXY3C87PHoTEpPtX>tdE-xZ1h}aH38h~xaNKHpG%D8s_5lUlTEkA5I(&o z?dtkA+Zq)9m61_iGWh%8HpSF;Ot7Vu{dU2;0i)QP08Zb#k$){?rJ7~Mm6g=k2WwH` z4ky%6uTz}8z4GCmwRvkd*%Xbz`iE!N8^@BN$9@=10(Gue8x;Rq>+^gSPQ&co%KX=m zRIF09OjCDKjD$X+KBAtENBXQuqMl|~9-x~14?6YXf#5rUT^c*nh{s=I*}WuoT3;?ZwX2N~wzs$c zOFRFY*GvjjQFU1jkq>wG5~Z%GsR0Nnbq)3ETB7YQelhg&^eq#I$DQ3dRur$_wvQ)L zEj4Y=>1x+}Mh9d|^k(^MfCnSfciQ1ueUDW03k40L`=w&h+ZUNA+Y#Yd0d82C3bGwh z8C5g_C!z{E_!?AbmH7-7QTXX6r^=7u#_c)%^RX)?2+voXaLufaD~{7J*r^%eKheFr zyQ36#4jmpI?u%IFAQi7l`te`6J}b$n>3(02xD3D!yQX;z^z}VVlHrd)^?CG7QF|K@ zBk`PU?{ksX_VWA?fIepcKkbY$F@bG4t=I{WlatHjsVy!op1CjY*S^}*Aw{DMM(QP7 zej;v$GdUgoi2Z*B9=$0;-178LH%P6`f?Yd`jZv9KyA=B zd`&#wSbXIMs&_dtxefN52%Peeo7@4LjKmX(85dweQ?3bv)Vf__DeKz=`L6RlGif=scl5u=&XJGKa z67%$o&SAipMf3Dej);Whd?(HRyu#i)!u#PGn^9ZD--{o%WLc!aBab03oT1!i!1+@< zGLu?DIpo6^gspX5P*QK$TbbH8z{;)sjKWS^-EA2+7zfaPNoA$^_He=*3`HD7%!Lq^ zO>YG>e4zkLhCua9nw;e|nlFhj z5O?**3+yp&I9wR0%Jhu<(%SfSTw=L*{^c2fchp*ydP7e)YZ5b=o|q0mxpJ8mqxC$m z{JDI(G7|&>3G6k7<~V%6{mR@#Dc$;#qZ%sFerb{KCvw~b*xG(f&NeQ190dlqhQR~M z3@EI(XhR$)f<9&)RJyaSU_=o=Ffk04Zzy7RqMJTY>ldG#$Lx?v>=~ z0vfN~Jxdr!X_dwdB$~S(E&YzD>gYSLuL285#2AV{zPD@HV}$15;YvJmN!$&oSXx<; zD;2LI93x%pc*_NN#iwWi;DjS7qOy{ME>`~gvrRP}t^RaKKJ~Oq1^`S>@0V`K^C^AQ*m4=V|tHaM8YiMQz&(T@_)k&eLi6s^eDMFr@59&Wm*A?Z% z_lidqAuRty(D`q`z0U@(hClo$rnf=XELsb;)+?=vp~zN7o2;-(CB_YoV%Sh=d6iv$ z%b`(rl~?SOq>=+S#j$?UyAZupne;ni+{)k}+q#;lG5Q}e?v1W^Wa7l+WQ_Pt@vGz<&sUvXQD8r<9v=)A-B zx(2A^dTxk@iR!MH;PZdK+fn^oA!evXI5Qo>iAFk!VFX)mjVz{ah}0EPt+B~2Kgkk? zVt}F7CqQC;=*CLPclYzcE>-OT_cfBrf~`1t@vUE7d(hj5%uh&H@6#j5lo7Nk8x14m z1fvkjE$Io2m&RfUVZ~J=r%gNJl$LXa2$t30XL7X_XUFwr^auY z{z#|YfU~6%_(p&g)n_sZ76TO-V^J(jIY$AZkhA+G`|59ZP8^+&PNk)#?)xQ0udMIG z5LDhn4)|cbHRYAv9Dkb|j`i*R-fnnHi#sV>#LA7 zo;<$Rruu7Z%yicB_^1J5Ka$A4`JdH}AYrE?!#bb&pmH`Lje7+?7sScvdWlyT$GQz^ z>cWZpKCN6t2%83=oeUj5G?oiWij;_ei5v|MRg})bQ<1nq#VY!bJNCMl%N+>D-k;$v z+>&YY&s97K4sib`0LF23*=9P2e`--xc|jNF=LCAUP_(J(X&t~O7(Oh{YXfN)@vNAq zhntJqtf$D`EZ@w`OsU;JnmHJ}nKV+)dQ1>`pkybvnwcO94<~4X-@6MTv1MWcHSGDI zWvnIYK&Sm$^Z0h3iVbj&fTz@&^oRS?y{N4>O77j zkZ>A|0uNl?S7lVrgYn9jWgkNMch{6+e6+R1v!(iV?de18m+?A!w;uHTLgA z0sMrQ2ct;bKJGCyRz7g-xKf6ei<}@VXAg;mHlhFD-=AwQ!H3JX$1MjOAOI|{VekC3 zMR?}nc-$16=Z=GBgpQ&V23G`s1p-huN2&Dpr%J& z_UGM+xObDC&!HD%A~7-I_n`@_A8{_vL8oM<=|?Vb99X&fc z`+o@pB2rS*R7Swh`498}?~>N;bwhv?f?9i@eg0QMy8h>N7dncj3G=uVYJx!x8)h%p zgiFT2j;zv%M!a8KI*Pv}O}3!?G@tdp&20=5&Gz3r(u)F4?=VT)t~W8FP;c)i$=w%@ zQeR)nO~GC6I7LZ`Q^9ZoHr}zti^JOS^YanT_{_{`GaYAFR{|W?zlmUE2~gX2mY|jX zp`Q%{A5?@TxyS$l4xsLWjaBvaVQeJt-RX_yU*2QGt|geJ`R(Z0K=BaAnN0<%7$FZnQJ$ z&wBX<&>MyUngi_17xoy@^It{?BT4!2gRw4e@gvT672B3aYQXhW_!fzL)Kr&^_q}k= z+~@pMWoUS4CvEMK5g=(6WqO8x0X&q;_ELX26$_+s!2!oY?%Zb3wN|z?nxjOis2grT z{KrqlLu@wwUjDCbwdPy2*<4F|a7*Vzr3lEeURl}O4{o0Qh>w@Huwas7iOxpHHl|6P zAlkGiP@3KfBUY*LK2h$tX+KfZ(i-?LmGF2rh$<{ccv}y5dT2V0*{ni;a~#wVq3`Lp zh6bJkeayYxWz255@Knt(eDJI2Vrr>$8?@<`Q*&5Upa`Fwo|XnAir0M9s0Vqr@$&3C zruhi7F~tvgfyjyt1az8C;sh~I5uIb?SXg>iDE(mXm3#-{_TM<$Km$>c5rKjag7g5nV zlwf;e*RN@9{@^;_px|p(cKNYkczqJ5dGG4VX{V0-eCj=Cf?N!BLZQ_#>sp@DOs} zwfsC7+8NNjP&onB2#}kvf|~yIY$F)ji9na6S30ivo}3&`QFu(uxw8wf^vOlZ7r5@OV2YQP|X!3^+Maax~s>1j}^#@Z$5U zTlz)ENK;A+{07of$z>>#)7|+t4@tyOhKSqnwyu7^I;l!-@aP{M*MwPKN0hL_gj%bOu7+}Q#_7_0$${Mlyy)Xu%^%G0# z=%kdFm%~?9R?bVyD&zb{hsI)k4bIB3-nx!N_eMD#eXSZUz)X^_nrHow-94hs#+a6x zy7gONO*Hvb025qEL;4K7C|0)?h&KpNZnJ0Ksd?+`xENCb{2b6^K{I?X#G210VUMj< z%MknaJ^Rl-cA1x+?dRR7H1Hf%c~=*gA+O_Rx&0RLAE$DGcoLvUM-;AT$4D{vKs-(+ zdnECfyK#Ue#8y^Xx&>rMnAIL``eKukm}oq<@X0ZPKC7t2JU-uaa_V!;8_m7oO5Ovv z(3H{?57;*V)bmz?YHDk@7wc^b;OZR!;&D3P8Y1{%Q1#IVDE^EE5*o3<`Hqb#-?g83 zt9XiLb_e>#uT<@0Fp0zrj?+FJ^?$-$C^B<@}cl~Q_$Kk9Yo zE5rj(&-h@j;&9<-fDxCVHut#Nx;SKeWOLSMlWb7PuPKcquxYk(E`Dgqe=`Qm_JXi< z9&zRkoVeq0H#qRN5HI&D8kpuER}kI_I+#Q`L)c}(m=0JfIKYt(4F#Ow=t$Muv_L#u zba41U#WS!jx_1l%4cr$54V4SR16o*hZw8991r`%QMInJrN@yFD!0A7J95hToUO+sO zjA}{^NWDn(PqkMKqCn6@Pao<||KG=R1DjzA$jcyE&kHJ;pp#{^ehLo+0x>wh({}!B z>})1%>SPA|fOxpLx!Jk+fIkgxeqkN~VO}9tE*@bnuBBiQ*8f@X#s0IExyS#%pmeFJ Q8dv~Qc&98=2{rouKXRD0LjV8( literal 58137 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 diff --git a/data/icons/tomahawk-icon-32x32.png b/data/icons/tomahawk-icon-32x32.png index 1b1b2ce0074d49ebcda2fe3c54ed79e30a0887b0..396e3f8fa8d2bd0a742a4c3a27f4e723a3914eb3 100644 GIT binary patch delta 2586 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 delta 2466 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%0DCxc5pZqEAKfUMb#mx7 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 Q2QPrADr&+?<;~yyAFXBGNB{r; literal 159739 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