1
0
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:
Christian Muehlhaeuser
2011-01-09 07:22:42 +01:00
parent 7a9a117307
commit 3061a95cb1
26 changed files with 466 additions and 270 deletions

54
README
View File

@@ -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!

View File

@@ -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:

View File

@@ -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
{

View File

@@ -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 );
}

View File

@@ -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 ) );

View File

@@ -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();

View File

@@ -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

View File

@@ -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];
}

View File

@@ -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

View File

@@ -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();
}
}

View File

@@ -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

View File

@@ -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;
}

View File

@@ -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 )
{

View File

@@ -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();
}

View File

@@ -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() )

View File

@@ -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 );

View File

@@ -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;
}

View File

@@ -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;

View File

@@ -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

View File

@@ -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();

View File

@@ -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 )
{

View File

@@ -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;
};
};

View File

@@ -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 );

View File

@@ -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& );

View File

@@ -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 );

View File

@@ -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() ) );
}