1
0
mirror of https://github.com/tomahawk-player/tomahawk.git synced 2025-04-19 07:22:32 +02:00

Merge branch 'master' into dynamic

Conflicts:
	src/libtomahawk/collection.h
	src/libtomahawk/database/databasecommand_loadplaylistentries.cpp
	src/libtomahawk/database/databasecommand_setplaylistrevision.cpp
	src/libtomahawk/database/databasecommand_setplaylistrevision.h
	src/libtomahawk/playlist.cpp
	src/libtomahawk/playlist.h
This commit is contained in:
Leo Franchi 2011-01-10 19:01:05 -05:00
commit 34799e36e0
33 changed files with 532 additions and 346 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 uncertain 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

@ -2,13 +2,13 @@
FLACTranscode::FLACTranscode()
: m_FLACInit( false )
, m_FLACRunning( false )
: m_FLACRunning( false )
, m_finished( false )
{
qDebug() << Q_FUNC_INFO;
init();
set_metadata_respond_all();
}
@ -33,7 +33,6 @@ FLACTranscode::clearBuffers()
{
QMutexLocker locker( &m_mutex );
m_FLACInit = false;
m_FLACRunning = false;
m_finished = false;
@ -52,14 +51,7 @@ FLACTranscode::processData( const QByteArray& data, bool finish )
m_buffer.append( data );
m_mutex.unlock();
if ( !m_FLACInit && m_buffer.size() > FLAC_BUFFER )
{
m_FLACInit = true;
set_metadata_respond_all();
process_single();
}
while ( m_buffer.size() > FLAC_BUFFER / 2 )
while ( m_buffer.size() >= FLAC_BUFFER )
{
process_single();
}
@ -79,10 +71,7 @@ FLACTranscode::read_callback( FLAC__byte buffer[], size_t *bytes )
memcpy( buffer, (char*)m_buffer.data(), *bytes );
m_buffer.remove( 0, *bytes );
// if ( !*bytes )
// return FLAC__STREAM_DECODER_READ_STATUS_ABORT;
// else
return FLAC__STREAM_DECODER_READ_STATUS_CONTINUE;
return FLAC__STREAM_DECODER_READ_STATUS_CONTINUE;
}
@ -110,10 +99,17 @@ FLACTranscode::write_callback( const ::FLAC__Frame *frame, const FLAC__int32 *co
}
::FLAC__StreamDecoderSeekStatus
FLACTranscode::seek_callback(FLAC__uint64 absolute_byte_offset)
{
return FLAC__STREAM_DECODER_SEEK_STATUS_UNSUPPORTED;
}
void
FLACTranscode::metadata_callback( const ::FLAC__StreamMetadata *metadata )
{
qDebug() << Q_FUNC_INFO;
qDebug() << Q_FUNC_INFO << metadata->is_last;
switch ( metadata->type )
{

View File

@ -15,7 +15,7 @@
#include <QMutex>
#include <QDebug>
#define FLAC_BUFFER 32768
#define FLAC_BUFFER 32768 * 36
#define FLAC_BUFFER_PREFERRED 32768
class FLACTranscode : public TranscodeInterface , protected FLAC::Decoder::Stream
@ -49,21 +49,17 @@ class FLACTranscode : public TranscodeInterface , protected FLAC::Decoder::Strea
protected:
virtual ::FLAC__StreamDecoderReadStatus read_callback( FLAC__byte buffer[], size_t *bytes );
virtual ::FLAC__StreamDecoderWriteStatus write_callback( const ::FLAC__Frame *frame, const FLAC__int32 *const buffer[] );
virtual ::FLAC__StreamDecoderSeekStatus seek_callback( FLAC__uint64 absolute_byte_offset );
virtual bool eof_callback();
void metadata_callback( const ::FLAC__StreamMetadata *metadata );
virtual void metadata_callback( const ::FLAC__StreamMetadata *metadata );
void error_callback( ::FLAC__StreamDecoderErrorStatus status );
/* ::FLAC__StreamDecoderSeekStatus seek_callback( FLAC__uint64 absolute_byte_offset );
::FLAC__StreamDecoderTellStatus tell_callback( FLAC__uint64 *absolute_byte_offset );
::FLAC__StreamDecoderLengthStatus length_callback( FLAC__uint64 *stream_length );*/
private:
QByteArray m_outBuffer;
QMutex m_mutex;
QByteArray m_buffer;
bool m_FLACInit;
bool m_FLACRunning;
bool m_finished;
};

View File

@ -93,7 +93,7 @@ DatabaseCommand::factory( const QVariant& op, const source_ptr& source )
return cmd;
}
qDebug() << "ERRROR in" << Q_FUNC_INFO << name;
qDebug() << "ERROR in" << Q_FUNC_INFO << name;
// Q_ASSERT( false );
return NULL;
}

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

