mirror of
https://github.com/tomahawk-player/tomahawk.git
synced 2025-07-31 19:30:21 +02:00
* 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.
This commit is contained in:
54
README
54
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!
|
||||
|
@@ -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:
|
||||
|
@@ -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
|
||||
{
|
||||
|
@@ -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 );
|
||||
}
|
||||
|
@@ -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 ) );
|
||||
|
@@ -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();
|
||||
|
@@ -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<Tomahawk::query_ptr>& queries );
|
||||
|
||||
private:
|
||||
unsigned int m_amount;
|
||||
};
|
||||
|
||||
#endif // DATABASECOMMAND_PLAYBACKHISTORY_H
|
||||
|
@@ -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<QString, QVariant> 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<Tomahawk::result_ptr> 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<int,float> scorepair_t;
|
||||
typedef QPair<int, float> 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<Tomahawk::result_ptr> 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<int> > 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<int> 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];
|
||||
}
|
||||
|
@@ -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<Tomahawk::result_ptr> 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<int> > 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<int> 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
|
||||
|
@@ -12,13 +12,17 @@ DatabaseCommand_SetPlaylistRevision::DatabaseCommand_SetPlaylistRevision(
|
||||
const QString& newrev,
|
||||
const QString& oldrev,
|
||||
const QStringList& orderedguids,
|
||||
const QList<plentry_ptr>& addedentries )
|
||||
const QList<plentry_ptr>& addedentries,
|
||||
const QList<plentry_ptr>& 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();
|
||||
}
|
||||
}
|
||||
|
@@ -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<Tomahawk::plentry_ptr>& addedentries );
|
||||
const QList<Tomahawk::plentry_ptr>& addedentries,
|
||||
const QList<Tomahawk::plentry_ptr>& 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<Tomahawk::plentry_ptr> m_addedentries;
|
||||
QList<Tomahawk::plentry_ptr> m_addedentries, m_entries;
|
||||
bool m_applied;
|
||||
QMap<QString, Tomahawk::plentry_ptr> m_addedmap;
|
||||
|
||||
bool m_localOnly;
|
||||
};
|
||||
|
||||
#endif // DATABASECOMMAND_SETPLAYLISTREVISION_H
|
||||
|
@@ -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;
|
||||
}
|
||||
|
@@ -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<int,float>& left, const QPair<int,float>& right )
|
||||
{
|
||||
|
@@ -71,7 +71,7 @@ DatabaseWorker::doWork( QSharedPointer<DatabaseCommand> 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<DatabaseCommand> cmd )
|
||||
|
||||
if( cmd->doesMutates() )
|
||||
{
|
||||
qDebug() << "Comitting" << cmd->commandname();;
|
||||
qDebug() << "Committing" << cmd->commandname();;
|
||||
if( !m_dbimpl->database().commit() )
|
||||
{
|
||||
|
||||
@@ -152,6 +152,7 @@ DatabaseWorker::doWork( QSharedPointer<DatabaseCommand> cmd )
|
||||
Q_ASSERT( false );
|
||||
throw;
|
||||
}
|
||||
|
||||
cmd->emitFinished();
|
||||
}
|
||||
|
||||
|
@@ -4,6 +4,7 @@
|
||||
|
||||
#include <QTime>
|
||||
|
||||
|
||||
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<quint32, quint16> >& 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<quint32, quint16> >& 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<quint32, quint16> >& 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<quint32, quint16> > tomerge )
|
||||
void FuzzyIndex::mergeIndex( const QString& table, const QHash< QString, QMap<quint32, quint16> >& tomerge )
|
||||
{
|
||||
qDebug() << Q_FUNC_INFO << table << tomerge.keys().size();
|
||||
|
||||
@@ -75,9 +78,10 @@ void FuzzyIndex::mergeIndex( const QString& table, QHash< QString, QMap<quint32,
|
||||
if ( table == "artist" ) idx = &m_artist_ngrams;
|
||||
else if( table == "album" ) idx = &m_album_ngrams;
|
||||
else if( table == "track" ) idx = &m_track_ngrams;
|
||||
else Q_ASSERT(false);
|
||||
else Q_ASSERT( false );
|
||||
|
||||
if( tomerge.size() == 0 ) return;
|
||||
if( tomerge.size() == 0 )
|
||||
return;
|
||||
|
||||
if( idx->size() == 0 )
|
||||
{
|
||||
@@ -85,12 +89,13 @@ void FuzzyIndex::mergeIndex( const QString& table, QHash< QString, QMap<quint32,
|
||||
}
|
||||
else
|
||||
{
|
||||
foreach( const QString& ngram, tomerge.keys() )
|
||||
QList<QString> tmk = tomerge.keys();
|
||||
foreach( const QString& ngram, tmk )
|
||||
{
|
||||
|
||||
if( idx->contains( ngram ) )
|
||||
{
|
||||
foreach( quint32 id, tomerge[ngram].keys() )
|
||||
QList<unsigned int> 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, QMap<quint32,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
qDebug() << Q_FUNC_INFO << table << "merge complete, num items:" << tomerge.size();
|
||||
}
|
||||
|
||||
@@ -120,6 +126,7 @@ FuzzyIndex::search( const QString& table, const QString& name )
|
||||
{
|
||||
if( !idx->contains( ngram ) )
|
||||
continue;
|
||||
|
||||
//qDebug() << name_orig << "NGRAM:" << ngram << "candidates:" << (*idx)[ngram].size();
|
||||
QMapIterator<quint32, quint16> iter( (*idx)[ngram] );
|
||||
while( iter.hasNext() )
|
||||
|
@@ -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<quint32, quint16> > tomerge );
|
||||
void mergeIndex( const QString& table, const QHash< QString, QMap<quint32, quint16> >& tomerge );
|
||||
|
||||
private:
|
||||
void loadNgramIndex_helper( QHash< QString, QMap<quint32, quint16> >& idx, const QString& table, unsigned int fromkey = 0 );
|
||||
|
@@ -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<DatabaseCommand>( cmd ) );
|
||||
return;
|
||||
}
|
||||
|
@@ -603,13 +603,13 @@ Servent::claimOffer( ControlConnection* cc, const QString &key, const QHostAddre
|
||||
QSharedPointer<QIODevice>
|
||||
Servent::remoteIODeviceFactory( const result_ptr& result )
|
||||
{
|
||||
qDebug() << Q_FUNC_INFO << thread() ;
|
||||
qDebug() << Q_FUNC_INFO << thread();
|
||||
QSharedPointer<QIODevice> 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;
|
||||
|
||||
|
@@ -97,7 +97,7 @@ Pipeline::add( const QList<query_ptr>& 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
|
||||
|
@@ -53,6 +53,9 @@ public slots:
|
||||
void add( const QList<query_ptr>& qlist, bool prioritized = true );
|
||||
void databaseReady();
|
||||
|
||||
signals:
|
||||
void idle();
|
||||
|
||||
private slots:
|
||||
void shunt( const query_ptr& q );
|
||||
void shuntNext();
|
||||
|
@@ -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<QString> 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<DatabaseCommand>( 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<QString>, neworderedguids ),
|
||||
Q_ARG( QList<QString>, 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<plentry_ptr> 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<QString> 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 ; k<m_entries.length(); ++k )
|
||||
for( int k = 0 ; k < m_entries.length(); ++k )
|
||||
{
|
||||
if( m_entries.at(k)->guid() == 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<Tomahawk::result_ptr> ) ),
|
||||
SLOT( onResultsFound( QList<Tomahawk::result_ptr> ) ), 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<Tomahawk::result_ptr>& results )
|
||||
{
|
||||
Query* query = qobject_cast<Query*>( 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 )
|
||||
{
|
||||
|
@@ -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<Tomahawk::result_ptr>& 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;
|
||||
};
|
||||
|
||||
};
|
||||
|
@@ -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 );
|
||||
|
@@ -26,9 +26,10 @@ public:
|
||||
void removeAllRemote();
|
||||
|
||||
QList<Tomahawk::source_ptr> 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& );
|
||||
|
@@ -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<Tomahawk::query_ptr> ) ),
|
||||
SLOT( onTracksAdded( QList<Tomahawk::query_ptr> ) ), Qt::QueuedConnection );
|
||||
|
@@ -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() ) );
|
||||
}
|
||||
|
||||
|
Reference in New Issue
Block a user