From b60f6ffeb3b91394fc936d77ef0ce6e93409e9c3 Mon Sep 17 00:00:00 2001 From: craig Date: Thu, 29 Mar 2012 15:49:19 +0000 Subject: [PATCH] Use 'plchangesposid' MPD command to get list of playqueue changes - means less data needs to be read from MPD per playqueue update. --- ChangeLog | 2 + gui/mainwindow.cpp | 2 +- models/playqueuemodel.cpp | 61 ++++++---------- models/playqueuemodel.h | 4 +- mpd/mpdconnection.cpp | 145 +++++++++++++++++++++++++++++++++----- mpd/mpdconnection.h | 6 ++ mpd/mpdparseutils.cpp | 55 +++++++++++---- mpd/mpdparseutils.h | 9 +++ mpd/mpdstatus.h | 4 +- mpd/song.cpp | 22 +++++- mpd/song.h | 3 +- widgets/groupedview.h | 2 - widgets/playqueueview.cpp | 10 --- widgets/playqueueview.h | 2 - 14 files changed, 232 insertions(+), 95 deletions(-) diff --git a/ChangeLog b/ChangeLog index 104c7c542..33f0a9675 100644 --- a/ChangeLog +++ b/ChangeLog @@ -38,6 +38,8 @@ 25. Use CD icon (without music note) for albums page. 26. Better size calculation of compact view height - should prevent text clipping. +27. Use 'plchangesposid' MPD command to get list of playqueue changes - means + less data needs to be read from MPD per playqueue update. 0.5.1 ----- diff --git a/gui/mainwindow.cpp b/gui/mainwindow.cpp index f2d0a5233..21e7583fa 100644 --- a/gui/mainwindow.cpp +++ b/gui/mainwindow.cpp @@ -1475,7 +1475,7 @@ void MainWindow::updatePlaylist(const QList &songs) } } - playQueue->setControlledAlbums(playQueueModel.updatePlaylist(songs, playQueue->getControlledAlbums())); + playQueueModel.updatePlaylist(songs); playQueue->updateRows(usingProxy ? playQueueModel.rowCount()+10 : playQueueModel.currentSongRow(), false); // reselect song ids or minrow if songids were not found (songs removed) diff --git a/models/playqueuemodel.cpp b/models/playqueuemodel.cpp index a6c96412e..95449780d 100644 --- a/models/playqueuemodel.cpp +++ b/models/playqueuemodel.cpp @@ -410,7 +410,7 @@ QMimeData *PlayQueueModel::mimeData(const QModelIndexList &indexes) const continue; } - positions.append(QString::number(getPosByRow(index.row()))); + positions.append(QString::number(index.row())); // getPosByRow(index.row()))); rows.append(index.row()); filenames.append(songs.at(index.row()).file); } @@ -529,10 +529,10 @@ qint32 PlayQueueModel::getIdByRow(qint32 row) const return row>=songs.size() ? -1 : songs.at(row).id; } -qint32 PlayQueueModel::getPosByRow(qint32 row) const -{ - return row>=songs.size() ? -1 : songs.at(row).pos; -} +// qint32 PlayQueueModel::getPosByRow(qint32 row) const +// { +// return row>=songs.size() ? -1 : songs.at(row).pos; +// } qint32 PlayQueueModel::getRowById(qint32 id) const { @@ -587,41 +587,19 @@ void PlayQueueModel::setGrouped(bool g) } // Update playqueue with contents returned from MPD. -// Also, return set of artist-album keys associated with songs in 'ids' -QSet PlayQueueModel::updatePlaylist(const QList &songList, QSet controlledAlbums) +void PlayQueueModel::updatePlaylist(const QList &songList) { TF_DEBUG QSet newIds; - QSet controlledIds; - QSet keys; foreach (const Song &s, songList) { newIds.insert(s.id); } - // Map from album keys, into song ids... - foreach (const Song &s, songs) { - if (controlledAlbums.contains(s.key)) { - controlledIds.insert(s.id); - } - } - if (songs.isEmpty() || songList.isEmpty()) { beginResetModel(); songs=songList; ids=newIds; endResetModel(); - - if (grouped && !controlledIds.isEmpty()) { - foreach (const Song &s, songs) { - if (controlledIds.contains(s.id)) { - keys.insert(s.key); - controlledIds.remove(s.id); - if (controlledIds.isEmpty()) { - break; - } - } - } - } } else { QSet artists; QSet albums; @@ -638,18 +616,25 @@ QSet PlayQueueModel::updatePlaylist(const QList &songList, QSet=songs.count() || s.id!=songs.at(i).id) { - qint32 existing=getRowById(s.id); - if (-1==existing) { + bool newSong=i>=songs.count(); + Song curentSongAtPos=newSong ? Song() : songs.at(i); + bool isEmpty=s.isEmpty(); + + if (newSong || s.id!=curentSongAtPos.id) { + qint32 existingPos=newSong ? -1 : getRowById(s.id); + if (-1==existingPos) { beginInsertRows(QModelIndex(), i, i); songs.insert(i, s); endInsertRows(); } else { - beginMoveRows(QModelIndex(), existing, existing, QModelIndex(), i>existing ? i+1 : i); - songs.takeAt(existing); - songs.insert(i, s); + beginMoveRows(QModelIndex(), existingPos, existingPos, QModelIndex(), i>existingPos ? i+1 : i); + Song old=songs.takeAt(existingPos); +// old.pos=s.pos; + songs.insert(i, isEmpty ? old : s); endMoveRows(); } + } else if (isEmpty) { + s=curentSongAtPos; } else { songs.replace(i, s); } @@ -657,17 +642,11 @@ QSet PlayQueueModel::updatePlaylist(const QList &songList, QSet updatePlaylist(const QList &songList, QSet controlled); + void updatePlaylist(const QList &songList); public Q_SLOTS: void addItems(const QStringList &items, int row, bool replace); diff --git a/mpd/mpdconnection.cpp b/mpd/mpdconnection.cpp index dc3dd76e0..83e04a6f6 100644 --- a/mpd/mpdconnection.cpp +++ b/mpd/mpdconnection.cpp @@ -49,7 +49,7 @@ MPDConnection * MPDConnection::self() return conn; #else static MPDConnection *conn=0;; - if(!conn) { + if (!conn) { conn=new MPDConnection; } return conn; @@ -79,7 +79,7 @@ static QByteArray readFromSocket(MpdSocket &socket) break; } } - if(data.size()>256) { + if (data.size()>256) { qDebug() << (void *)(&socket) << "Read (bytes):" << data.size(); } else { qDebug() << (void *)(&socket) << "Read:" << data; @@ -153,6 +153,8 @@ bool MPDConnection::connectToMPD(MpdSocket &socket, bool enableIdle) qDebug("Received identification string"); } + lastUpdatePlayQueueVersion=lastStatusPlayQueueVersion=0; + playQueueIds.clear(); int min, maj, patch; if (3==sscanf(&(recvdata.constData()[7]), "%d.%d.%d", &maj, &min, &patch)) { long v=((maj&0xFF)<<16)+((min&0xFF)<<8)+(patch&0xFF); @@ -180,7 +182,7 @@ bool MPDConnection::connectToMPD(MpdSocket &socket, bool enableIdle) } } - if(enableIdle) { + if (enableIdle) { connect(&socket, SIGNAL(readyRead()), this, SLOT(idleDataReady())); qDebug() << "Enabling idle"; socket.write("idle\n"); @@ -355,7 +357,10 @@ void MPDConnection::addid(const QStringList &files, quint32 pos, quint32 size, b void MPDConnection::clear() { - sendCommand("clear"); + if (sendCommand("clear").ok) { + lastUpdatePlayQueueVersion=0; + playQueueIds.clear(); + } } void MPDConnection::removeSongs(const QList &items) @@ -435,16 +440,113 @@ void MPDConnection::shuffle(quint32 from, quint32 to) void MPDConnection::currentSong() { Response response=sendCommand("currentsong"); - if(response.ok) { + if (response.ok) { emit currentSongUpdated(MPDParseUtils::parseSong(response.data)); } } +/* + * Call "plchangesposid" to recieve a list of positions+ids that have been changed since the last update. + * If we have ids in this list that we don't know about, then these are new songs - so we call + * "playlistinfo " to get the song information. + * + * Any songs that are know about, will actually be sent with empty data - as the playqueue model will + * already hold these songs. + */ +void MPDConnection::playListChanges() +{ + if (0==lastUpdatePlayQueueVersion || 0==playQueueIds.size()) { + playListInfo(); + return; + } + + QByteArray data = "plchangesposid "; + data += QByteArray::number(lastUpdatePlayQueueVersion); + Response response=sendCommand(data); + if (response.ok) { + // We need an updated status so as to detect deletes at end of list... + Response status=sendCommand("status"); + if (status.ok) { + MPDStatusValues sv=MPDParseUtils::parseStatus(status.data); + lastUpdatePlayQueueVersion=lastStatusPlayQueueVersion=sv.playlist; + emit statusUpdated(sv); + QList changes=MPDParseUtils::parseChanges(response.data); + if (!changes.isEmpty()) { + bool first=true; + quint32 firstPos=0; + QList songs; + QList ids; + QSet prevIds=playQueueIds.toSet(); + + foreach (const MPDParseUtils::IdPos &idp, changes) { + if (first) { + first=false; + firstPos=idp.pos; + if (idp.pos!=0) { + for (quint32 i=0; i songs=MPDParseUtils::parseSongs(response.data); + playQueueIds.clear(); + foreach (const Song &s, songs) { + playQueueIds.append(s.id); + } + emit playlistUpdated(songs); } } @@ -561,7 +663,7 @@ void MPDConnection::stopPlaying() void MPDConnection::getStats() { Response response=sendCommand("stats"); - if(response.ok) { + if (response.ok) { emit statsUpdated(MPDParseUtils::parseStats(response.data)); } } @@ -569,15 +671,17 @@ void MPDConnection::getStats() void MPDConnection::getStatus() { Response response=sendCommand("status"); - if(response.ok) { - emit statusUpdated(MPDParseUtils::parseStatus(response.data)); + if (response.ok) { + MPDStatusValues sv=MPDParseUtils::parseStatus(response.data); + lastStatusPlayQueueVersion=sv.playlist; + emit statusUpdated(sv); } } void MPDConnection::getUrlHandlers() { Response response=sendCommand("urlhandlers"); - if(response.ok) { + if (response.ok) { emit urlHandlers(MPDParseUtils::parseUrlHandlers(response.data)); } } @@ -625,23 +729,26 @@ void MPDConnection::parseIdleReturn(const QByteArray &data) qDebug() << "parseIdleReturn:" << data; QList lines = data.split('\n'); - QByteArray line; - /* * See http://www.musicpd.org/doc/protocol/ch02.html */ - foreach(line, lines) { + bool playListUpdated=false; + foreach(const QByteArray &line, lines) { if (line == "changed: database") { /* * Temp solution */ getStats(); + playListInfo(); + playListUpdated=true; } else if (line == "changed: update") { emit databaseUpdated(); } else if (line == "changed: stored_playlist") { emit storedPlayListUpdated(); } else if (line == "changed: playlist") { - playListInfo(); + if (!playListUpdated) { + playListChanges(); + } } else if (line == "changed: player") { getStatus(); } else if (line == "changed: mixer") { @@ -661,7 +768,7 @@ void MPDConnection::parseIdleReturn(const QByteArray &data) void MPDConnection::outputs() { Response response=sendCommand("outputs"); - if(response.ok) { + if (response.ok) { emit outputsUpdated(MPDParseUtils::parseOuputs(response.data)); } } @@ -703,7 +810,7 @@ void MPDConnection::listAllInfo(const QDateTime &dbUpdate) TF_DEBUG emit updatingLibrary(); Response response=sendCommand("listallinfo"); - if(response.ok) { + if (response.ok) { emit musicLibraryUpdated(MPDParseUtils::parseLibraryItems(response.data), dbUpdate); } emit updatedLibrary(); @@ -718,7 +825,7 @@ void MPDConnection::listAll() TF_DEBUG emit updatingFileList(); Response response=sendCommand("listall"); - if(response.ok) { + if (response.ok) { emit dirViewUpdated(MPDParseUtils::parseDirViewItems(response.data)); } emit updatedFileList(); @@ -736,7 +843,7 @@ void MPDConnection::listPlaylists() { TF_DEBUG Response response=sendCommand("listplaylists"); - if(response.ok) { + if (response.ok) { emit playlistsRetrieved(MPDParseUtils::parsePlaylists(response.data)); } } diff --git a/mpd/mpdconnection.h b/mpd/mpdconnection.h index ea6d4b186..fd1d77c89 100644 --- a/mpd/mpdconnection.h +++ b/mpd/mpdconnection.h @@ -152,6 +152,7 @@ public Q_SLOTS: void add(const QStringList &files, bool replace); void addid(const QStringList &files, quint32 pos, quint32 size, bool replace); void currentSong(); + void playListChanges(); void playListInfo(); void removeSongs(const QList &items); void move(quint32 from, quint32 to); @@ -257,6 +258,11 @@ private: MpdSocket sock; MpdSocket idleSocket; + // The three items are used so that we can do quick playqueue updates... + QList playQueueIds; + quint32 lastStatusPlayQueueVersion; + quint32 lastUpdatePlayQueueVersion; + enum State { State_Blank, diff --git a/mpd/mpdparseutils.cpp b/mpd/mpdparseutils.cpp index 48f11f782..b80ff96fe 100644 --- a/mpd/mpdparseutils.cpp +++ b/mpd/mpdparseutils.cpp @@ -186,7 +186,6 @@ Song MPDParseUtils::parseSong(const QByteArray &data) { Song song; QString tmpData = QString::fromUtf8(data.constData()); - QStringList lines = tmpData.split('\n'); QStringList tokens; QString element; @@ -216,8 +215,8 @@ Song MPDParseUtils::parseSong(const QByteArray &data) song.title = value; } else if (element == QLatin1String("Track")) { song.track = value.split("/").at(0).toInt(); - } else if (element == QLatin1String("Pos")) { - song.pos = value.toInt(); +// } else if (element == QLatin1String("Pos")) { +// song.pos = value.toInt(); } else if (element == QLatin1String("Id")) { song.id = value.toUInt(); } else if (element == QLatin1String("Disc")) { @@ -252,9 +251,6 @@ QList MPDParseUtils::parseSongs(const QByteArray &data) QByteArray line; QList lines = data.split('\n'); int amountOfLines = lines.size(); - QString album; - QString artist; - quint16 key=0; for (int i = 0; i < amountOfLines; i++) { line += lines.at(i); @@ -274,12 +270,7 @@ QList MPDParseUtils::parseSongs(const QByteArray &data) } } - if (song.album!=album || song.albumArtist()!=artist) { - key++; - album=song.album; - artist=song.albumArtist(); - } - song.key=key; + song.setKey(); songs.append(song); line.clear(); } @@ -288,6 +279,46 @@ QList MPDParseUtils::parseSongs(const QByteArray &data) return songs; } +QList MPDParseUtils::parseChanges(const QByteArray &data) +{ + TF_DEBUG + QList changes; + QList lines = data.split('\n'); + int amountOfLines = lines.size(); + quint32 cpos=0; + bool foundCpos=false; + + for (int i = 0; i < amountOfLines; i++) { + QByteArray line = lines.at(i); + // Skip the "OK" line, this is NOT a song!!! + if ("OK"==line || line.length()<1) { + continue; + } + QList tokens = line.split(':'); + if (2!=tokens.count()) { + return QList(); + } + QByteArray element = tokens.takeFirst(); + QByteArray value = tokens.takeFirst(); + if (element == "cpos") { + if (foundCpos) { + return QList(); + } + foundCpos=true; + cpos = value.toInt(); + } else if (element == "Id") { + if (!foundCpos) { + return QList(); + } + foundCpos=false; + qint32 id=value.toInt(); + changes.append(IdPos(id, cpos)); + } + } + + return changes; +} + QStringList MPDParseUtils::parseUrlHandlers(const QByteArray &data) { TF_DEBUG diff --git a/mpd/mpdparseutils.h b/mpd/mpdparseutils.h index 6f3d43758..15c60df92 100644 --- a/mpd/mpdparseutils.h +++ b/mpd/mpdparseutils.h @@ -40,6 +40,14 @@ class MPDStatusValues; class MPDParseUtils { public: + struct IdPos { + IdPos(qint32 i, quint32 p) + : id(i) + , pos(p) { + } + qint32 id; + quint32 pos; + }; static QString fixPath(const QString &f); static QString getDir(const QString &f); static QList parsePlaylists(const QByteArray &data); @@ -47,6 +55,7 @@ public: static MPDStatusValues parseStatus(const QByteArray &data); static Song parseSong(const QByteArray &data); static QList parseSongs(const QByteArray &data); + static QList parseChanges(const QByteArray &data); static QStringList parseUrlHandlers(const QByteArray &data); static bool groupSingle(); static void setGroupSingle(bool g); diff --git a/mpd/mpdstatus.h b/mpd/mpdstatus.h index 354d257ee..98884743c 100644 --- a/mpd/mpdstatus.h +++ b/mpd/mpdstatus.h @@ -61,7 +61,7 @@ struct MPDStatusValues { bool repeat; bool random; quint32 playlist; - qint32 playlistLength; + quint32 playlistLength; qint32 crossFade; MPDState state; qint32 song; @@ -98,7 +98,7 @@ public: quint32 playlist() const { return values.playlist; } - qint32 playlistLength() const { + quint32 playlistLength() const { return values.playlistLength; } qint32 crossFade() const { diff --git a/mpd/song.cpp b/mpd/song.cpp index 06ff8e799..47498c015 100644 --- a/mpd/song.cpp +++ b/mpd/song.cpp @@ -42,7 +42,7 @@ Song::Song() : id(-1), time(0), track(0), - pos(0), +// pos(0), disc(0), year(0), size(0), @@ -61,7 +61,7 @@ Song & Song::operator=(const Song &s) title = s.title; modifiedtitle = s.modifiedtitle; track = s.track; - pos = s.pos; +// pos = s.pos; disc = s.disc; year = s.year; genre = s.genre; @@ -132,6 +132,22 @@ void Song::fillEmptyFields() } } +void Song::setKey() +{ + static quint16 currentKey=0; + static QMap keys; + + QString albumAndArtist=albumArtist()+QLatin1String("::")+album; + QMap::ConstIterator it=keys.find(albumAndArtist); + if (it!=keys.end()) { + key=it.value(); + } else { + currentKey++; + keys.insert(albumAndArtist, currentKey); + key=currentKey; + } +} + bool Song::isUnknown() const { #ifdef ENABLE_KDE_SUPPORT @@ -151,7 +167,7 @@ void Song::clear() artist.clear(); title.clear(); track = 0; - pos = 0; +// pos = 0; disc = 0; year = 0; genre.clear(); diff --git a/mpd/song.h b/mpd/song.h index d39c2601d..05e34a1e2 100644 --- a/mpd/song.h +++ b/mpd/song.h @@ -45,7 +45,7 @@ struct Song QString title; QString modifiedtitle; qint32 track; - quint32 pos; +// quint32 pos; quint32 disc; quint32 year; QString genre; @@ -63,6 +63,7 @@ struct Song virtual ~Song() { } bool isEmpty() const; void fillEmptyFields(); + void setKey(); virtual void clear(); static QString formattedTime(quint32 seconds); QString format(); diff --git a/widgets/groupedview.h b/widgets/groupedview.h index f65773fc6..0c5cd10e1 100644 --- a/widgets/groupedview.h +++ b/widgets/groupedview.h @@ -63,8 +63,6 @@ public: bool isAutoExpand() const { return autoExpand; } void setStartClosed(bool sc); bool isStartClosed() const { return startClosed; } - QSet getControlledAlbums(quint32 collection=0) const { return controlledAlbums[collection]; } - void setControlledAlbums(const QSet &keys, quint32 collection=0) { controlledAlbums[collection]=keys; } void updateRows(qint32 row, bool scroll, const QModelIndex &parent=QModelIndex()); void updateRows(const QModelIndex &parent); void updateCollectionRows(); diff --git a/widgets/playqueueview.cpp b/widgets/playqueueview.cpp index f9d6b309f..603e3b07b 100644 --- a/widgets/playqueueview.cpp +++ b/widgets/playqueueview.cpp @@ -225,16 +225,6 @@ bool PlayQueueView::isStartClosed() const return groupedView->isStartClosed(); } -QSet PlayQueueView::getControlledAlbums() const -{ - return currentWidget()==groupedView ? groupedView->getControlledAlbums() : QSet(); -} - -void PlayQueueView::setControlledAlbums(const QSet &keys) -{ - groupedView->setControlledAlbums(keys); -} - void PlayQueueView::setFilterActive(bool f) { groupedView->setFilterActive(f); diff --git a/widgets/playqueueview.h b/widgets/playqueueview.h index 6f0b6446d..5aead6f99 100644 --- a/widgets/playqueueview.h +++ b/widgets/playqueueview.h @@ -74,8 +74,6 @@ public: bool isAutoExpand() const; void setStartClosed(bool sc); bool isStartClosed() const; - QSet getControlledAlbums() const; - void setControlledAlbums(const QSet &keys); void setFilterActive(bool f); void updateRows(qint32 row, bool scroll); void scrollTo(const QModelIndex &index, QAbstractItemView::ScrollHint hint);