@ -14,33 +14,25 @@ DatabaseCommand_CollectionStats::DatabaseCommand_CollectionStats( const source_p
void
DatabaseCommand_CollectionStats::exec( DatabaseImpl* dbi )
{
//qDebug() << Q_FUNC_INFO;
Q_ASSERT( !source().isNull() );
TomahawkSqlQuery query = dbi->newquery();
Q_ASSERT( source()->isLocal() || source()->id() >= 1 );
TomahawkSqlQuery query = dbi->newquery();
if( source()->isLocal() )
{
query.exec("SELECT count(*), max(mtime), (SELECT guid FROM oplog WHERE source IS NULL ORDER BY id DESC LIMIT 1) "
"FROM file "
"WHERE source IS NULL");
query.exec( "SELECT count(*), max(mtime), (SELECT guid FROM oplog WHERE source IS NULL ORDER BY id DESC LIMIT 1) "
"FROM file "
"WHERE source IS NULL" );
}
else
{
query.prepare("SELECT count(*), max(mtime), "
" (SELECT lastop FROM source WHERE id = ?) "
"FROM file "
"WHERE source = ?"
);
query.prepare( "SELECT count(*), max(mtime), (SELECT lastop FROM source WHERE id = ?) "
"FROM file "
"WHERE source = ?" );
query.addBindValue( source()->id() );
query.addBindValue( source()->id() );
}
if( !query.exec() )
{
qDebug() << "Failed to get collection stats:" << query.boundValues();
throw "failed to get collection stats";
}
query.exec();
QVariantMap m;
if( query.next() )
@ -50,8 +42,5 @@ DatabaseCommand_CollectionStats::exec( DatabaseImpl* dbi )
m.insert( "lastop", query.value( 2 ).toString() );
}
//qDebug() << "Loaded collection stats for"
// << (source()->isLocal() ? "LOCAL" : source()->username())
// << m;
emit done( m );
}

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

@ -43,31 +43,33 @@ void DatabaseCommand_LoadPlaylistEntries::generateEntries( DatabaseImpl* dbi )
"duration, addedon, addedby, result_hint "
"FROM playlist_item "
"WHERE guid IN %1").arg( inclause );
//qDebug() << sql;
query.exec( sql );
while( query.next() )
{
plentry_ptr e( new PlaylistEntry );
e->setGuid( query.value( 0 ).toString() );
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() );
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( "qid", uuid() );
Tomahawk::query_ptr q( new Tomahawk::Query( m ) );
e->setQuery( q );
m_entrymap.insert( e->guid(), e );
}
prevrev = query_entries.value( 4 ).toString();
//qDebug() << sql;
query.exec( sql );
while( query.next() )
{
plentry_ptr e( new PlaylistEntry );
e->setGuid( query.value( 0 ).toString() );
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() );
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 ) );
e->setQuery( q );
m_entrymap.insert( e->guid(), e );
}
prevrev = query_entries.value( 4 ).toString();
}
else
{

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,10 +12,11 @@ DatabaseCommand_SetDynamicPlaylistRevision::DatabaseCommand_SetDynamicPlaylistRe
const QString& oldrev,
const QStringList& orderedguids,
const QList< plentry_ptr >& addedentries,
const QList<plentry_ptr>& entries,
const QString& type,
GeneratorMode mode,
const QList< dyncontrol_ptr >& controls )
: DatabaseCommand_SetPlaylistRevision( s, playlistguid, newrev, oldrev, orderedguids, addedentries )
: DatabaseCommand_SetPlaylistRevision( s, playlistguid, newrev, oldrev, orderedguids, addedentries, entries )
, m_type( type )
, m_mode( mode )
, m_controls( controls )
@ -30,7 +31,7 @@ DatabaseCommand_SetDynamicPlaylistRevision::DatabaseCommand_SetDynamicPlaylistRe
const QString& type,
GeneratorMode mode,
const QList< dyncontrol_ptr >& controls )
: DatabaseCommand_SetPlaylistRevision( s, playlistguid, newrev, oldrev, QStringList(), QList< plentry_ptr >() )
: DatabaseCommand_SetPlaylistRevision( s, playlistguid, newrev, oldrev, QStringList(), QList< plentry_ptr >(), QList< plentry_ptr >() )
, m_type( type )
, m_mode( mode )
, m_controls( controls )

