summaryrefslogtreecommitdiff
path: root/src/db
diff options
context:
space:
mode:
authorMax Kellermann <max@musicpd.org>2018-09-02 12:35:10 +0200
committerMax Kellermann <max@musicpd.org>2018-09-02 17:37:02 +0200
commitc7c32a3ce92f4fa483a4a1b41aa39cdaa0afcf45 (patch)
tree500f6fefdadcb1953fb958213da434e865659ca7 /src/db
parent53170ca2f24112df6f85b7e8313adf8ce839c627 (diff)
db/Print: move sort/window emulation code to class DatabaseVisitorHelper
That way, each plugin can decide to implement it better.
Diffstat (limited to 'src/db')
-rw-r--r--src/db/DatabasePrint.cxx93
-rw-r--r--src/db/VHelper.cxx133
-rw-r--r--src/db/VHelper.hxx70
-rw-r--r--src/db/plugins/ProxyDatabasePlugin.cxx15
-rw-r--r--src/db/plugins/simple/SimpleDatabasePlugin.cxx14
-rw-r--r--src/db/plugins/upnp/UpnpDatabasePlugin.cxx14
6 files changed, 247 insertions, 92 deletions
diff --git a/src/db/DatabasePrint.cxx b/src/db/DatabasePrint.cxx
index 047dd5d18..9699dc442 100644
--- a/src/db/DatabasePrint.cxx
+++ b/src/db/DatabasePrint.cxx
@@ -144,38 +144,6 @@ PrintPlaylistFull(Response &r, bool base,
time_print(r, "Last-Modified", playlist.mtime);
}
-gcc_pure
-static bool
-CompareNumeric(const char *a, const char *b) noexcept
-{
- long a_value = strtol(a, nullptr, 10);
- long b_value = strtol(b, nullptr, 10);
-
- return a_value < b_value;
-}
-
-gcc_pure
-static bool
-CompareTags(TagType type, bool descending, const Tag &a, const Tag &b) noexcept
-{
- const char *a_value = a.GetSortValue(type);
- const char *b_value = b.GetSortValue(type);
-
- if (descending) {
- using std::swap;
- swap(a_value, b_value);
- }
-
- switch (type) {
- case TAG_DISC:
- case TAG_TRACK:
- return CompareNumeric(a_value, b_value);
-
- default:
- return strcmp(a_value, b_value) < 0;
- }
-}
-
void
db_selection_print(Response &r, Partition &partition,
const DatabaseSelection &selection,
@@ -195,66 +163,7 @@ db_selection_print(Response &r, Partition &partition,
std::ref(r), base, _1, _2)
: VisitPlaylist();
- const auto window = selection.window;
-
- if (selection.sort == TAG_NUM_OF_ITEM_TYPES) {
- unsigned i = 0;
- if (!selection.window.IsAll())
- s = [s, window, &i](const LightSong &song){
- if (window.Contains(i++))
- s(song);
- };
-
- db.Visit(selection, d, s, p);
- } else {
- // TODO: allow the database plugin to sort internally
-
- /* the client has asked us to sort the result; this is
- pretty expensive, because instead of streaming the
- result to the client, we need to copy it all into
- this std::vector, and then sort it */
- std::vector<DetachedSong> songs;
-
- {
- auto collect_songs = [&songs](const LightSong &song){
- songs.emplace_back(song);
- };
-
- db.Visit(selection, d, collect_songs, p);
- }
-
- const auto sort = selection.sort;
- const auto descending = selection.descending;
-
- if (sort == TagType(SORT_TAG_LAST_MODIFIED))
- std::stable_sort(songs.begin(), songs.end(),
- [descending](const DetachedSong &a, const DetachedSong &b){
- return descending
- ? a.GetLastModified() > b.GetLastModified()
- : a.GetLastModified() < b.GetLastModified();
- });
- else
- std::stable_sort(songs.begin(), songs.end(),
- [sort, descending](const DetachedSong &a,
- const DetachedSong &b){
- return CompareTags(sort, descending,
- a.GetTag(),
- b.GetTag());
- });
-
- if (window.end < songs.size())
- songs.erase(std::next(songs.begin(), window.end),
- songs.end());
-
- if (window.start >= songs.size())
- return;
-
- songs.erase(songs.begin(),
- std::next(songs.begin(), window.start));
-
- for (const auto &song : songs)
- s((LightSong)song);
- }
+ db.Visit(selection, d, s, p);
}
static void
diff --git a/src/db/VHelper.cxx b/src/db/VHelper.cxx
new file mode 100644
index 000000000..f2bbc79a6
--- /dev/null
+++ b/src/db/VHelper.cxx
@@ -0,0 +1,133 @@
+/*
+ * Copyright 2003-2018 The Music Player Daemon Project
+ * http://www.musicpd.org
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License along
+ * with this program; if not, write to the Free Software Foundation, Inc.,
+ * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
+ */
+
+#include "config.h"
+#include "VHelper.hxx"
+#include "song/DetachedSong.hxx"
+#include "song/LightSong.hxx"
+#include "song/Filter.hxx"
+
+#include <assert.h>
+#include <string.h>
+
+DatabaseVisitorHelper::DatabaseVisitorHelper(const DatabaseSelection &_selection,
+ VisitSong &visit_song) noexcept
+ :selection(_selection)
+{
+ // TODO: apply URI and SongFilter
+ assert(selection.uri.empty());
+ assert(selection.filter == nullptr);
+
+ if (selection.sort != TAG_NUM_OF_ITEM_TYPES) {
+ /* the client has asked us to sort the result; this is
+ pretty expensive, because instead of streaming the
+ result to the client, we need to copy it all into
+ this std::vector, and then sort it */
+
+ original_visit_song = std::move(visit_song);
+ visit_song = [this](const auto &song){
+ songs.emplace_back(song);
+ };
+ } else if (selection.window != RangeArg::All()) {
+ original_visit_song = std::move(visit_song);
+ visit_song = [this](const auto &song){
+ if (selection.window.Contains(counter++))
+ original_visit_song(song);
+ };
+ }
+}
+
+DatabaseVisitorHelper::~DatabaseVisitorHelper() noexcept = default;
+
+gcc_pure
+static bool
+CompareNumeric(const char *a, const char *b) noexcept
+{
+ long a_value = strtol(a, nullptr, 10);
+ long b_value = strtol(b, nullptr, 10);
+
+ return a_value < b_value;
+}
+
+gcc_pure
+static bool
+CompareTags(TagType type, bool descending, const Tag &a, const Tag &b) noexcept
+{
+ const char *a_value = a.GetSortValue(type);
+ const char *b_value = b.GetSortValue(type);
+
+ if (descending) {
+ using std::swap;
+ swap(a_value, b_value);
+ }
+
+ switch (type) {
+ case TAG_DISC:
+ case TAG_TRACK:
+ return CompareNumeric(a_value, b_value);
+
+ default:
+ return strcmp(a_value, b_value) < 0;
+ }
+}
+
+void
+DatabaseVisitorHelper::Commit()
+{
+ /* only needed if sorting is enabled */
+ if (selection.sort == TAG_NUM_OF_ITEM_TYPES)
+ return;
+
+ assert(original_visit_song);
+
+ /* sort the song collection */
+ const auto sort = selection.sort;
+ const auto descending = selection.descending;
+
+ if (sort == TagType(SORT_TAG_LAST_MODIFIED))
+ std::stable_sort(songs.begin(), songs.end(),
+ [descending](const DetachedSong &a, const DetachedSong &b){
+ return descending
+ ? a.GetLastModified() > b.GetLastModified()
+ : a.GetLastModified() < b.GetLastModified();
+ });
+ else
+ std::stable_sort(songs.begin(), songs.end(),
+ [sort, descending](const DetachedSong &a,
+ const DetachedSong &b){
+ return CompareTags(sort, descending,
+ a.GetTag(),
+ b.GetTag());
+ });
+
+ /* apply the "window" */
+ if (selection.window.end < songs.size())
+ songs.erase(std::next(songs.begin(), selection.window.end),
+ songs.end());
+
+ if (selection.window.start >= songs.size())
+ return;
+
+ songs.erase(songs.begin(),
+ std::next(songs.begin(), selection.window.start));
+
+ /* now pass all songs to the original visitor callback */
+ for (const auto &song : songs)
+ original_visit_song((LightSong)song);
+}
diff --git a/src/db/VHelper.hxx b/src/db/VHelper.hxx
new file mode 100644
index 000000000..ed6bbc2ef
--- /dev/null
+++ b/src/db/VHelper.hxx
@@ -0,0 +1,70 @@
+/*
+ * Copyright 2003-2018 The Music Player Daemon Project
+ * http://www.musicpd.org
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License along
+ * with this program; if not, write to the Free Software Foundation, Inc.,
+ * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
+ */
+
+#ifndef MPD_DATABASE_VISITOR_HELPER_HXX
+#define MPD_DATABASE_VISITOR_HELPER_HXX
+
+#include "Visitor.hxx"
+#include "Selection.hxx"
+
+#include <vector>
+
+class DetachedSong;
+
+/**
+ * This class helps implementing Database::Visit() by emulating
+ * #DatabaseSelection features that the #Database implementation
+ * doesn't have, e.g. filtering, sorting and window.
+ *
+ * To use this class, construct it, passing unsupported features and
+ * the visitor callback to the constructor; before leaving Visit(),
+ * call Commit() (unless an error has occurred).
+ */
+class DatabaseVisitorHelper {
+ const DatabaseSelection selection;
+
+ /**
+ * If the plugin can't sort, then this container will collect
+ * all songs, sort them and report them to the visitor in
+ * Commit().
+ */
+ std::vector<DetachedSong> songs;
+
+ VisitSong original_visit_song;
+
+ /**
+ * Used to emulate the "window".
+ */
+ unsigned counter = 0;
+
+public:
+ /**
+ * @param selection a #DatabaseSelection instance with only
+ * features enabled which shall be emulated by this class
+ * @param visit_song the callback function passed to
+ * Database::Visit(); may be replaced by this class
+ */
+ DatabaseVisitorHelper(const DatabaseSelection &selection,
+ VisitSong &visit_song) noexcept;
+ ~DatabaseVisitorHelper() noexcept;
+
+ void Commit();
+};
+
+#endif
diff --git a/src/db/plugins/ProxyDatabasePlugin.cxx b/src/db/plugins/ProxyDatabasePlugin.cxx
index af1d19c56..daeaa8477 100644
--- a/src/db/plugins/ProxyDatabasePlugin.cxx
+++ b/src/db/plugins/ProxyDatabasePlugin.cxx
@@ -23,6 +23,7 @@
#include "db/DatabasePlugin.hxx"
#include "db/DatabaseListener.hxx"
#include "db/Selection.hxx"
+#include "db/VHelper.hxx"
#include "db/DatabaseError.hxx"
#include "db/PlaylistInfo.hxx"
#include "db/LightDirectory.hxx"
@@ -804,6 +805,15 @@ try {
throw;
}
+gcc_const
+static DatabaseSelection
+CheckSelection(DatabaseSelection selection) noexcept
+{
+ selection.uri.clear();
+ selection.filter = nullptr;
+ return selection;
+}
+
void
ProxyDatabase::Visit(const DatabaseSelection &selection,
VisitDirectory visit_directory,
@@ -813,11 +823,14 @@ ProxyDatabase::Visit(const DatabaseSelection &selection,
// TODO: eliminate the const_cast
const_cast<ProxyDatabase *>(this)->EnsureConnected();
+ DatabaseVisitorHelper helper(CheckSelection(selection), visit_song);
+
if (!visit_directory && !visit_playlist && selection.recursive &&
!selection.IsEmpty()) {
/* this optimized code path can only be used under
certain conditions */
::SearchSongs(connection, selection, visit_song);
+ helper.Commit();
return;
}
@@ -825,6 +838,8 @@ ProxyDatabase::Visit(const DatabaseSelection &selection,
::Visit(connection, selection.uri.c_str(),
selection.recursive, selection.filter,
visit_directory, visit_song, visit_playlist);
+
+ helper.Commit();
}
void
diff --git a/src/db/plugins/simple/SimpleDatabasePlugin.cxx b/src/db/plugins/simple/SimpleDatabasePlugin.cxx
index f4c766288..5fdc4ec80 100644
--- a/src/db/plugins/simple/SimpleDatabasePlugin.cxx
+++ b/src/db/plugins/simple/SimpleDatabasePlugin.cxx
@@ -26,6 +26,7 @@
#include "db/Helpers.hxx"
#include "db/Stats.hxx"
#include "db/UniqueTags.hxx"
+#include "db/VHelper.hxx"
#include "db/LightDirectory.hxx"
#include "Directory.hxx"
#include "Song.hxx"
@@ -265,6 +266,15 @@ SimpleDatabase::ReturnSong(gcc_unused const LightSong *song) const noexcept
}
}
+gcc_const
+static DatabaseSelection
+CheckSelection(DatabaseSelection selection) noexcept
+{
+ selection.uri.clear();
+ selection.filter = nullptr;
+ return selection;
+}
+
void
SimpleDatabase::Visit(const DatabaseSelection &selection,
VisitDirectory visit_directory,
@@ -286,6 +296,8 @@ SimpleDatabase::Visit(const DatabaseSelection &selection,
return;
}
+ DatabaseVisitorHelper helper(CheckSelection(selection), visit_song);
+
if (r.uri == nullptr) {
/* it's a directory */
@@ -295,6 +307,7 @@ SimpleDatabase::Visit(const DatabaseSelection &selection,
r.directory->Walk(selection.recursive, selection.filter,
visit_directory, visit_song,
visit_playlist);
+ helper.Commit();
return;
}
@@ -306,6 +319,7 @@ SimpleDatabase::Visit(const DatabaseSelection &selection,
if (selection.Match(song2))
visit_song(song2);
+ helper.Commit();
return;
}
}
diff --git a/src/db/plugins/upnp/UpnpDatabasePlugin.cxx b/src/db/plugins/upnp/UpnpDatabasePlugin.cxx
index 1f7732da7..9d6a64f04 100644
--- a/src/db/plugins/upnp/UpnpDatabasePlugin.cxx
+++ b/src/db/plugins/upnp/UpnpDatabasePlugin.cxx
@@ -27,6 +27,7 @@
#include "db/Interface.hxx"
#include "db/DatabasePlugin.hxx"
#include "db/Selection.hxx"
+#include "db/VHelper.hxx"
#include "db/DatabaseError.hxx"
#include "db/LightDirectory.hxx"
#include "song/LightSong.hxx"
@@ -576,6 +577,15 @@ UpnpDatabase::VisitServer(const ContentDirectoryService &server,
}
}
+gcc_const
+static DatabaseSelection
+CheckSelection(DatabaseSelection selection) noexcept
+{
+ selection.uri.clear();
+ selection.filter = nullptr;
+ return selection;
+}
+
// Deal with the possibly multiple servers, call VisitServer if needed.
void
UpnpDatabase::Visit(const DatabaseSelection &selection,
@@ -583,6 +593,8 @@ UpnpDatabase::Visit(const DatabaseSelection &selection,
VisitSong visit_song,
VisitPlaylist visit_playlist) const
{
+ DatabaseVisitorHelper helper(CheckSelection(selection), visit_song);
+
auto vpath = SplitString(selection.uri.c_str(), '/');
if (vpath.empty()) {
for (const auto &server : discovery->GetDirectories()) {
@@ -598,6 +610,7 @@ UpnpDatabase::Visit(const DatabaseSelection &selection,
visit_playlist);
}
+ helper.Commit();
return;
}
@@ -608,6 +621,7 @@ UpnpDatabase::Visit(const DatabaseSelection &selection,
auto server = discovery->GetServer(servername.c_str());
VisitServer(server, std::move(vpath), selection,
visit_directory, visit_song, visit_playlist);
+ helper.Commit();
}
void