1
0
mirror of https://github.com/tomahawk-player/tomahawk.git synced 2025-04-13 04:21:51 +02:00

* Two new DatabaseCommands: LogPlayback and PlaybackHistory.

* LogPlayback keeps track of recently played songs in the database.
* PlaybackHistory retrieves playback logs from the database.
* Added "recently-played" views to WelcomeWidget & SourceInfoWidget.
* Needs testing.
This commit is contained in:
Christian Muehlhaeuser 2011-01-04 09:37:04 +01:00
parent f3b7375779
commit 5863dc7e14
23 changed files with 380 additions and 34 deletions

View File

@ -4,6 +4,9 @@
#include <QMutexLocker>
#include "playlistinterface.h"
#include "database/database.h"
#include "database/databasecommand_logplayback.h"
#include "network/servent.h"
#include "madtranscode.h"
@ -20,6 +23,7 @@ AudioEngine::AudioEngine()
, m_playlist( 0 )
, m_currentTrackPlaylist( 0 )
, m_queue( 0 )
, m_timeElapsed( 0 )
, m_i( 0 )
{
qDebug() << "Init AudioEngine";
@ -155,7 +159,12 @@ AudioEngine::loadTrack( const Tomahawk::result_ptr& result )
{
m_lastTrack = m_currentTrack;
if ( !m_lastTrack.isNull() )
{
DatabaseCommand_LogPlayback* cmd = new DatabaseCommand_LogPlayback( m_lastTrack, m_timeElapsed );
Database::instance()->enqueue( QSharedPointer<DatabaseCommand>(cmd) );
emit finished( m_lastTrack );
}
m_currentTrack = result;
io = Servent::instance()->getIODeviceForUrl( m_currentTrack );
@ -321,6 +330,7 @@ AudioEngine::setStreamData( long sampleRate, int channels )
void
AudioEngine::timerTriggered( unsigned int seconds )
{
m_timeElapsed = seconds;
emit timerSeconds( seconds );
if ( m_currentTrack->duration() == 0 )
@ -407,7 +417,6 @@ AudioEngine::loop()
// are we cleanly at the end of a track, and ready for the next one?
if ( !m_input.isNull() &&
m_input->atEnd() &&
// m_input->isOpen() &&
!m_input->bytesAvailable() &&
!m_audio->haveData() &&
!m_audio->isPaused() )

View File

@ -100,6 +100,7 @@ private:
PlaylistInterface* m_queue;
QMutex m_mutex;
unsigned int m_timeElapsed;
int m_i;
};

View File

@ -7,6 +7,7 @@
#include "playlist/playlistmanager.h"
#include "playlist/albummodel.h"
#include "playlist/collectionflatmodel.h"
#include "playlist/playlistmodel.h"
#include "database/databasecommand_alltracks.h"
#include "database/databasecommand_allalbums.h"
@ -24,8 +25,17 @@ SourceInfoWidget::SourceInfoWidget( const Tomahawk::source_ptr& source, QWidget*
ui->recentCollectionView->setModel( m_recentCollectionModel );
m_recentCollectionModel->addFilteredCollection( source->collection(), 250, DatabaseCommand_AllTracks::ModificationTime );
// ui->recentCollectionView->setColumnHidden( TrackModel::Bitrate, true );
// ui->recentCollectionView->setColumnHidden( TrackModel::Origin, true );
m_historyModel = new PlaylistModel( ui->historyView );
ui->historyView->setModel( m_historyModel );
m_historyModel->loadHistory( source );
ui->recentCollectionView->setColumnHidden( TrackModel::Bitrate, true );
ui->recentCollectionView->setColumnHidden( TrackModel::Origin, true );
ui->recentCollectionView->setColumnHidden( TrackModel::Filesize, true );
ui->historyView->setColumnHidden( TrackModel::Bitrate, true );
ui->historyView->setColumnHidden( TrackModel::Origin, true );
ui->historyView->setColumnHidden( TrackModel::Filesize, true );
m_recentAlbumModel = new AlbumModel( ui->recentAlbumView );
ui->recentAlbumView->setModel( m_recentAlbumModel );

View File

@ -9,6 +9,7 @@
class AlbumModel;
class CollectionFlatModel;
class PlaylistModel;
namespace Ui
{
@ -30,6 +31,7 @@ private:
Ui::SourceInfoWidget *ui;
CollectionFlatModel* m_recentCollectionModel;
PlaylistModel* m_historyModel;
AlbumModel* m_recentAlbumModel;
};

View File

@ -10,7 +10,7 @@
<height>460</height>
</rect>
</property>
<layout class="QVBoxLayout" name="verticalLayout_3" stretch="0,0,0">
<layout class="QVBoxLayout" name="verticalLayout_4">
<item>
<widget class="QLabel" name="sourceLabel">
<property name="font">
@ -69,29 +69,59 @@
</layout>
</item>
<item>
<layout class="QVBoxLayout" name="verticalLayout_2">
<layout class="QHBoxLayout" name="horizontalLayout">
<item>
<widget class="QLabel" name="label">
<property name="font">
<font>
<pointsize>13</pointsize>
<weight>75</weight>
<bold>true</bold>
</font>
</property>
<property name="text">
<string>Latest Additions to their Collection</string>
</property>
</widget>
<layout class="QVBoxLayout" name="verticalLayout_3">
<item>
<widget class="QLabel" name="label">
<property name="font">
<font>
<pointsize>13</pointsize>
<weight>75</weight>
<bold>true</bold>
</font>
</property>
<property name="text">
<string>Latest Additions to their Collection</string>
</property>
</widget>
</item>
<item>
<widget class="CollectionView" name="recentCollectionView"/>
</item>
</layout>
</item>
<item>
<widget class="CollectionView" name="recentCollectionView"/>
<layout class="QVBoxLayout" name="verticalLayout_2">
<item>
<widget class="QLabel" name="label_3">
<property name="font">
<font>
<pointsize>13</pointsize>
<weight>75</weight>
<bold>true</bold>
</font>
</property>
<property name="text">
<string>Recently played Tracks</string>
</property>
</widget>
</item>
<item>
<widget class="PlaylistView" name="historyView"/>
</item>
</layout>
</item>
</layout>
</item>
</layout>
</widget>
<customwidgets>
<customwidget>
<class>PlaylistView</class>
<extends>QTreeView</extends>
<header>playlistview.h</header>
</customwidget>
<customwidget>
<class>CollectionView</class>
<extends>QTreeView</extends>

View File

@ -42,11 +42,13 @@ set( libSources
database/databasecommand_addfiles.cpp
database/databasecommand_dirmtimes.cpp
database/databasecommand_loadfile.cpp
database/databasecommand_logplayback.cpp
database/databasecommand_addsource.cpp
database/databasecommand_sourceoffline.cpp
database/databasecommand_collectionstats.cpp
database/databasecommand_loadplaylistentries.cpp
database/databasecommand_modifyplaylist.cpp
database/databasecommand_playbackhistory.cpp
database/databasecommand_setplaylistrevision.cpp
database/databasecommand_loadallplaylists.cpp
database/databasecommand_createplaylist.cpp
@ -96,11 +98,13 @@ set( libHeaders
database/databasecommand_addfiles.h
database/databasecommand_dirmtimes.h
database/databasecommand_loadfile.h
database/databasecommand_logplayback.h
database/databasecommand_addsource.h
database/databasecommand_sourceoffline.h
database/databasecommand_collectionstats.h
database/databasecommand_loadplaylistentries.h
database/databasecommand_modifyplaylist.h
database/databasecommand_playbackhistory.h
database/databasecommand_setplaylistrevision.h
database/databasecommand_loadallplaylists.h
database/databasecommand_createplaylist.h

View File

@ -74,10 +74,10 @@ DatabaseCommand_AllTracks::exec( DatabaseImpl* dbi )
t["mtime"] = query.value( 9 ).toInt();
t["mimetype"] = query.value( 10 ).toString();
t["albumpos"] = query.value( 11 ).toUInt();
unsigned int trkid = query.value( 14 ).toInt();
t["trackid"] = query.value( 14 ).toUInt();
attrQuery.prepare( "SELECT k, v FROM track_attributes WHERE id = ?" );
attrQuery.bindValue( 0, trkid );
attrQuery.bindValue( 0, t["trackid"] );
attrQuery.exec();
while ( attrQuery.next() )
{

View File

@ -0,0 +1,54 @@
#include "databasecommand_logplayback.h"
#include <QSqlQuery>
#include "collection.h"
#include "database/database.h"
#include "databaseimpl.h"
#include "network/servent.h"
using namespace Tomahawk;
// After changing a collection, we need to tell other bits of the system:
void
DatabaseCommand_LogPlayback::postCommitHook()
{
qDebug() << Q_FUNC_INFO;
if( source()->isLocal() )
Servent::instance()->triggerDBSync();
}
void
DatabaseCommand_LogPlayback::exec( DatabaseImpl* dbi )
{
qDebug() << Q_FUNC_INFO;
Q_ASSERT( !source().isNull() );
TomahawkSqlQuery query = dbi->newquery();
query.prepare( "INSERT INTO playback_log(source,track,playtime,secs_played) "
"VALUES (?, ?, ?, ?)" );
QVariant srcid = source()->isLocal() ? QVariant( QVariant::Int ) : source()->id();
qDebug() << "Logging playback of" << m_artist << "-" << m_track << "for source" << srcid;
query.bindValue( 0, srcid );
bool isnew;
int artid = dbi->artistId( m_artist, isnew );
if( artid < 1 )
return;
int trkid = dbi->trackId( artid, m_track, isnew );
if( trkid < 1 )
return;
query.bindValue( 1, trkid );
query.bindValue( 2, m_playtime );
query.bindValue( 3, m_secsPlayed );
query.exec();
}

View File

@ -0,0 +1,63 @@
#ifndef DATABASECOMMAND_LOGPLAYBACK_H
#define DATABASECOMMAND_LOGPLAYBACK_H
#include <QObject>
#include <QVariantMap>
#include "database/databasecommandloggable.h"
#include "sourcelist.h"
#include "typedefs.h"
#include "dllmacro.h"
class DLLEXPORT DatabaseCommand_LogPlayback : public DatabaseCommandLoggable
{
Q_OBJECT
Q_PROPERTY( QString artist READ artist WRITE setArtist )
Q_PROPERTY( QString track READ track WRITE setTrack )
Q_PROPERTY( unsigned int playtime READ playtime WRITE setPlaytime )
Q_PROPERTY( unsigned int secsPlayed READ secsPlayed WRITE setSecsPlayed )
public:
explicit DatabaseCommand_LogPlayback( QObject* parent = 0 )
: DatabaseCommandLoggable( parent )
{}
explicit DatabaseCommand_LogPlayback( const Tomahawk::result_ptr& result, unsigned int secsPlayed, QObject* parent = 0 )
: DatabaseCommandLoggable( parent ), m_result( result ), m_secsPlayed( secsPlayed )
{
m_playtime = QDateTime::currentDateTimeUtc().toTime_t();
setSource( SourceList::instance()->getLocal() );
setArtist( result->artist()->name() );
setTrack( result->track() );
}
virtual QString commandname() const { return "logplayback"; }
virtual void exec( DatabaseImpl* );
virtual bool doesMutates() const { return true; }
virtual void postCommitHook();
QString artist() const { return m_artist; }
void setArtist( const QString& s ) { m_artist = s; }
QString track() const { return m_track; }
void setTrack( const QString& s ) { m_track = s; }
unsigned int playtime() const { return m_playtime; }
void setPlaytime( unsigned int i ) { m_playtime = i; }
unsigned int secsPlayed() const { return m_secsPlayed; }
void setSecsPlayed( unsigned int i ) { m_secsPlayed = i; }
private:
Tomahawk::result_ptr m_result;
QString m_artist;
QString m_track;
unsigned int m_playtime;
unsigned int m_secsPlayed;
};
#endif // DATABASECOMMAND_LOGPLAYBACK_H

View File

@ -0,0 +1,61 @@
#include "databasecommand_playbackhistory.h"
#include <QSqlQuery>
#include "databaseimpl.h"
#include "pipeline.h"
void
DatabaseCommand_PlaybackHistory::exec( DatabaseImpl* dbi )
{
TomahawkSqlQuery query = dbi->newquery();
QList<Tomahawk::query_ptr> ql;
QString whereToken;
if ( !source().isNull() )
{
whereToken = QString( "WHERE source %1" ).arg( source()->isLocal() ? "IS NULL" : QString( "= %1" ).arg( source()->id() ) );
}
QString sql = QString(
"SELECT track, playtime, secs_played "
"FROM playback_log "
"%1" ).arg( whereToken );
query.prepare( sql );
query.exec();
while( query.next() )
{
TomahawkSqlQuery query_track = dbi->newquery();
QString sql = QString(
"SELECT track.name, artist.name "
"FROM track, artist "
"WHERE artist.id = track.artist "
"AND track.id = %1 "
).arg( query.value( 0 ).toUInt() );
query_track.prepare( sql );
query_track.exec();
if ( query_track.next() )
{
QVariantMap m;
m.insert( "track", query_track.value( 0 ).toString() );
m.insert( "artist", query_track.value( 1 ).toString() );
m.insert( "qid", uuid() );
Tomahawk::query_ptr q( new Tomahawk::Query( m ) );
ql << q;
}
}
qDebug() << Q_FUNC_INFO << ql.length();
if ( ql.count() )
{
Tomahawk::Pipeline::instance()->add( ql );
emit tracks( ql );
}
}

View File

@ -0,0 +1,34 @@
#ifndef DATABASECOMMAND_PLAYBACKHISTORY_H
#define DATABASECOMMAND_PLAYBACKHISTORY_H
#include <QObject>
#include <QVariantMap>
#include "databasecommand.h"
#include "source.h"
#include "typedefs.h"
#include "dllmacro.h"
class DLLEXPORT DatabaseCommand_PlaybackHistory : public DatabaseCommand
{
Q_OBJECT
public:
explicit DatabaseCommand_PlaybackHistory( const Tomahawk::source_ptr& source, QObject* parent = 0 )
: DatabaseCommand( parent )
{
setSource( source );
}
virtual void exec( DatabaseImpl* );
virtual bool doesMutates() const { return false; }
virtual QString commandname() const { return "playbackhistory"; }
signals:
void tracks( const QList<Tomahawk::query_ptr>& queries );
private:
};
#endif // DATABASECOMMAND_PLAYBACKHISTORY_H

View File

@ -10,12 +10,12 @@
#include "databasecommand_updatesearchindex.h"
/* !!!! You need to manually generate schema.sql.h when the schema changes:
cd src/database
cd src/libtomahawk/database
./gen_schema.h.sh ./schema.sql tomahawk > schema.sql.h
*/
#include "schema.sql.h"
#define CURRENT_SCHEMA_VERSION 14
#define CURRENT_SCHEMA_VERSION 15
DatabaseImpl::DatabaseImpl( const QString& dbname, Database* parent )

View File

@ -97,8 +97,6 @@ CREATE TABLE IF NOT EXISTS playlist_revision (
previous_revision TEXT REFERENCES playlist_revision(guid) DEFERRABLE INITIALLY DEFERRED
);
--INSERT INTO playlist_revision(guid, playlist, entries)
-- VALUES('revisionguid-1', 'playlistguid-1', '["itemguid-2","itemguid-1","itemguid-3"]');
-- the trigram search indexes
@ -209,10 +207,25 @@ CREATE TABLE IF NOT EXISTS track_attributes (
CREATE INDEX track_attrib_id ON track_attributes(id);
CREATE INDEX track_attrib_k ON track_attributes(k);
-- playback history
-- if source=null, file is local to this machine
CREATE TABLE IF NOT EXISTS playback_log (
id INTEGER PRIMARY KEY AUTOINCREMENT,
source INTEGER REFERENCES source(id) ON DELETE CASCADE ON UPDATE CASCADE DEFERRABLE INITIALLY DEFERRED,
track INTEGER REFERENCES track(id) ON DELETE CASCADE ON UPDATE CASCADE DEFERRABLE INITIALLY DEFERRED,
playtime INTEGER NOT NULL, -- when playback finished (timestamp)
secs_played INTEGER NOT NULL
);
CREATE INDEX playback_log_source ON playback_log(source);
CREATE INDEX playback_log_track ON playback_log(track);
-- Schema version, and misc tomahawk settings relating to the collection db
CREATE TABLE IF NOT EXISTS settings (
k TEXT NOT NULL PRIMARY KEY,
v TEXT NOT NULL DEFAULT ''
);
INSERT INTO settings(k,v) VALUES('schema_version', '14');
INSERT INTO settings(k,v) VALUES('schema_version', '15');

View File

@ -1,5 +1,5 @@
/*
This file was automatically generated from schema.sql on Tue Jul 13 12:23:44 CEST 2010.
This file was automatically generated from schema.sql on Tue Jan 4 06:55:23 CET 2011.
*/
static const char * tomahawk_schema_sql =
@ -148,11 +148,20 @@ static const char * tomahawk_schema_sql =
");"
"CREATE INDEX track_attrib_id ON track_attributes(id);"
"CREATE INDEX track_attrib_k ON track_attributes(k);"
"CREATE TABLE IF NOT EXISTS playback_log ("
" id INTEGER PRIMARY KEY AUTOINCREMENT,"
" source INTEGER REFERENCES source(id) ON DELETE CASCADE ON UPDATE CASCADE DEFERRABLE INITIALLY DEFERRED,"
" track INTEGER REFERENCES track(id) ON DELETE CASCADE ON UPDATE CASCADE DEFERRABLE INITIALLY DEFERRED,"
" playtime INTEGER NOT NULL, "
" secs_played INTEGER NOT NULL"
");"
"CREATE INDEX playback_log_source ON playback_log(source);"
"CREATE INDEX playback_log_track ON playback_log(track);"
"CREATE TABLE IF NOT EXISTS settings ("
" k TEXT NOT NULL PRIMARY KEY,"
" v TEXT NOT NULL DEFAULT ''"
");"
"INSERT INTO settings(k,v) VALUES('schema_version', '14');"
"INSERT INTO settings(k,v) VALUES('schema_version', '15');"
;
const char * get_tomahawk_sql()

View File

@ -50,7 +50,7 @@ MusicScanner::startScan()
// trigger the scan once we've loaded old mtimes for dirs below our path
DatabaseCommand_DirMtimes* cmd = new DatabaseCommand_DirMtimes( m_dir );
connect( cmd, SIGNAL( done( const QMap<QString, unsigned int>& ) ),
SLOT( setMtimes( const QMap<QString, unsigned int>& ) ), Qt::DirectConnection );
SLOT( setMtimes( const QMap<QString, unsigned int>& ) ), Qt::DirectConnection );
connect( cmd, SIGNAL( done( const QMap<QString,unsigned int>& ) ),
SLOT( scan() ), Qt::DirectConnection );
@ -81,7 +81,7 @@ MusicScanner::scan()
// queued, so will only fire after all dirs have been scanned:
connect( lister, SIGNAL( finished( const QMap<QString, unsigned int>& ) ),
SLOT( listerFinished( const QMap<QString, unsigned int>& ) ), Qt::QueuedConnection );
SLOT( listerFinished( const QMap<QString, unsigned int>& ) ), Qt::QueuedConnection );
connect( lister, SIGNAL( finished() ), lister, SLOT( deleteLater() ) );

View File

@ -6,6 +6,9 @@
#include "album.h"
#include "database/database.h"
#include "database/databasecommand_playbackhistory.h"
using namespace Tomahawk;
@ -115,6 +118,29 @@ PlaylistModel::loadAlbum( const Tomahawk::album_ptr& album )
}
void
PlaylistModel::loadHistory( const Tomahawk::source_ptr& source, unsigned int amount )
{
if ( rowCount( QModelIndex() ) )
{
emit beginRemoveRows( QModelIndex(), 0, rowCount( QModelIndex() ) - 1 );
delete m_rootItem;
emit endRemoveRows();
m_rootItem = new PlItem( 0, this );
}
m_playlist.clear();
setReadOnly( true );
DatabaseCommand_PlaybackHistory* cmd = new DatabaseCommand_PlaybackHistory( source );
connect( cmd, SIGNAL( tracks( QList<Tomahawk::query_ptr> ) ),
SLOT( onTracksAdded( QList<Tomahawk::query_ptr> ) ), Qt::QueuedConnection );
Database::instance()->enqueue( QSharedPointer<DatabaseCommand>( cmd ) );
}
void
PlaylistModel::appendTrack( const Tomahawk::query_ptr& query )
{

View File

@ -32,6 +32,7 @@ public:
void loadPlaylist( const Tomahawk::playlist_ptr& playlist );
void loadAlbum( const Tomahawk::album_ptr& album );
void loadHistory( const Tomahawk::source_ptr& source, unsigned int amount = 100 );
void appendTrack( const Tomahawk::query_ptr& query );

View File

@ -24,7 +24,7 @@ TrackHeader::TrackHeader( TrackView* parent )
setMinimumSectionSize( 60 );
setDefaultAlignment( Qt::AlignLeft );
setMovable( true );
setCascadingSectionResizes( true );
// setCascadingSectionResizes( true );
m_menu->addAction( tr( "Resize columns to fit window" ), this, SLOT( onToggleResizeColumns() ) );
m_menu->addSeparator();
@ -72,7 +72,8 @@ TrackHeader::onResized()
if ( sectionSize( x ) )
{
// not hidden
resizeSection( x, int( (double)width * m_columnWeights[x] ) );
double nw = (double)width * m_columnWeights[x];
resizeSection( x, qMax( minimumSectionSize(), int( nw ) ) );
}
}

View File

@ -36,7 +36,7 @@ TrackView::TrackView( QWidget* parent )
setVerticalScrollMode( QAbstractItemView::ScrollPerPixel );
setRootIsDecorated( false );
setUniformRowHeights( true );
setMinimumWidth( 700 );
setMinimumWidth( 300 );
setHeader( m_header );

View File

@ -50,10 +50,10 @@ Scrobbler::Scrobbler( QObject* parent )
}
connect( TomahawkApp::instance(), SIGNAL( settingsChanged() ),
SLOT( settingsChanged() ), Qt::QueuedConnection );
SLOT( settingsChanged() ), Qt::QueuedConnection );
connect( TomahawkApp::instance()->audioEngine(), SIGNAL( timerSeconds( unsigned int ) ),
SLOT( engineTick( unsigned int ) ), Qt::QueuedConnection );
SLOT( engineTick( unsigned int ) ), Qt::QueuedConnection );
}