View File

@ -28,6 +28,7 @@ public:
const QString& oldrev,
const QStringList& orderedguids,
const QList<Tomahawk::plentry_ptr>& addedentries,
const QList<plentry_ptr>& entries,
const QString& type,
GeneratorMode mode,
const QList< dyncontrol_ptr >& controls );

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_applied( false )
, m_newrev( newrev )
, m_oldrev( oldrev )
, m_addedentries( addedentries )
, m_entries( entries )
{
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();
@ -68,7 +75,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() )
{
@ -87,38 +94,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 );
@ -131,9 +159,10 @@ DatabaseCommand_SetPlaylistRevision::exec( DatabaseImpl* lib )
// if optimistic locking is ok, update current revision to this new one
if( m_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();
@ -155,6 +184,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 )
{
@ -81,7 +85,9 @@ protected:
QString m_currentRevision;
private:
QVariantList m_orderedguids;
QList<Tomahawk::plentry_ptr> m_addedentries;
QList<Tomahawk::plentry_ptr> m_addedentries, m_entries;
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
@ -469,3 +470,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

@ -165,6 +165,7 @@ DynamicPlaylist::createNewRevision( const QString& newrev,
oldrev,
orderedguids,
added,
entries,
type,
Static,
controls );

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";
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;
}
@ -253,15 +257,11 @@ void
DBSyncConnection::sendOps()
{
qDebug() << Q_FUNC_INFO;
if ( m_lastSentOp.isEmpty() )
m_lastSentOp = m_uscache.value( "lastop" ).toString();
qDebug() << "Will send peer all ops since" << m_lastSentOp;
qDebug() << "Will send peer all ops since" << m_uscache.value( "lastop" ).toString();
source_ptr src = SourceList::instance()->getLocal();
DatabaseCommand_loadOps* cmd = new DatabaseCommand_loadOps( src, m_lastSentOp );
DatabaseCommand_loadOps* cmd = new DatabaseCommand_loadOps( src, m_uscache.value( "lastop" ).toString() );
connect( cmd, SIGNAL( done( QString, QString, QList< dbop_ptr > ) ),
this, SLOT( sendOpsData( QString, QString, QList< dbop_ptr > ) ) );
@ -273,8 +273,11 @@ void
DBSyncConnection::sendOpsData( QString sinceguid, QString lastguid, QList< dbop_ptr > ops )
{
qDebug() << Q_FUNC_INFO << sinceguid << lastguid << "Num ops to send:" << ops.length();
m_lastSentOp = lastguid;
if ( m_lastSentOp == lastguid )
ops.clear();
m_lastSentOp = lastguid;
if( ops.length() == 0 )
{
sendMsg( Msg::factory( "ok", Msg::DBOP ) );

View File

@ -100,7 +100,7 @@ Servent::startListening( QHostAddress ha, bool upnp, int port )
// last.fm office firewall policy hack
// (corp. firewall allows outgoing connections to this port,
// so listen on this if you want lastfmers to connect to you)
if( qApp->arguments().contains("--porthack") )
if( qApp->arguments().contains( "--porthack" ) )
{
tryport = 3389;
pf->remove( tryport );
@ -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

@ -91,7 +91,7 @@ public:
void reverseOfferRequest( ControlConnection* orig_conn, const QString& key, const QString& theirkey );
void setExternalAddress( QHostAddress ha, int port );
bool visibleExternally() const { return m_externalPort > 0; }
bool visibleExternally() const { return m_externalPort > 0 && !m_externalAddress.isNull(); }
QHostAddress externalAddress() const { return m_externalAddress; }
int externalPort() const { return m_externalPort; }

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

@ -20,14 +20,14 @@ PlaylistEntry::PlaylistEntry() {}
PlaylistEntry::~PlaylistEntry() {}
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();
}
@ -46,13 +46,13 @@ PlaylistEntry::query() const
source_ptr
PlaylistEntry::lastsource() const
PlaylistEntry::lastSource() const
{
return m_lastsource;
}
void
PlaylistEntry::setLastsource( source_ptr s )
PlaylistEntry::setLastSource( source_ptr s )
{
m_lastsource = s;
}
@ -84,6 +84,7 @@ Playlist::Playlist( const source_ptr& src,
, m_shared( shared )
{
qDebug() << Q_FUNC_INFO << "1";
init();
}
@ -103,6 +104,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() ) );
}
Playlist::~Playlist() {}
@ -227,7 +237,7 @@ Playlist::createNewRevision( const QString& newrev, const QString& oldrev, const
foreach( plentry_ptr p, entries )
orderedguids << p->guid();
// 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 =
@ -236,7 +246,9 @@ Playlist::createNewRevision( const QString& newrev, const QString& oldrev, const
newrev,
oldrev,
orderedguids,
added );
added,
entries
);
Database::instance()->enqueue( QSharedPointer<DatabaseCommand>( cmd ) );
}
@ -253,9 +265,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,
@ -263,7 +272,7 @@ 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;
@ -274,7 +283,13 @@ Playlist::setRevision( const QString& rev,
if( applied )
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 );
}
@ -285,22 +300,16 @@ Playlist::setNewRevision( const QString& rev,
bool is_newest_rev,
const QMap< QString, Tomahawk::plentry_ptr >& addedmap )
{
qDebug() << Q_FUNC_INFO << (qlonglong)this
<< rev << neworderedguids << oldorderedguids
<< "isnewest:" << is_newest_rev << addedmap << m_entries;
// build up correctly ordered new list of plentry_ptrs from
// existing ones, and the ones that have been added
QMap<QString, plentry_ptr> entriesmap;
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;
@ -316,7 +325,8 @@ Playlist::setNewRevision( 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
{
@ -343,12 +353,12 @@ Playlist::setNewRevision( 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;
}
}
@ -358,10 +368,6 @@ Playlist::setNewRevision( const QString& rev,
pr.added = addedmap.values();
//qDebug() << "Revision set:" << rev
// << "added" << pr.added.size()
// << "removed" << pr.removed.size()
// << "total entries" << m_entries.size();
pr.newlist = entries;
return pr;
@ -373,7 +379,8 @@ Playlist::author()
return m_source;
}
void Playlist::resolve()
void
Playlist::resolve()
{
QList< query_ptr > qlist;
foreach( const plentry_ptr& p, m_entries )
@ -383,6 +390,24 @@ void Playlist::resolve()
Pipeline::instance()->add( qlist );
}
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 )
@ -440,7 +465,7 @@ Playlist::newEntries( const QList< plentry_ptr >& entries )
QList<plentry_ptr> added;
foreach( plentry_ptr p, entries )
{
if( !currentguids.contains(p->guid()) )
if( !currentguids.contains( p->guid() ) )
added << p;
}
return added;

View File

@ -24,10 +24,10 @@ 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( 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:
PlaylistEntry();
@ -37,8 +37,8 @@ public:
const Tomahawk::query_ptr& query() const;
// 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; }
@ -46,8 +46,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; }
@ -55,8 +55,8 @@ public:
unsigned int lastmodified() const { return m_lastmodified; }
void setLastmodified( unsigned int i ) { m_lastmodified = i; }
source_ptr lastsource() const;
void setLastsource( source_ptr s );
source_ptr lastSource() const;
void setLastSource( source_ptr s );
private:
QString m_guid;
@ -188,8 +188,14 @@ protected:
const QMap< QString, Tomahawk::plentry_ptr >& addedmap );
QList<plentry_ptr> addEntriesInternal( const QList<Tomahawk::query_ptr>& queries );
private slots:
void onResultsFound( const QList<Tomahawk::result_ptr>& results );
void onResolvingFinished();
private:
Playlist();
void init();
source_ptr m_source;
QString m_currentrevision;
@ -198,6 +204,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() ) );
}