diff --git a/Makefile.am b/Makefile.am index 18cd535ba..7688f3096 100644 --- a/Makefile.am +++ b/Makefile.am @@ -1039,6 +1039,7 @@ libsong_a_SOURCES = \ src/song/AudioFormatSongFilter.cxx src/song/AudioFormatSongFilter.hxx \ src/song/AndSongFilter.cxx src/song/AndSongFilter.hxx \ src/song/NotSongFilter.hxx \ + src/song/OptimizeFilter.cxx src/song/OptimizeFilter.hxx \ src/song/Filter.cxx src/song/Filter.hxx \ src/song/LightSong.cxx src/song/LightSong.hxx diff --git a/src/command/DatabaseCommands.cxx b/src/command/DatabaseCommands.cxx index 07ac81edb..0d0b8448d 100644 --- a/src/command/DatabaseCommands.cxx +++ b/src/command/DatabaseCommands.cxx @@ -105,6 +105,7 @@ handle_match(Client &client, Request args, Response &r, bool fold_case) GetFullMessage(std::current_exception()).c_str()); return CommandResult::ERROR; } + filter.Optimize(); const DatabaseSelection selection("", true, &filter); @@ -138,6 +139,7 @@ handle_match_add(Client &client, Request args, Response &r, bool fold_case) GetFullMessage(std::current_exception()).c_str()); return CommandResult::ERROR; } + filter.Optimize(); auto &partition = client.GetPartition(); const ScopeBulkEdit bulk_edit(partition); @@ -172,6 +174,7 @@ handle_searchaddpl(Client &client, Request args, Response &r) GetFullMessage(std::current_exception()).c_str()); return CommandResult::ERROR; } + filter.Optimize(); const Database &db = client.GetDatabaseOrThrow(); @@ -206,6 +209,8 @@ handle_count(Client &client, Request args, Response &r) GetFullMessage(std::current_exception()).c_str()); return CommandResult::ERROR; } + + filter.Optimize(); } PrintSongCount(r, client.GetPartition(), "", &filter, group); @@ -238,6 +243,7 @@ handle_list_file(Client &client, Request args, Response &r) GetFullMessage(std::current_exception()).c_str()); return CommandResult::ERROR; } + filter->Optimize(); } PrintSongUris(r, client.GetPartition(), filter.get()); @@ -300,6 +306,7 @@ handle_list(Client &client, Request args, Response &r) GetFullMessage(std::current_exception()).c_str()); return CommandResult::ERROR; } + filter->Optimize(); } if (group_mask.Test(tagType)) { diff --git a/src/command/QueueCommands.cxx b/src/command/QueueCommands.cxx index 55abcc268..c040033a2 100644 --- a/src/command/QueueCommands.cxx +++ b/src/command/QueueCommands.cxx @@ -272,6 +272,7 @@ handle_playlist_match(Client &client, Request args, Response &r, GetFullMessage(std::current_exception()).c_str()); return CommandResult::ERROR; } + filter.Optimize(); playlist_print_find(r, client.GetPlaylist(), filter); return CommandResult::OK; diff --git a/src/song/AndSongFilter.hxx b/src/song/AndSongFilter.hxx index 5c49a98c1..5c3869d93 100644 --- a/src/song/AndSongFilter.hxx +++ b/src/song/AndSongFilter.hxx @@ -31,6 +31,9 @@ class AndSongFilter final : public ISongFilter { std::list items; + friend void OptimizeSongFilter(AndSongFilter &) noexcept; + friend ISongFilterPtr OptimizeSongFilter(ISongFilterPtr) noexcept; + public: const auto &GetItems() const noexcept { return items; diff --git a/src/song/Filter.cxx b/src/song/Filter.cxx index c96ecb8dc..df943b8d6 100644 --- a/src/song/Filter.cxx +++ b/src/song/Filter.cxx @@ -352,6 +352,12 @@ SongFilter::Parse(ConstBuffer args, bool fold_case) } while (!args.empty()); } +void +SongFilter::Optimize() noexcept +{ + OptimizeSongFilter(and_filter); +} + bool SongFilter::Match(const LightSong &song) const noexcept { diff --git a/src/song/Filter.hxx b/src/song/Filter.hxx index e22d96986..5164ee888 100644 --- a/src/song/Filter.hxx +++ b/src/song/Filter.hxx @@ -68,6 +68,8 @@ public: */ void Parse(ConstBuffer args, bool fold_case=false); + void Optimize() noexcept; + gcc_pure bool Match(const LightSong &song) const noexcept; diff --git a/src/song/NotSongFilter.hxx b/src/song/NotSongFilter.hxx index 000ac1f69..94e30dca0 100644 --- a/src/song/NotSongFilter.hxx +++ b/src/song/NotSongFilter.hxx @@ -28,6 +28,8 @@ class NotSongFilter final : public ISongFilter { ISongFilterPtr child; + friend ISongFilterPtr OptimizeSongFilter(ISongFilterPtr) noexcept; + public: template explicit NotSongFilter(C &&_child) noexcept diff --git a/src/song/OptimizeFilter.cxx b/src/song/OptimizeFilter.cxx new file mode 100644 index 000000000..471045b76 --- /dev/null +++ b/src/song/OptimizeFilter.cxx @@ -0,0 +1,73 @@ +/* + * 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 "OptimizeFilter.hxx" +#include "AndSongFilter.hxx" +#include "NotSongFilter.hxx" +#include "TagSongFilter.hxx" +#include "UriSongFilter.hxx" + +void +OptimizeSongFilter(AndSongFilter &af) noexcept +{ + for (auto i = af.items.begin(); i != af.items.end();) { + auto f = OptimizeSongFilter(std::move(*i)); + if (auto *nested = dynamic_cast(f.get())) { + /* collapse nested #AndSongFilter instances */ + af.items.splice(i, std::move(nested->items)); + i = af.items.erase(i); + } else { + *i = std::move(f); + ++i; + } + } +} + +ISongFilterPtr +OptimizeSongFilter(ISongFilterPtr f) noexcept +{ + if (auto *af = dynamic_cast(f.get())) { + /* first optimize all items */ + OptimizeSongFilter(*af); + + if (!af->items.empty() && + std::next(af->items.begin()) == af->items.end()) + /* only one item: the containing + #AndSongFilter can be removed */ + return std::move(af->items.front()); + } else if (auto *nf = dynamic_cast(f.get())) { + auto child = OptimizeSongFilter(std::move(nf->child)); + if (auto *tf = dynamic_cast(child.get())) { + /* #TagSongFilter has its own "negated" flag, + so we can drop the #NotSongFilter + container */ + tf->negated = !tf->negated; + return child; + } else if (auto *uf = dynamic_cast(child.get())) { + /* same for #UriSongFilter */ + uf->negated = !uf->negated; + return child; + } + + nf->child = std::move(child); + } + + return f; +} diff --git a/src/song/OptimizeFilter.hxx b/src/song/OptimizeFilter.hxx new file mode 100644 index 000000000..754b5943a --- /dev/null +++ b/src/song/OptimizeFilter.hxx @@ -0,0 +1,33 @@ +/* + * 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_SONG_OPTIMIZE_FILTER_HXX +#define MPD_SONG_OPTIMIZE_FILTER_HXX + +#include "ISongFilter.hxx" + +class AndSongFilter; + +void +OptimizeSongFilter(AndSongFilter &af) noexcept; + +ISongFilterPtr +OptimizeSongFilter(ISongFilterPtr f) noexcept; + +#endif diff --git a/src/song/TagSongFilter.hxx b/src/song/TagSongFilter.hxx index 527339a3a..cf880ec50 100644 --- a/src/song/TagSongFilter.hxx +++ b/src/song/TagSongFilter.hxx @@ -38,6 +38,8 @@ class TagSongFilter final : public ISongFilter { StringFilter filter; + friend ISongFilterPtr OptimizeSongFilter(ISongFilterPtr) noexcept; + public: template TagSongFilter(TagType _type, V &&_value, bool fold_case, bool _negated) diff --git a/src/song/UriSongFilter.hxx b/src/song/UriSongFilter.hxx index 4c16ee19f..8d37e48f1 100644 --- a/src/song/UriSongFilter.hxx +++ b/src/song/UriSongFilter.hxx @@ -28,6 +28,8 @@ class UriSongFilter final : public ISongFilter { bool negated; + friend ISongFilterPtr OptimizeSongFilter(ISongFilterPtr) noexcept; + public: template UriSongFilter(V &&_value, bool fold_case, bool _negated) diff --git a/test/ParseSongFilter.cxx b/test/ParseSongFilter.cxx index 7f368a03a..f2d248608 100644 --- a/test/ParseSongFilter.cxx +++ b/test/ParseSongFilter.cxx @@ -41,6 +41,7 @@ try { SongFilter filter; filter.Parse(ConstBuffer(argv + 1, argc - 1)); + filter.Optimize(); puts(filter.ToExpression().c_str()); return EXIT_SUCCESS;