From cf2e5fbe76ebb3ce2ca3183c95d75f8a44fead2a Mon Sep 17 00:00:00 2001 From: "craig.p.drummond" Date: Wed, 8 Jan 2014 19:43:05 +0000 Subject: [PATCH] If listallinfo fails, usr lsinfo recursively BUG: 379 --- ChangeLog | 4 ++-- README | 9 ++++++++ gui/settings.cpp | 5 +++++ gui/settings.h | 1 + mpd/mpdconnection.cpp | 50 ++++++++++++++++++++++++++++++++----------- mpd/mpdconnection.h | 1 + mpd/mpdparseutils.cpp | 21 ++++++++++++------ mpd/mpdparseutils.h | 4 +++- 8 files changed, 74 insertions(+), 21 deletions(-) diff --git a/ChangeLog b/ChangeLog index 868a2a4ac..37db756ed 100644 --- a/ChangeLog +++ b/ChangeLog @@ -30,8 +30,8 @@ HTTP server, set alwaysUseHttp to true in Cantata's config file. Refer to README for more details. 16. Add CMake option to disable building of internal HTTP server. -17. If listallinfo fails with MPD 0.18.x then prompt user to check MPD's - max_output_buffer_size setting. +17. If listallinfo fails, then attempt to retrieve music listing via lsinfo + calls on each folder. 18. Add CMake option to disable streams, dynamic, and online services. Refer to INSTALL file for details. 19. Don't use QKeySequence::Delete to detect delete key event for play queue, diff --git a/README b/README index c4034cbe4..0ccb18212 100644 --- a/README +++ b/README @@ -361,6 +361,14 @@ alwaysUseHttp= from media devices, or when connected to MPD via a standard socket. Default is false. +alwaysUseLsInfo= + By default, Cantata uses MPD's listallinfo command to retrieve the whole + music collection. This can fail (especially with MPD 0.18.x which needs + more memory), and if it does Cantata will fall back to calling + "lsinfo " for each directory. Setting this config item to true will + cause Cantata to always use this alternative method. + Default is false. + e.g. [General] iconTheme=oxygen @@ -373,6 +381,7 @@ undoSteps=20 mpdPoll=true mpdListSize=5000 alwaysUseHttp=true +alwaysUseLsInfo=true 7. CUE Files diff --git a/gui/settings.cpp b/gui/settings.cpp index e01c4be34..ac0674dd3 100644 --- a/gui/settings.cpp +++ b/gui/settings.cpp @@ -863,6 +863,11 @@ QString Settings::lang() } #endif +bool Settings::alwaysUseLsInfo() +{ + return GET_BOOL("alwaysUseLsInfo", false); +} + void Settings::removeConnectionDetails(const QString &v) { if (v==currentConnection()) { diff --git a/gui/settings.h b/gui/settings.h index 85c355bff..4162149fa 100644 --- a/gui/settings.h +++ b/gui/settings.h @@ -209,6 +209,7 @@ public: #ifndef ENABLE_KDE_SUPPORT QString lang(); #endif + bool alwaysUseLsInfo(); void removeConnectionDetails(const QString &v); void saveConnectionDetails(const MPDConnectionDetails &v); diff --git a/mpd/mpdconnection.cpp b/mpd/mpdconnection.cpp index 430c61c0d..42065a481 100644 --- a/mpd/mpdconnection.cpp +++ b/mpd/mpdconnection.cpp @@ -26,6 +26,7 @@ #include "mpdconnection.h" #include "mpdparseutils.h" +#include "musiclibraryitemroot.h" #include "mpduser.h" #include "localize.h" #include "utils.h" @@ -53,6 +54,7 @@ void MPDConnection::enableDebug() static const int constSocketCommsTimeout=2000; static const int constMaxReadAttempts=4; static int maxFilesPerAddCommand=10000; +static bool alwaysUseLsInfo=false; #ifdef ENABLE_KDE_SUPPORT K_GLOBAL_STATIC(MPDConnection, conn) @@ -213,6 +215,7 @@ MPDConnection::MPDConnection() connect(PowerManagement::self(), SIGNAL(resuming()), this, SLOT(reconnect())); #endif maxFilesPerAddCommand=Settings::self()->mpdListSize(); + alwaysUseLsInfo=Settings::self()->alwaysUseLsInfo(); } MPDConnection::~MPDConnection() @@ -530,11 +533,11 @@ MPDConnection::Response MPDConnection::sendCommand(const QByteArray &command, bo } } else if (!response.getError(command).isEmpty()) { emit error(i18n("MPD reported the following error: %1", response.getError(command))); - } else if ("listallinfo"==command && ver>=MPD_MAKE_VERSION(0,18,0)) { + } /*else if ("listallinfo"==command && ver>=MPD_MAKE_VERSION(0,18,0)) { disconnectFromMPD(); emit stateChanged(false); emit error(i18n("Failed to load library. Please increase \"max_output_buffer_size\" in MPD's config file.")); - } else { + } */ else { disconnectFromMPD(); emit stateChanged(false); emit error(i18n("Failed to send command. Disconnected from %1", details.description()), true); @@ -1207,24 +1210,29 @@ void MPDConnection::update() * Database commands */ -/** - * Get all files in the playlist with detailed info (artist, album, - * title, time etc). - */ void MPDConnection::loadLibrary() { emit updatingLibrary(); - Response response=sendCommand("listallinfo"); + Response response=alwaysUseLsInfo ? Response(false) : sendCommand("listallinfo", false); + MusicLibraryItemRoot *root=0; if (response.ok) { - emit musicLibraryUpdated(MPDParseUtils::parseLibraryItems(response.data, details.dir, ver), dbUpdate); + root = new MusicLibraryItemRoot; + MPDParseUtils::parseLibraryItems(response.data, details.dir, ver, root); + } else { // MPD >=0.18 can fail listallinfo for large DBs, so get info dir by dir... + root = new MusicLibraryItemRoot; + if (!listDirInfo("/", root)) { + delete root; + root=0; + } + } + + if (root) { + root->applyGrouping(); + emit musicLibraryUpdated(root, dbUpdate); } emit updatedLibrary(); } -/** -* Get all the files and dir in the mpdmusic dir. -* -*/ void MPDConnection::loadFolders() { emit updatingFileList(); @@ -1451,6 +1459,24 @@ void MPDConnection::toggleStopAfterCurrent(bool afterCurrent) } } +bool MPDConnection::listDirInfo(const QString &dir, MusicLibraryItemRoot *root) +{ + bool topLevel="/"==dir; + Response response=sendCommand(topLevel ? "lsinfo /" : ("lsinfo "+encodeName(dir))); + if (response.ok) { + QSet childDirs; + MPDParseUtils::parseLibraryItems(response.data, details.dir, ver, root, !topLevel, &childDirs); + foreach (const QString &child, childDirs) { + if (!listDirInfo(child, root)) { + return false; + } + } + return true; + } else { + return false; + } +} + MpdSocket::MpdSocket(QObject *parent) : QObject(parent) , tcp(0) diff --git a/mpd/mpdconnection.h b/mpd/mpdconnection.h index fb87478aa..1cb08b063 100644 --- a/mpd/mpdconnection.h +++ b/mpd/mpdconnection.h @@ -337,6 +337,7 @@ private: void parseIdleReturn(const QByteArray &data); bool doMoveInPlaylist(const QString &name, const QList &items, quint32 pos, quint32 size); void toggleStopAfterCurrent(bool afterCurrent); + bool listDirInfo(const QString &dir, MusicLibraryItemRoot *root); private: Thread *thread; diff --git a/mpd/mpdparseutils.cpp b/mpd/mpdparseutils.cpp index 06c0faf05..8913981de 100644 --- a/mpd/mpdparseutils.cpp +++ b/mpd/mpdparseutils.cpp @@ -409,10 +409,10 @@ void MPDParseUtils::setGroupMultiple(bool g) groupMultipleArtists=g; } -MusicLibraryItemRoot * MPDParseUtils::parseLibraryItems(const QByteArray &data, const QString &mpdDir, long mpdVersion) +void MPDParseUtils::parseLibraryItems(const QByteArray &data, const QString &mpdDir, long mpdVersion, + MusicLibraryItemRoot *rootItem, bool parsePlaylists, QSet *childDirs) { bool canSplitCue=mpdVersion>=MPD_MAKE_VERSION(0,17,0); - MusicLibraryItemRoot * const rootItem = new MusicLibraryItemRoot; QByteArray currentItem; QList lines = data.split('\n'); int amountOfLines = lines.size(); @@ -422,9 +422,13 @@ MusicLibraryItemRoot * MPDParseUtils::parseLibraryItems(const QByteArray &data, QString unknown=i18n("Unknown"); for (int i = 0; i < amountOfLines; i++) { - currentItem += lines.at(i); + QByteArray line=lines.at(i); + if (childDirs && line.startsWith("directory: ")) { + childDirs->insert(QString::fromUtf8(line.remove(0, 11))); + } + currentItem += line; currentItem += "\n"; - if (i == lines.size() - 1 || lines.at(i + 1).startsWith("file:") || lines.at(i + 1).startsWith("playlist:")) { + if (i == amountOfLines - 1 || lines.at(i + 1).startsWith("file:") || lines.at(i + 1).startsWith("playlist:")) { Song currentSong = parseSong(currentItem, false); currentItem.clear(); if (currentSong.file.isEmpty()) { @@ -432,6 +436,11 @@ MusicLibraryItemRoot * MPDParseUtils::parseLibraryItems(const QByteArray &data, } if (Song::Playlist==currentSong.type) { + // lsinfo / will return all stored playlists - but this is deprecated. + if (!parsePlaylists) { + continue; + } + MusicLibraryItemAlbum *prevAlbum=albumItem; QString prevSongFile=songItem ? songItem->file() : QString(); QList cueSongs; // List of songs from cue file @@ -603,10 +612,10 @@ MusicLibraryItemRoot * MPDParseUtils::parseLibraryItems(const QByteArray &data, albumItem->addGenre(currentSong.genre); artistItem->addGenre(currentSong.genre); rootItem->addGenre(currentSong.genre); + } else if (childDirs) { + } } - rootItem->applyGrouping(); - return rootItem; } DirViewItemRoot * MPDParseUtils::parseDirViewItems(const QByteArray &data) diff --git a/mpd/mpdparseutils.h b/mpd/mpdparseutils.h index 8c9b2529b..54c450ca7 100644 --- a/mpd/mpdparseutils.h +++ b/mpd/mpdparseutils.h @@ -28,6 +28,7 @@ #define MPD_PARSE_UTILS_H #include +#include struct Song; class Playlist; @@ -60,7 +61,8 @@ namespace MPDParseUtils extern void setGroupSingle(bool g); extern bool groupMultiple(); extern void setGroupMultiple(bool g); - extern MusicLibraryItemRoot * parseLibraryItems(const QByteArray &data, const QString &mpdDir, long mpdVersion); + extern void parseLibraryItems(const QByteArray &data, const QString &mpdDir, long mpdVersion, + MusicLibraryItemRoot *rootItem, bool parsePlaylists=true, QSet *childDirs=0); extern DirViewItemRoot * parseDirViewItems(const QByteArray &data); extern QList parseOuputs(const QByteArray &data); extern QString addStreamName(const QString &url, const QString &name);