View File

@ -22,6 +22,10 @@ WelcomeWidget::WelcomeWidget( QWidget* parent )
ui->setupUi( this );
ui->playlistWidget->setItemDelegate( new PlaylistDelegate() );
m_tracksModel = new PlaylistModel( ui->tracksView );
ui->tracksView->setModel( m_tracksModel );
m_tracksModel->loadHistory( Tomahawk::source_ptr() );
connect( SourceList::instance(), SIGNAL( sourceAdded( Tomahawk::source_ptr ) ), SLOT( onSourceAdded( Tomahawk::source_ptr ) ) );
connect( ui->playlistWidget, SIGNAL( itemActivated( QListWidgetItem* ) ), SLOT( onPlaylistActivated( QListWidgetItem* ) ) );

View File

@ -80,6 +80,8 @@ private slots:
private:
Ui::WelcomeWidget *ui;
PlaylistModel* m_tracksModel;
};
#endif // WELCOMEWIDGET_H

View File

@ -43,8 +43,30 @@
<item>
<widget class="QListWidget" name="playlistWidget"/>
</item>
<item>
<widget class="QLabel" name="label_3">
<property name="font">
<font>
<pointsize>14</pointsize>
</font>
</property>
<property name="text">
<string>Recently played tracks:</string>
</property>
</widget>
</item>
<item>
<widget class="PlaylistView" name="tracksView"/>
</item>
</layout>
</widget>
<customwidgets>
<customwidget>
<class>PlaylistView</class>
<extends>QTreeView</extends>
<header>playlistview.h</header>
</customwidget>
</customwidgets>
<resources/>
<connections/>
</ui>