From 7a9a117307fb3eafae688bf22b68d66cd46a8e03 Mon Sep 17 00:00:00 2001 From: Christian Muehlhaeuser Date: Fri, 7 Jan 2011 08:51:40 +0100 Subject: [PATCH 1/4] * Fixed recursive message fetching bug. --- src/libtomahawk/network/dbsyncconnection.cpp | 13 ++++++------- 1 file changed, 6 insertions(+), 7 deletions(-) diff --git a/src/libtomahawk/network/dbsyncconnection.cpp b/src/libtomahawk/network/dbsyncconnection.cpp index d36fc8980..2bfc41a60 100644 --- a/src/libtomahawk/network/dbsyncconnection.cpp +++ b/src/libtomahawk/network/dbsyncconnection.cpp @@ -253,15 +253,11 @@ void DBSyncConnection::sendOps() { qDebug() << Q_FUNC_INFO; - - if ( m_lastSentOp.isEmpty() ) - m_lastSentOp = m_uscache.value( "lastop" ).toString(); - - qDebug() << "Will send peer all ops since" << m_lastSentOp; + qDebug() << "Will send peer all ops since" << m_uscache.value( "lastop" ).toString(); source_ptr src = SourceList::instance()->getLocal(); - DatabaseCommand_loadOps* cmd = new DatabaseCommand_loadOps( src, m_lastSentOp ); + DatabaseCommand_loadOps* cmd = new DatabaseCommand_loadOps( src, m_uscache.value( "lastop" ).toString() ); connect( cmd, SIGNAL( done( QString, QString, QList< dbop_ptr > ) ), this, SLOT( sendOpsData( QString, QString, QList< dbop_ptr > ) ) ); @@ -273,8 +269,11 @@ void DBSyncConnection::sendOpsData( QString sinceguid, QString lastguid, QList< dbop_ptr > ops ) { qDebug() << Q_FUNC_INFO << sinceguid << lastguid << "Num ops to send:" << ops.length(); - m_lastSentOp = lastguid; + if ( m_lastSentOp == lastguid ) + ops.clear(); + + m_lastSentOp = lastguid; if( ops.length() == 0 ) { sendMsg( Msg::factory( "ok", Msg::DBOP ) ); From 3061a95cb10ec66633d60f8236c5dc4f9bc8eeec Mon Sep 17 00:00:00 2001 From: Christian Muehlhaeuser Date: Sun, 9 Jan 2011 07:22:42 +0100 Subject: [PATCH 2/4] * Introduced result-hints for static playlists. Speeds up resolving - lots. It will also preserve whichever result you prefer for a query. * If the last DatabaseCommand in a message is invalid, don't leave the source hanging in an unsynced state. * DatabaseImpl can now resolve results by their URL. * Updated README. * Little fixes / cleanups all over the place. --- README | 54 ++--- src/libtomahawk/collection.h | 2 +- src/libtomahawk/database/databasecommand.h | 1 + .../database/databasecommand_loadops.cpp | 2 +- .../databasecommand_loadplaylistentries.cpp | 3 +- .../databasecommand_playbackhistory.cpp | 4 +- .../databasecommand_playbackhistory.h | 4 + .../database/databasecommand_resolve.cpp | 219 ++++++++++++------ .../database/databasecommand_resolve.h | 72 +----- .../databasecommand_setplaylistrevision.cpp | 90 ++++--- .../databasecommand_setplaylistrevision.h | 10 +- src/libtomahawk/database/databaseimpl.cpp | 91 ++++++++ src/libtomahawk/database/databaseimpl.h | 1 + src/libtomahawk/database/databaseworker.cpp | 5 +- src/libtomahawk/database/fuzzyindex.cpp | 35 +-- src/libtomahawk/database/fuzzyindex.h | 3 +- src/libtomahawk/network/dbsyncconnection.cpp | 6 +- src/libtomahawk/network/servent.cpp | 10 +- src/libtomahawk/pipeline.cpp | 5 +- src/libtomahawk/pipeline.h | 3 + src/libtomahawk/playlist.cpp | 82 ++++--- src/libtomahawk/playlist.h | 22 +- src/libtomahawk/sourcelist.cpp | 5 +- src/libtomahawk/sourcelist.h | 3 +- src/playlist/playlistmodel.cpp | 3 +- src/sourcetree/sourcetreeitemwidget.cpp | 1 + 26 files changed, 466 insertions(+), 270 deletions(-) diff --git a/README b/README index 3155ce9b7..675a066e2 100644 --- a/README +++ b/README @@ -8,25 +8,25 @@ Quickstart on Ubuntu Gloox 1.0 (XMPP library) ------------------------ - On Ubuntu 10.10: + On Ubuntu 10.10 (and higher): $ sudo apt-get install libgloox-dev Otherwise see: http://camaya.net/glooxdownload - You need to build gloox 1.0 from source, Ubuntu 10.04 only packages v0.9. + You need to build gloox 1.0 from source, Ubuntu 10.04 only packages version 0.9. - $ # Download and unpack tarball + Download and unpack tarball: $ ./configure --without-openssl --with-gnutls --without-libidn --with-zlib --without-examples --without-tests $ CXXFLAGS=-fPIC make $ sudo make install QJson (Qt JSON library) ----------------------- - On Ubuntu 10.04: + On Ubuntu 10.04 (and higher): $ sudo apt-get install libqjson-dev - Otherwise see: http://sourceforge.net/projects/qjson/files/ (developed using 0.7.1) + Otherwise see: http://sourceforge.net/projects/qjson/files/ (developed using version 0.7.1) - $ # Download and unpack tarball + Download and unpack tarball: $ ./configure && make $ sudo make install @@ -34,47 +34,38 @@ libEchonest 0.1 --------------- See: http://projects.kde.org/projects/playground/libs/libechonest/ - $ git clone git://git.kde.org/libechonest.git - $ cd libechonest + Download and unpack tarball: $ mkdir build && cd build $ cmake .. $ make $ sudo make install -Now compile Tomahawk --------------------- - $ sudo ldconfig -v | grep -Ei 'qjson|gloox|echonest' - $ mkdir build && cd build - $ cmake .. - $ make - $ ./tomahawk - Quickstart on OS X ------------------ -# Install homebrew + Install homebrew $ ruby -e "$(curl -fsSL https://gist.github.com/raw/323731/install_homebrew.rb)" $ brew install qt qjson gloox libmad libvorbis flac taglib boost liblastfm -# Install libEchnoest as per the above instructions + Install libEchnoest as per the above instructions. -# If liblastfm gives problems, do the below: + If liblastfm gives problems, do the below: $ brew edit liblastfm -# change url to https://github.com/davidsansome/liblastfm/tarball/0.3.1 + Change the url to https://github.com/davidsansome/liblastfm/tarball/0.3.1 $ brew install liblastfm -# copy the md5 hash it gives + Copy the md5 hash it returns. $ brew edit liblastfm -# replace the md5 hash with the new one you copied + Replace the md5 hash with the new one you copied. $ brew install liblastfm -# Build Tomahawk - $ git clone git://github.com/tomahawk-player/tomahawk.git - $ cd tomahawk + +Now compile Tomahawk +-------------------- $ mkdir build && cd build $ cmake .. $ make - $ open tomahawk.app + $ ./tomahawk Dependencies @@ -102,10 +93,9 @@ Dependencies To build the app: ----------------- - $ mkdir build - $ cd build + $ mkdir build && cd build - (Pick one of the following two choices. If unsure pick the second one, you probably want a GUI) + Pick one of the following two choices. If unsure pick the second one, you probably want a GUI. $ cmake -Dgui=no .. # enables headless mode, build without GUI $ cmake .. # normal build including GUI @@ -113,10 +103,14 @@ To build the app: To run the app: --------------- - (Only run the next two commands if you installed any of the dependencies from source on Linux) + Only run the next two commands if you installed any of the dependencies from source on Linux. $ export LD_LIBRARY_PATH=/usr/local/lib:$LD_LIBRARY_PATH $ sudo ldconfig -v + Start the application on Linux: $ ./tomahawk + Start the application on OS X: + $ open tomahawk.app + Enjoy! diff --git a/src/libtomahawk/collection.h b/src/libtomahawk/collection.h index 7f4648be5..d11579fef 100644 --- a/src/libtomahawk/collection.h +++ b/src/libtomahawk/collection.h @@ -43,7 +43,7 @@ public: virtual QList< Tomahawk::playlist_ptr > playlists() { return m_playlists; } virtual QList< Tomahawk::query_ptr > tracks() { return m_tracks; } - const source_ptr& source() const { return m_source; } + source_ptr source() const { return m_source; } unsigned int lastmodified() const { return m_lastmodified; } signals: diff --git a/src/libtomahawk/database/databasecommand.h b/src/libtomahawk/database/databasecommand.h index 422aa5744..b8a59fc7f 100644 --- a/src/libtomahawk/database/databasecommand.h +++ b/src/libtomahawk/database/databasecommand.h @@ -55,6 +55,7 @@ public: virtual bool loggable() const { return false; } virtual bool singletonCmd() const { return false; } + virtual bool localOnly() const { return false; } QString guid() const { diff --git a/src/libtomahawk/database/databasecommand_loadops.cpp b/src/libtomahawk/database/databasecommand_loadops.cpp index 6265204f7..1c2a3880f 100644 --- a/src/libtomahawk/database/databasecommand_loadops.cpp +++ b/src/libtomahawk/database/databasecommand_loadops.cpp @@ -35,6 +35,6 @@ DatabaseCommand_loadOps::exec( DatabaseImpl* dbi ) ops << op; } - qDebug() << "Loaded" << ops.length() << "ops from db"; +// qDebug() << "Loaded" << ops.length() << "ops from db"; emit done( m_since, lastguid, ops ); } diff --git a/src/libtomahawk/database/databasecommand_loadplaylistentries.cpp b/src/libtomahawk/database/databasecommand_loadplaylistentries.cpp index c71fe9b8f..60c21b3af 100644 --- a/src/libtomahawk/database/databasecommand_loadplaylistentries.cpp +++ b/src/libtomahawk/database/databasecommand_loadplaylistentries.cpp @@ -51,12 +51,13 @@ DatabaseCommand_LoadPlaylistEntries::exec( DatabaseImpl* dbi ) e->setAnnotation( query.value( 4 ).toString() ); e->setDuration( query.value( 5 ).toUInt() ); e->setLastmodified( 0 ); // TODO e->lastmodified = query.value(6).toInt(); - e->setResulthint( query.value( 8 ).toString() ); + e->setResultHint( query.value( 8 ).toString() ); QVariantMap m; m.insert( "artist", query.value( 2 ).toString() ); m.insert( "album", query.value( 3 ).toString() ); m.insert( "track", query.value( 1 ).toString() ); + m.insert( "resulthint", query.value( 8 ).toString() ); m.insert( "qid", uuid() ); Tomahawk::query_ptr q( new Tomahawk::Query( m ) ); diff --git a/src/libtomahawk/database/databasecommand_playbackhistory.cpp b/src/libtomahawk/database/databasecommand_playbackhistory.cpp index 697be4916..849d5ce8b 100644 --- a/src/libtomahawk/database/databasecommand_playbackhistory.cpp +++ b/src/libtomahawk/database/databasecommand_playbackhistory.cpp @@ -35,7 +35,9 @@ DatabaseCommand_PlaybackHistory::exec( DatabaseImpl* dbi ) "FROM track, artist " "WHERE artist.id = track.artist " "AND track.id = %1 " - ).arg( query.value( 0 ).toUInt() ); + "%2" + ).arg( query.value( 0 ).toUInt() ) + .arg( m_amount > 0 ? QString( "LIMIT 0, %1" ).arg( m_amount ) : QString() ); query_track.prepare( sql ); query_track.exec(); diff --git a/src/libtomahawk/database/databasecommand_playbackhistory.h b/src/libtomahawk/database/databasecommand_playbackhistory.h index e3e414d59..da57e07df 100644 --- a/src/libtomahawk/database/databasecommand_playbackhistory.h +++ b/src/libtomahawk/database/databasecommand_playbackhistory.h @@ -16,6 +16,7 @@ Q_OBJECT public: explicit DatabaseCommand_PlaybackHistory( const Tomahawk::source_ptr& source, QObject* parent = 0 ) : DatabaseCommand( parent ) + , m_amount( 0 ) { setSource( source ); } @@ -25,10 +26,13 @@ public: virtual bool doesMutates() const { return false; } virtual QString commandname() const { return "playbackhistory"; } + void setLimit( unsigned int amount ) { m_amount = amount; } + signals: void tracks( const QList& queries ); private: + unsigned int m_amount; }; #endif // DATABASECOMMAND_PLAYBACKHISTORY_H diff --git a/src/libtomahawk/database/databasecommand_resolve.cpp b/src/libtomahawk/database/databasecommand_resolve.cpp index 9f76cc22b..394e449c8 100644 --- a/src/libtomahawk/database/databasecommand_resolve.cpp +++ b/src/libtomahawk/database/databasecommand_resolve.cpp @@ -19,12 +19,34 @@ DatabaseCommand_Resolve::DatabaseCommand_Resolve( const QVariant& v, bool search void DatabaseCommand_Resolve::exec( DatabaseImpl* lib ) { - const Tomahawk::QID qid = m_v.toMap().value("qid").toString(); - const QString artistname = m_v.toMap().value("artist").toString(); - const QString albumname = m_v.toMap().value("album").toString(); - const QString trackname = m_v.toMap().value("track").toString(); + const QMap map = m_v.toMap(); - //qDebug() << Q_FUNC_INFO << artistname << trackname; + const Tomahawk::QID qid = map.value( "qid" ).toString(); + const QString artistname = map.value( "artist" ).toString(); + const QString albumname = map.value( "album" ).toString(); + const QString trackname = map.value( "track" ).toString(); + const QString resulthint = map.value( "resulthint" ).toString(); + + collection_ptr coll; + QList res; + if ( !resulthint.isEmpty() ) + { + qDebug() << "Using result-hint to speed up resolving:" << resulthint; + + QVariantMap m = lib->result( resulthint ); + if ( !m.isEmpty() ) + { + if ( m.value( "srcid" ).toUInt() > 0 ) + coll = SourceList::instance()->get( m.value( "srcid" ).toUInt() )->collection(); + else + coll = SourceList::instance()->getLocal()->collection(); + + res << Tomahawk::result_ptr( new Tomahawk::Result( m, coll ) ); + emit results( qid, res ); + + return; + } + } /* Resolving is a 2 stage process. @@ -33,15 +55,14 @@ DatabaseCommand_Resolve::exec( DatabaseImpl* lib ) results that are less than MINSCORE */ - typedef QPair scorepair_t; + typedef QPair scorepair_t; // STEP 1 QList< int > artists = lib->searchTable( "artist", artistname, 10 ); - QList< int > tracks = lib->searchTable( "track", trackname, 10 ); - QList< int > albums = lib->searchTable( "album", albumname, 10 ); + QList< int > tracks = lib->searchTable( "track", trackname, 10 ); + QList< int > albums = lib->searchTable( "album", albumname, 10 ); - //qDebug() << "art" << artists.size() << "trk" << tracks.size(); - //qDebug() << "searchTable calls duration:" << timer.elapsed(); + //qDebug() << "searchTable calls duration:" << timer.elapsed() << "ms"; if( artists.length() == 0 || tracks.length() == 0 ) { @@ -53,8 +74,10 @@ DatabaseCommand_Resolve::exec( DatabaseImpl* lib ) TomahawkSqlQuery files_query = lib->newquery(); QStringList artsl, trksl; - foreach( int i, artists ) artsl.append( QString::number(i) ); - foreach( int i, tracks ) trksl.append( QString::number(i) ); + foreach( int i, artists ) + artsl.append( QString::number( i ) ); + foreach( int i, tracks ) + trksl.append( QString::number( i ) ); QString sql = QString( "SELECT " "url, mtime, size, md5, mimetype, duration, bitrate, file_join.artist, file_join.album, file_join.track, " @@ -73,81 +96,63 @@ DatabaseCommand_Resolve::exec( DatabaseImpl* lib ) "file.source %1 AND " "file.id = file_join.file AND " "file_join.artist IN (%2) AND " - "file_join.track IN (%3) " - "ORDER by file_join.artist,file_join.track" - ).arg( m_searchlocal ? "IS NULL" : " IN (SELECT id FROM source WHERE isonline = 'true') " ) - .arg( artsl.join(",") ) - .arg( trksl.join(",") ); + "file_join.track IN (%3)" + ).arg( m_searchlocal ? "IS NULL" : " > 0 " ) + .arg( artsl.join( "," ) ) + .arg( trksl.join( "," ) ); files_query.prepare( sql ); - bool ok = files_query.exec(); - if(!ok) - throw "Error"; - - //qDebug() << "SQL exec() duration, ms, " << timer.elapsed() - // << "numresults" << files_query.numRowsAffected(); - //qDebug() << sql; - - QList res; + files_query.exec(); while( files_query.next() ) { QVariantMap m; - m["mtime"] = files_query.value(1).toString(); - m["size"] = files_query.value(2).toInt(); - m["hash"] = files_query.value(3).toString(); - m["mimetype"] = files_query.value(4).toString(); - m["duration"] = files_query.value(5).toInt(); - m["bitrate"] = files_query.value(6).toInt(); - m["artist"] = files_query.value(10).toString(); - m["artistid"] = files_query.value(15).toUInt(); - m["album"] = files_query.value(11).toString(); - m["albumid"] = files_query.value(16).toUInt(); - m["track"] = files_query.value(12).toString(); - m["srcid"] = files_query.value(13).toInt(); - m["albumpos"] = files_query.value(14).toUInt(); + m["mtime"] = files_query.value( 1 ).toString(); + m["size"] = files_query.value( 2 ).toInt(); + m["hash"] = files_query.value( 3 ).toString(); + m["mimetype"] = files_query.value( 4 ).toString(); + m["duration"] = files_query.value( 5 ).toInt(); + m["bitrate"] = files_query.value( 6 ).toInt(); + m["artist"] = files_query.value( 10 ).toString(); + m["artistid"] = files_query.value( 15 ).toUInt(); + m["album"] = files_query.value( 11 ).toString(); + m["albumid"] = files_query.value( 16 ).toUInt(); + m["track"] = files_query.value( 12 ).toString(); + m["srcid"] = files_query.value( 13 ).toInt(); + m["albumpos"] = files_query.value( 14 ).toUInt(); m["sid"] = uuid(); - collection_ptr coll; - + source_ptr s; const QString url_str = files_query.value( 0 ).toString(); if( m_searchlocal ) { - coll = SourceList::instance()->getLocal()->collection(); + s = SourceList::instance()->getLocal(); m["url"] = url_str; m["source"] = "Local Database"; // TODO } else { - source_ptr s = SourceList::instance()->get( files_query.value( 13 ).toUInt() ); + s = SourceList::instance()->get( files_query.value( 13 ).toUInt() ); if( s.isNull() ) { - //qDebug() << "Skipping result for offline sourceid:" << files_query.value(13).toUInt(); + //qDebug() << "Skipping result for offline sourceid:" << files_query.value( 13 ).toUInt(); // will happen for valid sources which are offline (and thus not in the sourcelist) return; } - coll = s->collection(); - m.insert( "url", QString( "servent://%1\t%2" ) - .arg( s->userName() ) - .arg( url_str ) ); + m.insert( "url", QString( "servent://%1\t%2" ).arg( s->userName() ).arg( url_str ) ); m.insert( "source", s->friendlyName() ); } - //int artid = files_query.value( 7 ).toInt(); - //int albid = files_query.value( 8 ).toInt(); - //int trkid = files_query.value( 9 ).toInt(); - float score = how_similar( m_v.toMap(), m ); - //qDebug() << "Score calc:" << timer.elapsed(); + //qDebug() << "Score calc:" << timer.elapsed() << "ms"; m["score"] = score; - //qDebug() << "RESULT" << score << m; - if( score < MINSCORE ) continue; + coll = s->collection(); res << Tomahawk::result_ptr( new Tomahawk::Result( m, coll ) ); } @@ -160,24 +165,24 @@ float DatabaseCommand_Resolve::how_similar( const QVariantMap& q, const QVariantMap& r ) { // query values - const QString qArtistname = DatabaseImpl::sortname( q.value("artist").toString() ); - const QString qAlbumname = DatabaseImpl::sortname( q.value("album").toString() ); - const QString qTrackname = DatabaseImpl::sortname( q.value("track").toString() ); + const QString qArtistname = DatabaseImpl::sortname( q.value( "artist" ).toString() ); + const QString qAlbumname = DatabaseImpl::sortname( q.value( "album" ).toString() ); + const QString qTrackname = DatabaseImpl::sortname( q.value( "track" ).toString() ); // result values - const QString rArtistname = DatabaseImpl::sortname( r.value("artist").toString() ); - const QString rAlbumname = DatabaseImpl::sortname( r.value("album").toString() ); - const QString rTrackname = DatabaseImpl::sortname( r.value("track").toString() ); + const QString rArtistname = DatabaseImpl::sortname( r.value( "artist" ).toString() ); + const QString rAlbumname = DatabaseImpl::sortname( r.value( "album" ).toString() ); + const QString rTrackname = DatabaseImpl::sortname( r.value( "track" ).toString() ); // normal edit distance int artdist = levenshtein( qArtistname, rArtistname ); - int albdist = levenshtein( qAlbumname, rAlbumname ); - int trkdist = levenshtein( qTrackname, rTrackname ); + int albdist = levenshtein( qAlbumname, rAlbumname ); + int trkdist = levenshtein( qTrackname, rTrackname ); // max length of name int mlart = qMax( qArtistname.length(), rArtistname.length() ); - int mlalb = qMax( qAlbumname.length(), rAlbumname.length() ); - int mltrk = qMax( qTrackname.length(), rTrackname.length() ); + int mlalb = qMax( qAlbumname.length(), rAlbumname.length() ); + int mltrk = qMax( qTrackname.length(), rTrackname.length() ); // distance scores float dcart = (float)( mlart - artdist ) / mlart; @@ -185,10 +190,90 @@ DatabaseCommand_Resolve::how_similar( const QVariantMap& q, const QVariantMap& r float dctrk = (float)( mltrk - trkdist ) / mltrk; // don't penalize for missing album name - if( qAlbumname.length() == 0 ) dcalb = 1.0; + if( qAlbumname.length() == 0 ) + dcalb = 1.0; // weighted, so album match is worth less than track title float combined = ( dcart*4 + dcalb + dctrk*5 ) / 10; - return combined; } + + +int +DatabaseCommand_Resolve::levenshtein( const QString& source, const QString& target ) +{ + // Step 1 + const int n = source.length(); + const int m = target.length(); + + if ( n == 0 ) + return m; + if ( m == 0 ) + return n; + + // Good form to declare a TYPEDEF + typedef QVector< QVector > Tmatrix; + Tmatrix matrix; + matrix.resize( n + 1 ); + + // Size the vectors in the 2.nd dimension. Unfortunately C++ doesn't + // allow for allocation on declaration of 2.nd dimension of vec of vec + for ( int i = 0; i <= n; i++ ) + { + QVector tmp; + tmp.resize( m + 1 ); + matrix.insert( i, tmp ); + } + + // Step 2 + for ( int i = 0; i <= n; i++ ) + matrix[i][0] = i; + for ( int j = 0; j <= m; j++ ) + matrix[0][j] = j; + + // Step 3 + for ( int i = 1; i <= n; i++ ) + { + const QChar s_i = source[i - 1]; + + // Step 4 + for ( int j = 1; j <= m; j++ ) + { + const QChar t_j = target[j - 1]; + + // Step 5 + int cost; + if ( s_i == t_j ) + cost = 0; + else + cost = 1; + + // Step 6 + const int above = matrix[i - 1][j]; + const int left = matrix[i][j - 1]; + const int diag = matrix[i - 1][j - 1]; + + int cell = ( ((left + 1) > (diag + cost)) ? diag + cost : left + 1 ); + if( above + 1 < cell ) + cell = above + 1; + + // Step 6A: Cover transposition, in addition to deletion, + // insertion and substitution. This step is taken from: + // Berghel, Hal ; Roach, David : "An Extension of Ukkonen's + // Enhanced Dynamic Programming ASM Algorithm" + // (http://www.acm.org/~hlb/publications/asm/asm.html) + if ( i > 2 && j > 2 ) + { + int trans = matrix[i - 2][j - 2] + 1; + + if ( source[ i - 2 ] != t_j ) trans++; + if ( s_i != target[ j - 2 ] ) trans++; + if ( cell > trans) cell = trans; + } + matrix[i][j] = cell; + } + } + + // Step 7 + return matrix[n][m]; +} diff --git a/src/libtomahawk/database/databasecommand_resolve.h b/src/libtomahawk/database/databasecommand_resolve.h index a8ef660ba..78015f920 100644 --- a/src/libtomahawk/database/databasecommand_resolve.h +++ b/src/libtomahawk/database/databasecommand_resolve.h @@ -18,7 +18,7 @@ public: virtual QString commandname() const { return "dbresolve"; } virtual bool doesMutates() const { return false; } - virtual void exec(DatabaseImpl *lib); + virtual void exec( DatabaseImpl *lib ); signals: void results( Tomahawk::QID qid, QList results ); @@ -30,75 +30,7 @@ private: bool m_searchlocal; float how_similar( const QVariantMap& q, const QVariantMap& r ); - - static int levenshtein(const QString& source, const QString& target) - { - // Step 1 - const int n = source.length(); - const int m = target.length(); - if (n == 0) { - return m; - } - if (m == 0) { - return n; - } - // Good form to declare a TYPEDEF - typedef QVector< QVector > Tmatrix; - Tmatrix matrix; - matrix.resize( n+1 ); - - // Size the vectors in the 2.nd dimension. Unfortunately C++ doesn't - // allow for allocation on declaration of 2.nd dimension of vec of vec - for (int i = 0; i <= n; i++) { - QVector tmp; - tmp.resize( m+1 ); - matrix.insert( i, tmp ); - } - // Step 2 - for (int i = 0; i <= n; i++) { - matrix[i][0]=i; - } - for (int j = 0; j <= m; j++) { - matrix[0][j]=j; - } - // Step 3 - for (int i = 1; i <= n; i++) { - const QChar s_i = source[i-1]; - // Step 4 - for (int j = 1; j <= m; j++) { - const QChar t_j = target[j-1]; - // Step 5 - int cost; - if (s_i == t_j) { - cost = 0; - } - else { - cost = 1; - } - // Step 6 - const int above = matrix[i-1][j]; - const int left = matrix[i][j-1]; - const int diag = matrix[i-1][j-1]; - //int cell = min( above + 1, min(left + 1, diag + cost)); - int cell = (((left+1)>(diag+cost))?diag+cost:left+1); - if(above+1 < cell) cell = above+1; - // Step 6A: Cover transposition, in addition to deletion, - // insertion and substitution. This step is taken from: - // Berghel, Hal ; Roach, David : "An Extension of Ukkonen's - // Enhanced Dynamic Programming ASM Algorithm" - // (http://www.acm.org/~hlb/publications/asm/asm.html) - if (i>2 && j>2) { - int trans=matrix[i-2][j-2]+1; - if (source[i-2]!=t_j) trans++; - if (s_i!=target[j-2]) trans++; - if (cell>trans) cell=trans; - } - matrix[i][j]=cell; - } - } - // Step 7 - return matrix[n][m]; - }; + static int levenshtein( const QString& source, const QString& target ); }; #endif // DATABASECOMMAND_RESOLVE_H diff --git a/src/libtomahawk/database/databasecommand_setplaylistrevision.cpp b/src/libtomahawk/database/databasecommand_setplaylistrevision.cpp index 2d509f49c..1f3da6e4d 100644 --- a/src/libtomahawk/database/databasecommand_setplaylistrevision.cpp +++ b/src/libtomahawk/database/databasecommand_setplaylistrevision.cpp @@ -12,13 +12,17 @@ DatabaseCommand_SetPlaylistRevision::DatabaseCommand_SetPlaylistRevision( const QString& newrev, const QString& oldrev, const QStringList& orderedguids, - const QList& addedentries ) + const QList& addedentries, + const QList& entries ) : DatabaseCommandLoggable( s ) , m_newrev( newrev ) , m_oldrev( oldrev ) , m_addedentries( addedentries ) + , m_entries( entries ) , m_applied( false ) { + m_localOnly = ( newrev == oldrev ); + setPlaylistguid( playlistguid ); QVariantList tmp; @@ -34,6 +38,9 @@ DatabaseCommand_SetPlaylistRevision::postCommitHook() { qDebug() << Q_FUNC_INFO; + if ( m_localOnly ) + return; + QStringList orderedentriesguids; foreach( const QVariant& v, m_orderedguids ) orderedentriesguids << v.toString(); @@ -70,7 +77,7 @@ DatabaseCommand_SetPlaylistRevision::exec( DatabaseImpl* lib ) // get the current revision for this playlist // this also serves to check the playlist exists. TomahawkSqlQuery chkq = lib->newquery(); - chkq.prepare("SELECT currentrevision FROM playlist WHERE guid = ?"); + chkq.prepare( "SELECT currentrevision FROM playlist WHERE guid = ?" ); chkq.addBindValue( m_playlistguid ); if( chkq.exec() && chkq.next() ) { @@ -89,38 +96,59 @@ DatabaseCommand_SetPlaylistRevision::exec( DatabaseImpl* lib ) // add any new items: TomahawkSqlQuery adde = lib->newquery(); - - QString sql = "INSERT INTO playlist_item( guid, playlist, trackname, artistname, albumname, " - "annotation, duration, addedon, addedby, result_hint ) " - "VALUES( ?, ?, ?, ?, ?, ?, ?, ?, ?, ? )"; - adde.prepare( sql ); - - qDebug() << "Num new playlist_items to add:" << m_addedentries.length(); - foreach( const plentry_ptr& e, m_addedentries ) + if ( m_localOnly ) { - m_addedmap.insert( e->guid(), e ); // needed in postcommithook + QString sql = "UPDATE playlist_item SET result_hint = ? WHERE guid = ?"; + adde.prepare( sql ); - adde.bindValue( 0, e->guid() ); - adde.bindValue( 1, m_playlistguid ); - adde.bindValue( 2, e->query()->track() ); - adde.bindValue( 3, e->query()->artist() ); - adde.bindValue( 4, e->query()->album() ); - adde.bindValue( 5, e->annotation() ); - adde.bindValue( 6, (int) e->duration() ); - adde.bindValue( 7, e->lastmodified() ); - adde.bindValue( 8, source()->isLocal() ? QVariant(QVariant::Int) : source()->id() ); - adde.bindValue( 9, "" ); - adde.exec(); + foreach( const plentry_ptr& e, m_entries ) + { + if ( e->query()->results().isEmpty() ) + continue; + + adde.bindValue( 0, e->query()->results().first()->url() ); + adde.bindValue( 1, e->guid() ); + adde.exec(); + } + + return; + } + else + { + QString sql = "INSERT INTO playlist_item( guid, playlist, trackname, artistname, albumname, " + "annotation, duration, addedon, addedby, result_hint ) " + "VALUES( ?, ?, ?, ?, ?, ?, ?, ?, ?, ? )"; + adde.prepare( sql ); + + qDebug() << "Num new playlist_items to add:" << m_addedentries.length(); + foreach( const plentry_ptr& e, m_addedentries ) + { + m_addedmap.insert( e->guid(), e ); // needed in postcommithook + + QString resultHint; + if ( !e->query()->results().isEmpty() ) + resultHint = e->query()->results().first()->url(); + + adde.bindValue( 0, e->guid() ); + adde.bindValue( 1, m_playlistguid ); + adde.bindValue( 2, e->query()->track() ); + adde.bindValue( 3, e->query()->artist() ); + adde.bindValue( 4, e->query()->album() ); + adde.bindValue( 5, e->annotation() ); + adde.bindValue( 6, (int) e->duration() ); + adde.bindValue( 7, e->lastmodified() ); + adde.bindValue( 8, source()->isLocal() ? QVariant(QVariant::Int) : source()->id() ); + adde.bindValue( 9, resultHint ); + adde.exec(); + } } - // add the new revision: - //qDebug() << "Adding new playlist revision, guid:" << m_newrev - // << entries; + // add / update the revision: TomahawkSqlQuery query = lib->newquery(); - sql = "INSERT INTO playlist_revision(guid, playlist, entries, author, timestamp, previous_revision) " - "VALUES(?, ?, ?, ?, ?, ?)"; - + QString sql = "INSERT INTO playlist_revision(guid, playlist, entries, author, timestamp, previous_revision) " + "VALUES(?, ?, ?, ?, ?, ?)"; query.prepare( sql ); + query.addBindValue( m_newrev ); query.addBindValue( m_playlistguid ); query.addBindValue( entries ); @@ -133,9 +161,10 @@ DatabaseCommand_SetPlaylistRevision::exec( DatabaseImpl* lib ) // if optimistic locking is ok, update current revision to this new one if( currentrevision == m_oldrev ) { - TomahawkSqlQuery query2 = lib->newquery(); qDebug() << "updating current revision, optimistic locking ok"; - query2.prepare("UPDATE playlist SET currentrevision = ? WHERE guid = ?"); + + TomahawkSqlQuery query2 = lib->newquery(); + query2.prepare( "UPDATE playlist SET currentrevision = ? WHERE guid = ?" ); query2.bindValue( 0, m_newrev ); query2.bindValue( 1, m_playlistguid ); query2.exec(); @@ -157,6 +186,7 @@ DatabaseCommand_SetPlaylistRevision::exec( DatabaseImpl* lib ) QJson::Parser parser; QVariant v = parser.parse( query_entries.value(0).toByteArray(), &ok ); Q_ASSERT( ok && v.type() == QVariant::List ); //TODO + m_previous_rev_orderedguids = v.toStringList(); } } diff --git a/src/libtomahawk/database/databasecommand_setplaylistrevision.h b/src/libtomahawk/database/databasecommand_setplaylistrevision.h index 970cf767e..786acb0d1 100644 --- a/src/libtomahawk/database/databasecommand_setplaylistrevision.h +++ b/src/libtomahawk/database/databasecommand_setplaylistrevision.h @@ -23,6 +23,7 @@ public: explicit DatabaseCommand_SetPlaylistRevision( QObject* parent = 0 ) : DatabaseCommandLoggable( parent ) , m_applied( false ) + , m_localOnly( false ) {} explicit DatabaseCommand_SetPlaylistRevision( const source_ptr& s, @@ -30,13 +31,16 @@ public: const QString& newrev, const QString& oldrev, const QStringList& orderedguids, - const QList& addedentries ); + const QList& addedentries, + const QList& entries ); QString commandname() const { return "setplaylistrevision"; } virtual void exec( DatabaseImpl* lib ); virtual void postCommitHook(); + virtual bool doesMutates() const { return true; } + virtual bool localOnly() const { return m_localOnly; } void setAddedentriesV( const QVariantList& vlist ) { @@ -76,9 +80,11 @@ private: QString m_newrev, m_oldrev; QVariantList m_orderedguids; QStringList m_previous_rev_orderedguids; - QList m_addedentries; + QList m_addedentries, m_entries; bool m_applied; QMap m_addedmap; + + bool m_localOnly; }; #endif // DATABASECOMMAND_SETPLAYLISTREVISION_H diff --git a/src/libtomahawk/database/databaseimpl.cpp b/src/libtomahawk/database/databaseimpl.cpp index bce48c5aa..df6e55f7e 100644 --- a/src/libtomahawk/database/databaseimpl.cpp +++ b/src/libtomahawk/database/databaseimpl.cpp @@ -8,6 +8,7 @@ #include "database/database.h" #include "databasecommand_updatesearchindex.h" +#include "sourcelist.h" /* !!!! You need to manually generate schema.sql.h when the schema changes: cd src/libtomahawk/database @@ -470,3 +471,93 @@ DatabaseImpl::album( int id ) return m; } + +QVariantMap +DatabaseImpl::result( const QString& url ) +{ + TomahawkSqlQuery query = newquery(); + Tomahawk::source_ptr s; + QString fileUrl; + + if ( url.contains( "servent://" ) ) + { + QStringList parts = url.mid( QString( "servent://" ).length() ).split( "\t" ); + s = SourceList::instance()->get( parts.at( 0 ) ); + fileUrl = parts.at( 1 ); + } + else if ( url.contains( "file://" ) ) + { + s = SourceList::instance()->getLocal(); + fileUrl = url; + } + else + Q_ASSERT( false ); + + bool searchlocal = s->isLocal(); + + QString sql = QString( "SELECT " + "url, mtime, size, md5, mimetype, duration, bitrate, file_join.artist, file_join.album, file_join.track, " + "artist.name as artname, " + "album.name as albname, " + "track.name as trkname, " + "file.source, " + "file_join.albumpos, " + "artist.id as artid, " + "album.id as albid " + "FROM file, file_join, artist, track " + "LEFT JOIN album ON album.id = file_join.album " + "WHERE " + "artist.id = file_join.artist AND " + "track.id = file_join.track AND " + "file.source %1 AND " + "file_join.file = file.id AND " + "file.url = ?" + ).arg( searchlocal ? "IS NULL" : QString( "= %1" ).arg( s->id() ) ); + + query.prepare( sql ); + query.bindValue( 0, fileUrl ); + query.exec(); + + QVariantMap m; + if( query.next() ) + { + const QString url_str = query.value( 0 ).toString(); + if( searchlocal ) + { + m["url"] = url_str; + m["source"] = "Local Database"; // TODO + } + else + { + Tomahawk::source_ptr s; + s = SourceList::instance()->get( query.value( 13 ).toUInt() ); + if( s.isNull() ) + { + //qDebug() << "Skipping result for offline sourceid:" << files_query.value( 13 ).toUInt(); + // will happen for valid sources which are offline (and thus not in the sourcelist) + return m; + } + + m.insert( "url", QString( "servent://%1\t%2" ).arg( s->userName() ).arg( url_str ) ); + m.insert( "source", s->friendlyName() ); + } + + m["mtime"] = query.value( 1 ).toString(); + m["size"] = query.value( 2 ).toInt(); + m["hash"] = query.value( 3 ).toString(); + m["mimetype"] = query.value( 4 ).toString(); + m["duration"] = query.value( 5 ).toInt(); + m["bitrate"] = query.value( 6 ).toInt(); + m["artist"] = query.value( 10 ).toString(); + m["artistid"] = query.value( 15 ).toUInt(); + m["album"] = query.value( 11 ).toString(); + m["albumid"] = query.value( 16 ).toUInt(); + m["track"] = query.value( 12 ).toString(); + m["srcid"] = query.value( 13 ).toInt(); + m["albumpos"] = query.value( 14 ).toUInt(); + m["sid"] = uuid(); + m["score"] = 1.0; + } + + return m; +} diff --git a/src/libtomahawk/database/databaseimpl.h b/src/libtomahawk/database/databaseimpl.h index 63bd1ea36..32ec94ad4 100644 --- a/src/libtomahawk/database/databaseimpl.h +++ b/src/libtomahawk/database/databaseimpl.h @@ -46,6 +46,7 @@ public: QVariantMap album( int id ); QVariantMap track( int id ); QVariantMap file( int fid ); + QVariantMap result( const QString& url ); static bool scorepairSorter( const QPair& left, const QPair& right ) { diff --git a/src/libtomahawk/database/databaseworker.cpp b/src/libtomahawk/database/databaseworker.cpp index 7ebf4cfd1..60bbc2135 100644 --- a/src/libtomahawk/database/databaseworker.cpp +++ b/src/libtomahawk/database/databaseworker.cpp @@ -71,7 +71,7 @@ DatabaseWorker::doWork( QSharedPointer cmd ) { cmd->_exec( m_dbimpl ); // runs actual SQL stuff - if( cmd->loggable() ) + if( cmd->loggable() && !cmd->localOnly() ) { // We only save our own ops to the oplog, since incoming ops from peers // are applied immediately. @@ -110,7 +110,7 @@ DatabaseWorker::doWork( QSharedPointer cmd ) if( cmd->doesMutates() ) { - qDebug() << "Comitting" << cmd->commandname();; + qDebug() << "Committing" << cmd->commandname();; if( !m_dbimpl->database().commit() ) { @@ -152,6 +152,7 @@ DatabaseWorker::doWork( QSharedPointer cmd ) Q_ASSERT( false ); throw; } + cmd->emitFinished(); } diff --git a/src/libtomahawk/database/fuzzyindex.cpp b/src/libtomahawk/database/fuzzyindex.cpp index 961f3b590..a4dbf5478 100644 --- a/src/libtomahawk/database/fuzzyindex.cpp +++ b/src/libtomahawk/database/fuzzyindex.cpp @@ -4,6 +4,7 @@ #include + FuzzyIndex::FuzzyIndex( DatabaseImpl &db ) : QObject() , m_db( db ) @@ -16,10 +17,10 @@ void FuzzyIndex::loadNgramIndex() { // this updates the index in the DB, if needed: - qDebug() << "Checking catalogue is fully indexed.."; - m_db.updateSearchIndex("artist",0); - m_db.updateSearchIndex("album",0); - m_db.updateSearchIndex("track",0); + qDebug() << "Checking catalogue is fully indexed..."; + m_db.updateSearchIndex( "artist", 0 ); + m_db.updateSearchIndex( "album", 0 ); + m_db.updateSearchIndex( "track", 0 ); // loads index from DB into memory: qDebug() << "Loading search index for catalogue metadata..." << thread(); @@ -36,6 +37,7 @@ FuzzyIndex::loadNgramIndex_helper( QHash< QString, QMap >& idx { QTime t; t.start(); + TomahawkSqlQuery query = m_db.newquery(); query.exec( QString( "SELECT ngram, id, num " "FROM %1_search_index " @@ -46,13 +48,14 @@ FuzzyIndex::loadNgramIndex_helper( QHash< QString, QMap >& idx QString lastngram; while( query.next() ) { + const QString ng = query.value( 0 ).toString(); if( lastngram.isEmpty() ) - lastngram = query.value(0).toString(); + lastngram = ng; - if( query.value( 0 ).toString() != lastngram ) + if( ng != lastngram ) { idx.insert( lastngram, ngram_idx ); - lastngram = query.value( 0 ).toString(); + lastngram = ng; ngram_idx.clear(); } @@ -63,11 +66,11 @@ FuzzyIndex::loadNgramIndex_helper( QHash< QString, QMap >& idx idx.insert( lastngram, ngram_idx ); qDebug() << "Loaded" << idx.size() << "ngram entries for" << table - << "in" << t.elapsed(); + << "in" << t.elapsed() << "ms"; } -void FuzzyIndex::mergeIndex( const QString& table, QHash< QString, QMap > tomerge ) +void FuzzyIndex::mergeIndex( const QString& table, const QHash< QString, QMap >& tomerge ) { qDebug() << Q_FUNC_INFO << table << tomerge.keys().size(); @@ -75,9 +78,10 @@ void FuzzyIndex::mergeIndex( const QString& table, QHash< QString, QMapsize() == 0 ) { @@ -85,12 +89,13 @@ void FuzzyIndex::mergeIndex( const QString& table, QHash< QString, QMap tmk = tomerge.keys(); + foreach( const QString& ngram, tmk ) { - if( idx->contains( ngram ) ) { - foreach( quint32 id, tomerge[ngram].keys() ) + QList tmkn = tomerge[ngram].keys(); + foreach( quint32 id, tmkn ) { (*idx)[ ngram ][ id ] += tomerge[ngram][id]; } @@ -101,6 +106,7 @@ void FuzzyIndex::mergeIndex( const QString& table, QHash< QString, QMapcontains( ngram ) ) continue; + //qDebug() << name_orig << "NGRAM:" << ngram << "candidates:" << (*idx)[ngram].size(); QMapIterator iter( (*idx)[ngram] ); while( iter.hasNext() ) diff --git a/src/libtomahawk/database/fuzzyindex.h b/src/libtomahawk/database/fuzzyindex.h index 4659dbd3c..0258493eb 100644 --- a/src/libtomahawk/database/fuzzyindex.h +++ b/src/libtomahawk/database/fuzzyindex.h @@ -12,6 +12,7 @@ class DatabaseImpl; class FuzzyIndex : public QObject { Q_OBJECT + public: explicit FuzzyIndex( DatabaseImpl &db ); @@ -21,7 +22,7 @@ signals: public slots: void loadNgramIndex(); QMap< int, float > search( const QString& table, const QString& name ); - void mergeIndex( const QString& table, QHash< QString, QMap > tomerge ); + void mergeIndex( const QString& table, const QHash< QString, QMap >& tomerge ); private: void loadNgramIndex_helper( QHash< QString, QMap >& idx, const QString& table, unsigned int fromkey = 0 ); diff --git a/src/libtomahawk/network/dbsyncconnection.cpp b/src/libtomahawk/network/dbsyncconnection.cpp index 2bfc41a60..9b860d1fd 100644 --- a/src/libtomahawk/network/dbsyncconnection.cpp +++ b/src/libtomahawk/network/dbsyncconnection.cpp @@ -204,7 +204,10 @@ DBSyncConnection::handleMsg( msg_ptr msg ) DatabaseCommand *cmd = DatabaseCommand::factory( m, m_source ); if ( !cmd ) { - qDebug() << "UNKNOWN DBOP CMD!"; + qDebug() << "UNKNOWN DBOP CMD" << cmd->commandname() << cmd->guid(); + + if( !msg->is( Msg::FRAGMENT ) ) // last msg in this batch + lastOpApplied(); return; } @@ -215,6 +218,7 @@ DBSyncConnection::handleMsg( msg_ptr msg ) changeState( SAVING ); // just DB work left to complete connect( cmd, SIGNAL( finished() ), this, SLOT( lastOpApplied() ) ); } + Database::instance()->enqueue( QSharedPointer( cmd ) ); return; } diff --git a/src/libtomahawk/network/servent.cpp b/src/libtomahawk/network/servent.cpp index 3c0967d34..5d27e6e8c 100644 --- a/src/libtomahawk/network/servent.cpp +++ b/src/libtomahawk/network/servent.cpp @@ -603,13 +603,13 @@ Servent::claimOffer( ControlConnection* cc, const QString &key, const QHostAddre QSharedPointer Servent::remoteIODeviceFactory( const result_ptr& result ) { - qDebug() << Q_FUNC_INFO << thread() ; + qDebug() << Q_FUNC_INFO << thread(); QSharedPointer sp; - QStringList parts = result->url().mid( QString( "servent://" ).length()).split( "\t" ); - const QString& sourceName = parts.at( 0 ); - const QString& fileId = parts.at( 1 ); - const source_ptr& s = SourceList::instance()->get( sourceName ); + QStringList parts = result->url().mid( QString( "servent://" ).length() ).split( "\t" ); + const QString sourceName = parts.at( 0 ); + const QString fileId = parts.at( 1 ); + source_ptr s = SourceList::instance()->get( sourceName ); if ( s.isNull() ) return sp; diff --git a/src/libtomahawk/pipeline.cpp b/src/libtomahawk/pipeline.cpp index 4344d4956..158ceb203 100644 --- a/src/libtomahawk/pipeline.cpp +++ b/src/libtomahawk/pipeline.cpp @@ -97,7 +97,7 @@ Pipeline::add( const QList& qlist, bool prioritized ) m_queries_pending.append( qlist ); } - if ( m_index_ready ) + if ( m_index_ready && m_queries_pending.count() ) shuntNext(); } @@ -143,7 +143,10 @@ void Pipeline::shuntNext() { if ( m_queries_pending.isEmpty() ) + { + emit idle(); return; + } /* Since resolvers are async, we now dispatch to the highest weighted ones diff --git a/src/libtomahawk/pipeline.h b/src/libtomahawk/pipeline.h index 26429ea0e..873a91efa 100644 --- a/src/libtomahawk/pipeline.h +++ b/src/libtomahawk/pipeline.h @@ -53,6 +53,9 @@ public slots: void add( const QList& qlist, bool prioritized = true ); void databaseReady(); +signals: + void idle(); + private slots: void shunt( const query_ptr& q ); void shuntNext(); diff --git a/src/libtomahawk/playlist.cpp b/src/libtomahawk/playlist.cpp index cf8c32ff8..26fd23cbd 100644 --- a/src/libtomahawk/playlist.cpp +++ b/src/libtomahawk/playlist.cpp @@ -18,14 +18,14 @@ using namespace Tomahawk; void -PlaylistEntry::setQueryvariant( const QVariant& v ) +PlaylistEntry::setQueryVariant( const QVariant& v ) { m_query = query_ptr( new Query( v ) ); } QVariant -PlaylistEntry::queryvariant() const +PlaylistEntry::queryVariant() const { return m_query->toVariant(); } @@ -51,6 +51,7 @@ Playlist::Playlist( const source_ptr& src, , m_shared( shared ) { qDebug() << Q_FUNC_INFO << "1"; + init(); } @@ -70,6 +71,15 @@ Playlist::Playlist( const source_ptr& author, , m_shared( shared ) { qDebug() << Q_FUNC_INFO << "2"; + init(); +} + + +void +Playlist::init() +{ + m_locallyChanged = false; + connect( Pipeline::instance(), SIGNAL( idle() ), SLOT( onResolvingFinished() ) ); } @@ -186,9 +196,6 @@ Playlist::loadRevision( const QString& rev ) void Playlist::createNewRevision( const QString& newrev, const QString& oldrev, const QList< plentry_ptr >& entries ) { - // qDebug() << "m_entries guids:"; - // foreach( plentry_ptr pp, m_entries ) qDebug() << pp->guid(); - QSet currentguids; foreach( plentry_ptr p, m_entries ) currentguids.insert( p->guid() ); // could be cached as member? @@ -199,12 +206,13 @@ Playlist::createNewRevision( const QString& newrev, const QString& oldrev, const foreach( plentry_ptr p, entries ) { orderedguids << p->guid(); - if( !currentguids.contains(p->guid()) ) + if( !currentguids.contains( p->guid() ) ) added << p; } - // source making the change (localy user in this case) + // source making the change (local user in this case) source_ptr author = SourceList::instance()->getLocal(); + // command writes new rev to DB and calls setRevision, which emits our signal DatabaseCommand_SetPlaylistRevision* cmd = new DatabaseCommand_SetPlaylistRevision( author, @@ -212,7 +220,8 @@ Playlist::createNewRevision( const QString& newrev, const QString& oldrev, const newrev, oldrev, orderedguids, - added ); + added, + entries ); Database::instance()->enqueue( QSharedPointer( cmd ) ); } @@ -229,9 +238,6 @@ Playlist::setRevision( const QString& rev, { if( QThread::currentThread() != thread() ) { - //qDebug() << "Calling setRevision in correct thread, instead of" - // << QThread::currentThread(); - QMetaObject::invokeMethod( this, "setRevision", Qt::BlockingQueuedConnection, @@ -239,15 +245,11 @@ Playlist::setRevision( const QString& rev, Q_ARG( QList, neworderedguids ), Q_ARG( QList, oldorderedguids ), Q_ARG( bool, is_newest_rev ), - QGenericArgument( "QMap< QString,Tomahawk::plentry_ptr >" , (const void*)&addedmap ), + QGenericArgument( "QMap< QString,Tomahawk::plentry_ptr >", (const void*)&addedmap ), Q_ARG( bool, applied ) ); return; } - //qDebug() << Q_FUNC_INFO << (qlonglong)this - // << rev << neworderedguids << oldorderedguids - // << "isnewest:" << is_newest_rev << addedmap << applied << m_entries - // ; // build up correctly ordered new list of plentry_ptrs from // existing ones, and the ones that have been added @@ -255,12 +257,7 @@ Playlist::setRevision( const QString& rev, foreach( const plentry_ptr& p, m_entries ) entriesmap.insert( p->guid(), p ); - //qDebug() << "Entries map:" << entriesmap; - QList entries; - //qDebug() << "m_entries:" << m_entries.count() << m_entries; - - //qDebug() << "counters:" << neworderedguids.count() << entriesmap.count() << addedmap.count(); foreach( const QString& id, neworderedguids ) { //qDebug() << "id:" << id; @@ -276,7 +273,8 @@ Playlist::setRevision( const QString& rev, else if( addedmap.contains( id ) ) { entries.append( addedmap.value( id ) ); - if( is_newest_rev ) m_entries.append( addedmap.value( id ) ); + if( is_newest_rev ) + m_entries.append( addedmap.value( id ) ); } else { @@ -284,15 +282,12 @@ Playlist::setRevision( const QString& rev, } } - //qDebug() << Q_FUNC_INFO << rev << entries.length() << applied; - PlaylistRevision pr; pr.oldrevisionguid = m_currentrevision; pr.revisionguid = rev; // entries that have been removed: QSet removedguids = oldorderedguids.toSet().subtract( neworderedguids.toSet() ); - //qDebug() << "Removedguids:" << removedguids << "oldorederedguids" << oldorderedguids << "newog" << neworderedguids; foreach( QString remid, removedguids ) { // NB: entriesmap will contain old/removed entries only if the removal was done @@ -303,12 +298,12 @@ Playlist::setRevision( const QString& rev, if( is_newest_rev ) { //qDebug() << "Removing from m_entries" << remid; - for( int k = 0 ; kguid() == remid ) + if( m_entries.at( k )->guid() == remid ) { - //qDebug() << "removed at " << k; - m_entries.removeAt(k); + //qDebug() << "removed at" << k; + m_entries.removeAt( k ); break; } } @@ -329,11 +324,18 @@ Playlist::setRevision( const QString& rev, m_currentrevision = rev; pr.applied = applied; + foreach( const plentry_ptr& entry, m_entries ) + { + connect( entry->query().data(), SIGNAL( resultsAdded( QList ) ), + SLOT( onResultsFound( QList ) ), Qt::UniqueConnection ); + } + emit revisionLoaded( pr ); } -void Playlist::resolve() +void +Playlist::resolve() { QList< query_ptr > qlist; foreach( const plentry_ptr& p, m_entries ) @@ -344,6 +346,26 @@ void Playlist::resolve() } +void +Playlist::onResultsFound( const QList& results ) +{ + Query* query = qobject_cast( sender() ); + m_locallyChanged = true; +} + + +void +Playlist::onResolvingFinished() +{ + if ( m_locallyChanged ) + { + qDebug() << Q_FUNC_INFO; + m_locallyChanged = false; + createNewRevision( currentrevision(), currentrevision(), m_entries ); + } +} + + void Playlist::addEntry( const query_ptr& query, const QString& oldrev ) { diff --git a/src/libtomahawk/playlist.h b/src/libtomahawk/playlist.h index f198666e7..41a8c874a 100644 --- a/src/libtomahawk/playlist.h +++ b/src/libtomahawk/playlist.h @@ -22,18 +22,17 @@ class DLLEXPORT PlaylistEntry : public QObject Q_OBJECT Q_PROPERTY( QString guid READ guid WRITE setGuid ) Q_PROPERTY( QString annotation READ annotation WRITE setAnnotation ) -Q_PROPERTY( QString resulthint READ resulthint WRITE setResulthint ) Q_PROPERTY( unsigned int duration READ duration WRITE setDuration ) Q_PROPERTY( unsigned int lastmodified READ lastmodified WRITE setLastmodified ) -Q_PROPERTY( QVariant query READ queryvariant WRITE setQueryvariant ) +Q_PROPERTY( QVariant query READ queryVariant WRITE setQueryVariant ) public: void setQuery( const Tomahawk::query_ptr& q ) { m_query = q; } const Tomahawk::query_ptr& query() const { return m_query; } // I wish Qt did this for me once i specified the Q_PROPERTIES: - void setQueryvariant( const QVariant& v ); - QVariant queryvariant() const; + void setQueryVariant( const QVariant& v ); + QVariant queryVariant() const; QString guid() const { return m_guid; } void setGuid( const QString& s ) { m_guid = s; } @@ -41,8 +40,8 @@ public: QString annotation() const { return m_annotation; } void setAnnotation( const QString& s ) { m_annotation = s; } - QString resulthint() const { return m_resulthint; } - void setResulthint( const QString& s ) { m_resulthint= s; } + QString resultHint() const { return m_resulthint; } + void setResultHint( const QString& s ) { m_resulthint= s; } unsigned int duration() const { return m_duration; } void setDuration( unsigned int i ) { m_duration = i; } @@ -50,8 +49,8 @@ public: unsigned int lastmodified() const { return m_lastmodified; } void setLastmodified( unsigned int i ) { m_lastmodified = i; } - source_ptr lastsource() const { return m_lastsource; } - void setLastsource( source_ptr s ) { m_lastsource = s; } + source_ptr lastSource() const { return m_lastsource; } + void setLastSource( source_ptr s ) { m_lastsource = s; } private: QString m_guid; @@ -159,6 +158,10 @@ public slots: void resolve(); +private slots: + void onResultsFound( const QList& results ); + void onResolvingFinished(); + private: // called from loadAllPlaylists DB cmd: explicit Playlist( const source_ptr& src, @@ -178,6 +181,7 @@ private: const QString& creator, bool shared ); + void init(); void rundb(); source_ptr m_source; @@ -187,7 +191,7 @@ private: bool m_shared; QList< plentry_ptr > m_entries; - + bool m_locallyChanged; }; }; diff --git a/src/libtomahawk/sourcelist.cpp b/src/libtomahawk/sourcelist.cpp index 7d2dc94ee..a106926ee 100644 --- a/src/libtomahawk/sourcelist.cpp +++ b/src/libtomahawk/sourcelist.cpp @@ -45,12 +45,13 @@ SourceList::add( const Tomahawk::source_ptr& s ) Q_ASSERT( s->id() ); m_sources_id2name.insert( s->id(), s->userName() ); } - qDebug() << "SourceList::add(" << s->userName() << "), total sources now:" << m_sources.size(); if( s->isLocal() ) { Q_ASSERT( m_local.isNull() ); m_local = s; } + + qDebug() << "SourceList::add(" << s->userName() << "), total sources now:" << m_sources.size(); } emit sourceAdded( s ); @@ -86,7 +87,7 @@ SourceList::remove( Tomahawk::Source* s ) void SourceList::removeAllRemote() { - foreach( source_ptr s, m_sources ) + foreach( const source_ptr& s, m_sources ) { if( s != m_local ) remove( s ); diff --git a/src/libtomahawk/sourcelist.h b/src/libtomahawk/sourcelist.h index ad31b2c92..1f3363e97 100644 --- a/src/libtomahawk/sourcelist.h +++ b/src/libtomahawk/sourcelist.h @@ -26,9 +26,10 @@ public: void removeAllRemote(); QList sources() const; + unsigned int count() const; + Tomahawk::source_ptr get( const QString& username ) const; Tomahawk::source_ptr get( unsigned int id ) const; - unsigned int count() const; signals: void sourceAdded( const Tomahawk::source_ptr& ); diff --git a/src/playlist/playlistmodel.cpp b/src/playlist/playlistmodel.cpp index 531948582..acfb853c0 100644 --- a/src/playlist/playlistmodel.cpp +++ b/src/playlist/playlistmodel.cpp @@ -73,7 +73,7 @@ PlaylistModel::loadPlaylist( const Tomahawk::playlist_ptr& playlist ) { int c = rowCount( QModelIndex() ); - qDebug() << "starting loading" << playlist->title(); + qDebug() << "Starting loading" << playlist->title(); emit loadingStarts(); emit beginInsertRows( QModelIndex(), c, c + entries.count() - 1 ); @@ -133,6 +133,7 @@ PlaylistModel::loadHistory( const Tomahawk::source_ptr& source, unsigned int amo setReadOnly( true ); DatabaseCommand_PlaybackHistory* cmd = new DatabaseCommand_PlaybackHistory( source ); + cmd->setLimit( amount ); connect( cmd, SIGNAL( tracks( QList ) ), SLOT( onTracksAdded( QList ) ), Qt::QueuedConnection ); diff --git a/src/sourcetree/sourcetreeitemwidget.cpp b/src/sourcetree/sourcetreeitemwidget.cpp index 3bb46ba63..2102f5488 100644 --- a/src/sourcetree/sourcetreeitemwidget.cpp +++ b/src/sourcetree/sourcetreeitemwidget.cpp @@ -131,6 +131,7 @@ SourceTreeItemWidget::onLoadingStateChanged( DBSyncConnection::State newstate, D void SourceTreeItemWidget::onPlaybackStarted( const Tomahawk::query_ptr& query ) { + qDebug() << Q_FUNC_INFO << query->toString(); ui->activityLabel->setText( tr( "Playing: %1 by %2" ).arg( query->track() ).arg( query->artist() ) ); } From 3eeec206f1157607af875de7cde737e97023665a Mon Sep 17 00:00:00 2001 From: Christian Muehlhaeuser Date: Sun, 9 Jan 2011 07:42:31 +0100 Subject: [PATCH 3/4] * Fixed crash in DbSyncConnection. --- README | 2 +- src/libtomahawk/database/databasecommand.cpp | 2 +- src/libtomahawk/network/dbsyncconnection.cpp | 2 +- src/libtomahawk/network/servent.cpp | 2 +- src/libtomahawk/network/servent.h | 2 +- 5 files changed, 5 insertions(+), 5 deletions(-) diff --git a/README b/README index 675a066e2..d5207ea64 100644 --- a/README +++ b/README @@ -95,7 +95,7 @@ To build the app: ----------------- $ mkdir build && cd build - Pick one of the following two choices. If unsure pick the second one, you probably want a GUI. + 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 diff --git a/src/libtomahawk/database/databasecommand.cpp b/src/libtomahawk/database/databasecommand.cpp index 78ec8d71c..0cf98038c 100644 --- a/src/libtomahawk/database/databasecommand.cpp +++ b/src/libtomahawk/database/databasecommand.cpp @@ -93,7 +93,7 @@ DatabaseCommand::factory( const QVariant& op, const source_ptr& source ) return cmd; } - qDebug() << "ERRROR in" << Q_FUNC_INFO << name; + qDebug() << "ERROR in" << Q_FUNC_INFO << name; // Q_ASSERT( false ); return NULL; } diff --git a/src/libtomahawk/network/dbsyncconnection.cpp b/src/libtomahawk/network/dbsyncconnection.cpp index 9b860d1fd..2d2eb8842 100644 --- a/src/libtomahawk/network/dbsyncconnection.cpp +++ b/src/libtomahawk/network/dbsyncconnection.cpp @@ -204,7 +204,7 @@ DBSyncConnection::handleMsg( msg_ptr msg ) DatabaseCommand *cmd = DatabaseCommand::factory( m, m_source ); if ( !cmd ) { - qDebug() << "UNKNOWN DBOP CMD" << cmd->commandname() << cmd->guid(); + qDebug() << "UNKNOWN DBOP CMD"; if( !msg->is( Msg::FRAGMENT ) ) // last msg in this batch lastOpApplied(); diff --git a/src/libtomahawk/network/servent.cpp b/src/libtomahawk/network/servent.cpp index 5d27e6e8c..73cc749db 100644 --- a/src/libtomahawk/network/servent.cpp +++ b/src/libtomahawk/network/servent.cpp @@ -100,7 +100,7 @@ Servent::startListening( QHostAddress ha, bool upnp, int port ) // last.fm office firewall policy hack // (corp. firewall allows outgoing connections to this port, // so listen on this if you want lastfmers to connect to you) - if( qApp->arguments().contains("--porthack") ) + if( qApp->arguments().contains( "--porthack" ) ) { tryport = 3389; pf->remove( tryport ); diff --git a/src/libtomahawk/network/servent.h b/src/libtomahawk/network/servent.h index 9082ce14a..f0e2884bb 100644 --- a/src/libtomahawk/network/servent.h +++ b/src/libtomahawk/network/servent.h @@ -91,7 +91,7 @@ public: void reverseOfferRequest( ControlConnection* orig_conn, const QString& key, const QString& theirkey ); void setExternalAddress( QHostAddress ha, int port ); - bool visibleExternally() const { return m_externalPort > 0; } + bool visibleExternally() const { return m_externalPort > 0 && !m_externalAddress.isNull(); } QHostAddress externalAddress() const { return m_externalAddress; } int externalPort() const { return m_externalPort; } From 18d5d43695f5658d48aeaa1218d45e3e045d8132 Mon Sep 17 00:00:00 2001 From: Christian Muehlhaeuser Date: Sun, 9 Jan 2011 10:57:30 +0100 Subject: [PATCH 4/4] * Temporary fix for infinite loop inside FLAC transcoder. --- src/audio/flactranscode.cpp | 28 ++++++++---------- src/audio/flactranscode.h | 10 ++----- .../databasecommand_collectionstats.cpp | 29 ++++++------------- 3 files changed, 24 insertions(+), 43 deletions(-) diff --git a/src/audio/flactranscode.cpp b/src/audio/flactranscode.cpp index e21171e13..bb5e889f4 100644 --- a/src/audio/flactranscode.cpp +++ b/src/audio/flactranscode.cpp @@ -2,13 +2,13 @@ FLACTranscode::FLACTranscode() - : m_FLACInit( false ) - , m_FLACRunning( false ) + : m_FLACRunning( false ) , m_finished( false ) { qDebug() << Q_FUNC_INFO; init(); + set_metadata_respond_all(); } @@ -33,7 +33,6 @@ FLACTranscode::clearBuffers() { QMutexLocker locker( &m_mutex ); - m_FLACInit = false; m_FLACRunning = false; m_finished = false; @@ -52,14 +51,7 @@ FLACTranscode::processData( const QByteArray& data, bool finish ) m_buffer.append( data ); m_mutex.unlock(); - if ( !m_FLACInit && m_buffer.size() > FLAC_BUFFER ) - { - m_FLACInit = true; - set_metadata_respond_all(); - process_single(); - } - - while ( m_buffer.size() > FLAC_BUFFER / 2 ) + while ( m_buffer.size() >= FLAC_BUFFER ) { process_single(); } @@ -79,10 +71,7 @@ FLACTranscode::read_callback( FLAC__byte buffer[], size_t *bytes ) memcpy( buffer, (char*)m_buffer.data(), *bytes ); m_buffer.remove( 0, *bytes ); -// if ( !*bytes ) -// return FLAC__STREAM_DECODER_READ_STATUS_ABORT; -// else - return FLAC__STREAM_DECODER_READ_STATUS_CONTINUE; + return FLAC__STREAM_DECODER_READ_STATUS_CONTINUE; } @@ -110,10 +99,17 @@ FLACTranscode::write_callback( const ::FLAC__Frame *frame, const FLAC__int32 *co } +::FLAC__StreamDecoderSeekStatus +FLACTranscode::seek_callback(FLAC__uint64 absolute_byte_offset) +{ + return FLAC__STREAM_DECODER_SEEK_STATUS_UNSUPPORTED; +} + + void FLACTranscode::metadata_callback( const ::FLAC__StreamMetadata *metadata ) { - qDebug() << Q_FUNC_INFO; + qDebug() << Q_FUNC_INFO << metadata->is_last; switch ( metadata->type ) { diff --git a/src/audio/flactranscode.h b/src/audio/flactranscode.h index f668fbb30..ba65a0185 100644 --- a/src/audio/flactranscode.h +++ b/src/audio/flactranscode.h @@ -15,7 +15,7 @@ #include #include -#define FLAC_BUFFER 32768 +#define FLAC_BUFFER 32768 * 36 #define FLAC_BUFFER_PREFERRED 32768 class FLACTranscode : public TranscodeInterface , protected FLAC::Decoder::Stream @@ -49,21 +49,17 @@ class FLACTranscode : public TranscodeInterface , protected FLAC::Decoder::Strea protected: virtual ::FLAC__StreamDecoderReadStatus read_callback( FLAC__byte buffer[], size_t *bytes ); virtual ::FLAC__StreamDecoderWriteStatus write_callback( const ::FLAC__Frame *frame, const FLAC__int32 *const buffer[] ); + virtual ::FLAC__StreamDecoderSeekStatus seek_callback( FLAC__uint64 absolute_byte_offset ); virtual bool eof_callback(); - void metadata_callback( const ::FLAC__StreamMetadata *metadata ); + virtual void metadata_callback( const ::FLAC__StreamMetadata *metadata ); void error_callback( ::FLAC__StreamDecoderErrorStatus status ); -/* ::FLAC__StreamDecoderSeekStatus seek_callback( FLAC__uint64 absolute_byte_offset ); - ::FLAC__StreamDecoderTellStatus tell_callback( FLAC__uint64 *absolute_byte_offset ); - ::FLAC__StreamDecoderLengthStatus length_callback( FLAC__uint64 *stream_length );*/ - private: QByteArray m_outBuffer; QMutex m_mutex; QByteArray m_buffer; - bool m_FLACInit; bool m_FLACRunning; bool m_finished; }; diff --git a/src/libtomahawk/database/databasecommand_collectionstats.cpp b/src/libtomahawk/database/databasecommand_collectionstats.cpp index 4d2ce5ccf..587c7d1b3 100644 --- a/src/libtomahawk/database/databasecommand_collectionstats.cpp +++ b/src/libtomahawk/database/databasecommand_collectionstats.cpp @@ -14,33 +14,25 @@ DatabaseCommand_CollectionStats::DatabaseCommand_CollectionStats( const source_p void DatabaseCommand_CollectionStats::exec( DatabaseImpl* dbi ) { - //qDebug() << Q_FUNC_INFO; - Q_ASSERT( !source().isNull() ); - TomahawkSqlQuery query = dbi->newquery(); - Q_ASSERT( source()->isLocal() || source()->id() >= 1 ); + TomahawkSqlQuery query = dbi->newquery(); if( source()->isLocal() ) { - query.exec("SELECT count(*), max(mtime), (SELECT guid FROM oplog WHERE source IS NULL ORDER BY id DESC LIMIT 1) " - "FROM file " - "WHERE source IS NULL"); + query.exec( "SELECT count(*), max(mtime), (SELECT guid FROM oplog WHERE source IS NULL ORDER BY id DESC LIMIT 1) " + "FROM file " + "WHERE source IS NULL" ); } else { - query.prepare("SELECT count(*), max(mtime), " - " (SELECT lastop FROM source WHERE id = ?) " - "FROM file " - "WHERE source = ?" - ); + query.prepare( "SELECT count(*), max(mtime), (SELECT lastop FROM source WHERE id = ?) " + "FROM file " + "WHERE source = ?" ); query.addBindValue( source()->id() ); query.addBindValue( source()->id() ); } - if( !query.exec() ) - { - qDebug() << "Failed to get collection stats:" << query.boundValues(); - throw "failed to get collection stats"; - } + + query.exec(); QVariantMap m; if( query.next() ) @@ -50,8 +42,5 @@ DatabaseCommand_CollectionStats::exec( DatabaseImpl* dbi ) m.insert( "lastop", query.value( 2 ).toString() ); } - //qDebug() << "Loaded collection stats for" - // << (source()->isLocal() ? "LOCAL" : source()->username()) - // << m; emit done( m ); }