diff --git a/NEWS b/NEWS index fdf301876..284dabd5a 100644 --- a/NEWS +++ b/NEWS @@ -50,6 +50,10 @@ ver 0.21 (not yet released) * use GTest instead of cppunit ver 0.20.22 (not yet released) +* protocol + - add tag fallbacks for AlbumArtistSort, ArtistSort + - "count group ..." can print an empty group + - fix broken command "list ... group" * storage - curl: URL-encode paths * Android diff --git a/doc/protocol.rst b/doc/protocol.rst index acc2d3c47..618e784ec 100644 --- a/doc/protocol.rst +++ b/doc/protocol.rst @@ -732,6 +732,10 @@ The music database count group artist count title Echoes group artist + A group with an empty value contains counts of matching song which + don't this group tag. It exists only if at least one such song is + found. + .. _command_find: :command:`find {FILTER} [sort {TYPE}] [window {START:END}]` diff --git a/python/build/libs.py b/python/build/libs.py index e4f621daf..025d933df 100644 --- a/python/build/libs.py +++ b/python/build/libs.py @@ -9,8 +9,8 @@ from build.ffmpeg import FfmpegProject from build.boost import BoostProject libmpdclient = MesonProject( - 'https://www.musicpd.org/download/libmpdclient/2/libmpdclient-2.14.tar.xz', - '0a84e2791bfe3077cf22ee1784c805d5bb550803dffe56a39aa3690a38061372', + 'https://www.musicpd.org/download/libmpdclient/2/libmpdclient-2.16.tar.xz', + 'fa6bdab67c0e0490302b38f00c27b4959735c3ec8aef7a88327adb1407654464', 'lib/libmpdclient.a', ) @@ -38,8 +38,8 @@ libvorbis = AutotoolsProject( ) opus = AutotoolsProject( - 'https://archive.mozilla.org/pub/opus/opus-1.2.1.tar.gz', - 'cfafd339ccd9c5ef8d6ab15d7e1a412c054bf4cb4ecbbbcc78c12ef2def70732', + 'https://archive.mozilla.org/pub/opus/opus-1.3.tar.gz', + '4f3d69aefdf2dbaf9825408e452a8a414ffc60494c70633560700398820dc550', 'lib/libopus.a', [ '--disable-shared', '--enable-static', @@ -341,8 +341,8 @@ ffmpeg = FfmpegProject( ) curl = AutotoolsProject( - 'http://curl.haxx.se/download/curl-7.61.0.tar.xz', - 'ef6e55192d04713673b4409ccbcb4cb6cd723137d6e10ca45b0c593a454e1720', + 'http://curl.haxx.se/download/curl-7.61.1.tar.xz', + '3d5913d6a39bd22e68e34dff697fd6e4c3c81563f580c76fca2009315cd81891', 'lib/libcurl.a', [ '--disable-shared', '--enable-static', diff --git a/src/command/DatabaseCommands.cxx b/src/command/DatabaseCommands.cxx index 09c2612b4..f95c5eb3f 100644 --- a/src/command/DatabaseCommands.cxx +++ b/src/command/DatabaseCommands.cxx @@ -267,7 +267,7 @@ handle_list(Client &client, Request args, Response &r) } std::unique_ptr filter; - TagMask group_mask = TagMask::None(); + TagType group = TAG_NUM_OF_ITEM_TYPES; if (args.size == 1) { /* for compatibility with < 0.12.0 */ @@ -282,18 +282,16 @@ handle_list(Client &client, Request args, Response &r) args.shift())); } - while (args.size >= 2 && - StringIsEqual(args[args.size - 2], "group")) { + if (args.size >= 2 && + StringIsEqual(args[args.size - 2], "group")) { const char *s = args[args.size - 1]; - TagType gt = tag_name_parse_i(s); - if (gt == TAG_NUM_OF_ITEM_TYPES) { + group = tag_name_parse_i(s); + if (group == TAG_NUM_OF_ITEM_TYPES) { r.FormatError(ACK_ERROR_ARG, "Unknown tag type: %s", s); return CommandResult::ERROR; } - group_mask |= gt; - args.pop_back(); args.pop_back(); } @@ -310,13 +308,13 @@ handle_list(Client &client, Request args, Response &r) filter->Optimize(); } - if (group_mask.Test(tagType)) { + if (tagType < TAG_NUM_OF_ITEM_TYPES && tagType == group) { r.Error(ACK_ERROR_ARG, "Conflicting group"); return CommandResult::ERROR; } PrintUniqueTags(r, client.GetPartition(), - tagType, group_mask, filter.get()); + tagType, group, filter.get()); return CommandResult::OK; } diff --git a/src/db/Count.cxx b/src/db/Count.cxx index 2e55821cb..a38f0934d 100644 --- a/src/db/Count.cxx +++ b/src/db/Count.cxx @@ -25,6 +25,7 @@ #include "client/Response.hxx" #include "song/LightSong.hxx" #include "tag/Tag.hxx" +#include "tag/VisitFallback.hxx" #include "TagPrint.hxx" #include @@ -73,24 +74,15 @@ stats_visitor_song(SearchStats &stats, const LightSong &song) noexcept stats.total_duration += duration; } -static bool -CollectGroupCounts(TagCountMap &map, TagType group, const Tag &tag) noexcept +static void +CollectGroupCounts(TagCountMap &map, const Tag &tag, + const char *value) noexcept { - bool found = false; - for (const auto &item : tag) { - if (item.type == group) { - auto r = map.insert(std::make_pair(item.value, - SearchStats())); - SearchStats &s = r.first->second; - ++s.n_songs; - if (!tag.duration.IsNegative()) - s.total_duration += tag.duration; - - found = true; - } - } - - return found; + auto r = map.insert(std::make_pair(value, SearchStats())); + SearchStats &s = r.first->second; + ++s.n_songs; + if (!tag.duration.IsNegative()) + s.total_duration += tag.duration; } static void @@ -98,9 +90,10 @@ GroupCountVisitor(TagCountMap &map, TagType group, const LightSong &song) noexcept { const Tag &tag = song.tag; - if (!CollectGroupCounts(map, group, tag) && group == TAG_ALBUM_ARTIST) - /* fall back to "Artist" if no "AlbumArtist" was found */ - CollectGroupCounts(map, TAG_ARTIST, tag); + VisitTagWithFallbackOrEmpty(tag, group, + std::bind(CollectGroupCounts, std::ref(map), + std::cref(tag), + std::placeholders::_1)); } void diff --git a/src/db/DatabasePrint.cxx b/src/db/DatabasePrint.cxx index 9699dc442..78f32f940 100644 --- a/src/db/DatabasePrint.cxx +++ b/src/db/DatabasePrint.cxx @@ -187,22 +187,34 @@ PrintSongUris(Response &r, Partition &partition, } static void -PrintUniqueTag(Response &r, TagType tag_type, - const Tag &tag) noexcept +PrintUniqueTags(Response &r, TagType tag_type, + const std::set &values) { - const char *value = tag.GetValue(tag_type); - assert(value != nullptr); - tag_print(r, tag_type, value); + const char *const name = tag_item_names[tag_type]; + for (const auto &i : values) + r.Format("%s: %s\n", name, i.c_str()); +} - const auto tag_mask = r.GetTagMask(); - for (const auto &item : tag) - if (item.type != tag_type && tag_mask.Test(item.type)) - tag_print(r, item.type, item.value); +static void +PrintGroupedUniqueTags(Response &r, TagType tag_type, TagType group, + const std::map> &groups) +{ + if (group == TAG_NUM_OF_ITEM_TYPES) { + for (const auto &i : groups) + PrintUniqueTags(r, tag_type, i.second); + return; + } + + const char *const group_name = tag_item_names[group]; + for (const auto &i : groups) { + r.Format("%s: %s\n", group_name, i.first.c_str()); + PrintUniqueTags(r, tag_type, i.second); + } } void PrintUniqueTags(Response &r, Partition &partition, - TagType type, TagMask group_mask, + TagType type, TagType group, const SongFilter *filter) { assert(type < TAG_NUM_OF_ITEM_TYPES); @@ -211,7 +223,6 @@ PrintUniqueTags(Response &r, Partition &partition, const DatabaseSelection selection("", true, filter); - using namespace std::placeholders; - const auto f = std::bind(PrintUniqueTag, std::ref(r), type, _1); - db.VisitUniqueTags(selection, type, group_mask, f); + PrintGroupedUniqueTags(r, type, group, + db.CollectUniqueTags(selection, type, group)); } diff --git a/src/db/DatabasePrint.hxx b/src/db/DatabasePrint.hxx index 85670686c..b485ad787 100644 --- a/src/db/DatabasePrint.hxx +++ b/src/db/DatabasePrint.hxx @@ -45,7 +45,7 @@ PrintSongUris(Response &r, Partition &partition, void PrintUniqueTags(Response &r, Partition &partition, - TagType type, TagMask group_mask, + TagType type, TagType group, const SongFilter *filter); #endif diff --git a/src/db/Interface.hxx b/src/db/Interface.hxx index 544162b9f..7b403d037 100644 --- a/src/db/Interface.hxx +++ b/src/db/Interface.hxx @@ -25,6 +25,9 @@ #include "util/Compiler.h" #include +#include +#include +#include struct DatabasePlugin; struct DatabaseStats; @@ -105,14 +108,9 @@ public: return Visit(selection, VisitDirectory(), visit_song); } - /** - * Visit all unique tag values. - * - * Throws on error. - */ - virtual void VisitUniqueTags(const DatabaseSelection &selection, - TagType tag_type, TagMask group_mask, - VisitTag visit_tag) const = 0; + virtual std::map> CollectUniqueTags(const DatabaseSelection &selection, + TagType tag_type, + TagType group=TAG_NUM_OF_ITEM_TYPES) const = 0; /** * Throws on error. diff --git a/src/db/UniqueTags.cxx b/src/db/UniqueTags.cxx index ad2ec2430..65b26e839 100644 --- a/src/db/UniqueTags.cxx +++ b/src/db/UniqueTags.cxx @@ -20,34 +20,42 @@ #include "UniqueTags.hxx" #include "Interface.hxx" #include "song/LightSong.hxx" -#include "tag/Set.hxx" -#include "tag/Mask.hxx" +#include "tag/VisitFallback.hxx" #include #include static void -CollectTags(TagSet &set, TagType tag_type, TagMask group_mask, - const LightSong &song) +CollectTags(std::set &result, + const Tag &tag, + TagType tag_type) noexcept { - const Tag &tag = song.tag; - - set.InsertUnique(tag, tag_type, group_mask); + VisitTagWithFallbackOrEmpty(tag, tag_type, [&result](const char *value){ + result.emplace(value); + }); } -void -VisitUniqueTags(const Database &db, const DatabaseSelection &selection, - TagType tag_type, TagMask group_mask, - VisitTag visit_tag) +static void +CollectGroupTags(std::map> &result, + const Tag &tag, + TagType tag_type, + TagType group) noexcept { - TagSet set; - - using namespace std::placeholders; - const auto f = std::bind(CollectTags, std::ref(set), - tag_type, group_mask, _1); - db.Visit(selection, f); - - for (const auto &value : set) - visit_tag(value); + VisitTagWithFallbackOrEmpty(tag, group, [&](const char *group_name){ + CollectTags(result[group_name], tag, tag_type); + }); +} + +std::map> +CollectUniqueTags(const Database &db, const DatabaseSelection &selection, + TagType tag_type, TagType group) +{ + std::map> result; + + db.Visit(selection, [&result, tag_type, group](const LightSong &song){ + CollectGroupTags(result, song.tag, tag_type, group); + }); + + return result; } diff --git a/src/db/UniqueTags.hxx b/src/db/UniqueTags.hxx index 0790acb3d..d646ab3c2 100644 --- a/src/db/UniqueTags.hxx +++ b/src/db/UniqueTags.hxx @@ -20,16 +20,20 @@ #ifndef MPD_DB_UNIQUE_TAGS_HXX #define MPD_DB_UNIQUE_TAGS_HXX -#include "Visitor.hxx" #include "tag/Type.h" +#include "util/Compiler.h" + +#include +#include +#include class TagMask; class Database; struct DatabaseSelection; -void -VisitUniqueTags(const Database &db, const DatabaseSelection &selection, - TagType tag_type, TagMask group_mask, - VisitTag visit_tag); +gcc_pure +std::map> +CollectUniqueTags(const Database &db, const DatabaseSelection &selection, + TagType tag_type, TagType group); #endif diff --git a/src/db/plugins/ProxyDatabasePlugin.cxx b/src/db/plugins/ProxyDatabasePlugin.cxx index b1a63a6a6..ca249147b 100644 --- a/src/db/plugins/ProxyDatabasePlugin.cxx +++ b/src/db/plugins/ProxyDatabasePlugin.cxx @@ -128,9 +128,9 @@ public: VisitSong visit_song, VisitPlaylist visit_playlist) const override; - void VisitUniqueTags(const DatabaseSelection &selection, - TagType tag_type, TagMask group_mask, - VisitTag visit_tag) const override; + std::map> CollectUniqueTags(const DatabaseSelection &selection, + TagType tag_type, + TagType group) const override; DatabaseStats GetStats(const DatabaseSelection &selection) const override; @@ -411,31 +411,21 @@ SendConstraints(mpd_connection *connection, const DatabaseSelection &selection) } static bool -SendGroupMask(mpd_connection *connection, TagMask mask) +SendGroup(mpd_connection *connection, TagType group) { + if (group == TAG_NUM_OF_ITEM_TYPES) + return true; + #if LIBMPDCLIENT_CHECK_VERSION(2,12,0) - for (unsigned i = 0; i < TAG_NUM_OF_ITEM_TYPES; ++i) { - const auto tag_type = TagType(i); - if (!mask.Test(tag_type)) - continue; + const auto tag = Convert(group); + if (tag == MPD_TAG_COUNT) + throw std::runtime_error("Unsupported tag"); - const auto tag = Convert(tag_type); - if (tag == MPD_TAG_COUNT) - throw std::runtime_error("Unsupported tag"); - - if (!mpd_search_add_group_tag(connection, tag)) - return false; - } - - return true; + return mpd_search_add_group_tag(connection, tag); #else (void)connection; - (void)mask; - if (mask.TestAny()) - throw std::runtime_error("Grouping requires libmpdclient 2.12"); - - return true; + throw std::runtime_error("Grouping requires libmpdclient 2.12"); #endif } @@ -992,11 +982,9 @@ ProxyDatabase::Visit(const DatabaseSelection &selection, helper.Commit(); } -void -ProxyDatabase::VisitUniqueTags(const DatabaseSelection &selection, - TagType tag_type, - TagMask group_mask, - VisitTag visit_tag) const +std::map> +ProxyDatabase::CollectUniqueTags(const DatabaseSelection &selection, + TagType tag_type, TagType group) const try { // TODO: eliminate the const_cast const_cast(this)->EnsureConnected(); @@ -1007,54 +995,56 @@ try { if (!mpd_search_db_tags(connection, tag_type2) || !SendConstraints(connection, selection) || - !SendGroupMask(connection, group_mask)) + !SendGroup(connection, group)) ThrowError(connection); if (!mpd_search_commit(connection)) ThrowError(connection); - TagBuilder builder; + std::map> result; - while (auto *pair = mpd_recv_pair(connection)) { - AtScopeExit(this, pair) { - mpd_return_pair(connection, pair); - }; + if (group == TAG_NUM_OF_ITEM_TYPES) { + auto &values = result[std::string()]; - const auto current_type = tag_name_parse_i(pair->name); - if (current_type == TAG_NUM_OF_ITEM_TYPES) - continue; + while (auto *pair = mpd_recv_pair(connection)) { + AtScopeExit(this, pair) { + mpd_return_pair(connection, pair); + }; - if (current_type == tag_type && !builder.empty()) { - try { - visit_tag(builder.Commit()); - } catch (...) { - mpd_response_finish(connection); - throw; - } + const auto current_type = tag_name_parse_i(pair->name); + if (current_type == TAG_NUM_OF_ITEM_TYPES) + continue; + + if (current_type == tag_type) + values.emplace(pair->value); } + } else { + std::set *current_group = nullptr; - builder.AddItem(current_type, pair->value); + while (auto *pair = mpd_recv_pair(connection)) { + AtScopeExit(this, pair) { + mpd_return_pair(connection, pair); + }; - if (!builder.HasType(current_type)) - /* if no tag item has been added, then the - given value was not acceptable - (e.g. empty); forcefully insert an empty - tag in this case, as the caller expects the - given tag type to be present */ - builder.AddEmptyItem(current_type); - } + const auto current_type = tag_name_parse_i(pair->name); + if (current_type == TAG_NUM_OF_ITEM_TYPES) + continue; - if (!builder.empty()) { - try { - visit_tag(builder.Commit()); - } catch (...) { - mpd_response_finish(connection); - throw; + if (current_type == tag_type) { + if (current_group == nullptr) + current_group = &result[std::string()]; + + current_group->emplace(pair->value); + } else if (current_type == group) { + current_group = &result[pair->value]; + } } } if (!mpd_response_finish(connection)) ThrowError(connection); + + return result; } catch (...) { if (connection != nullptr) mpd_search_cancel(connection); diff --git a/src/db/plugins/simple/SimpleDatabasePlugin.cxx b/src/db/plugins/simple/SimpleDatabasePlugin.cxx index 5fdc4ec80..daf75654c 100644 --- a/src/db/plugins/simple/SimpleDatabasePlugin.cxx +++ b/src/db/plugins/simple/SimpleDatabasePlugin.cxx @@ -329,12 +329,11 @@ SimpleDatabase::Visit(const DatabaseSelection &selection, "No such directory"); } -void -SimpleDatabase::VisitUniqueTags(const DatabaseSelection &selection, - TagType tag_type, TagMask group_mask, - VisitTag visit_tag) const +std::map> +SimpleDatabase::CollectUniqueTags(const DatabaseSelection &selection, + TagType tag_type, TagType group) const { - ::VisitUniqueTags(*this, selection, tag_type, group_mask, visit_tag); + return ::CollectUniqueTags(*this, selection, tag_type, group); } DatabaseStats diff --git a/src/db/plugins/simple/SimpleDatabasePlugin.hxx b/src/db/plugins/simple/SimpleDatabasePlugin.hxx index b95b7f5f6..be3055ed7 100644 --- a/src/db/plugins/simple/SimpleDatabasePlugin.hxx +++ b/src/db/plugins/simple/SimpleDatabasePlugin.hxx @@ -122,9 +122,9 @@ public: VisitSong visit_song, VisitPlaylist visit_playlist) const override; - void VisitUniqueTags(const DatabaseSelection &selection, - TagType tag_type, TagMask group_mask, - VisitTag visit_tag) const override; + std::map> CollectUniqueTags(const DatabaseSelection &selection, + TagType tag_type, + TagType group) const override; DatabaseStats GetStats(const DatabaseSelection &selection) const override; diff --git a/src/db/plugins/upnp/UpnpDatabasePlugin.cxx b/src/db/plugins/upnp/UpnpDatabasePlugin.cxx index 9d6a64f04..01ae971aa 100644 --- a/src/db/plugins/upnp/UpnpDatabasePlugin.cxx +++ b/src/db/plugins/upnp/UpnpDatabasePlugin.cxx @@ -97,9 +97,9 @@ public: VisitSong visit_song, VisitPlaylist visit_playlist) const override; - void VisitUniqueTags(const DatabaseSelection &selection, - TagType tag_type, TagMask group_mask, - VisitTag visit_tag) const override; + std::map> CollectUniqueTags(const DatabaseSelection &selection, + TagType tag_type, + TagType group) const override; DatabaseStats GetStats(const DatabaseSelection &selection) const override; @@ -624,17 +624,15 @@ UpnpDatabase::Visit(const DatabaseSelection &selection, helper.Commit(); } -void -UpnpDatabase::VisitUniqueTags(const DatabaseSelection &selection, - TagType tag, gcc_unused TagMask group_mask, - VisitTag visit_tag) const +std::map> +UpnpDatabase::CollectUniqueTags(const DatabaseSelection &selection, + TagType tag, TagType group) const { - // TODO: use group_mask + (void)group; // TODO: use group - if (!visit_tag) - return; + std::map> result; + auto &values = result[std::string()]; - std::set values; for (auto& server : discovery->GetDirectories()) { const auto dirbuf = SearchSongs(server, rootid, selection); @@ -650,11 +648,7 @@ UpnpDatabase::VisitUniqueTags(const DatabaseSelection &selection, } } - for (const auto& value : values) { - TagBuilder builder; - builder.AddItem(tag, value.c_str()); - visit_tag(builder.Commit()); - } + return result; } DatabaseStats diff --git a/src/song/TagSongFilter.cxx b/src/song/TagSongFilter.cxx index 35939ee8a..e77169ddc 100644 --- a/src/song/TagSongFilter.cxx +++ b/src/song/TagSongFilter.cxx @@ -21,6 +21,7 @@ #include "TagSongFilter.hxx" #include "LightSong.hxx" #include "tag/Tag.hxx" +#include "tag/Fallback.hxx" std::string TagSongFilter::ToExpression() const noexcept @@ -62,14 +63,23 @@ TagSongFilter::MatchNN(const Tag &tag) const noexcept if (filter.empty()) return true; - if (type == TAG_ALBUM_ARTIST && visited_types[TAG_ARTIST]) { - /* if we're looking for "album artist", but - only "artist" exists, use that */ - for (const auto &item : tag) - if (item.type == TAG_ARTIST && - filter.Match(item.value)) - return true; - } + bool result = false; + if (ApplyTagFallback(type, + [&](TagType tag2) { + if (!visited_types[tag2]) + return false; + + for (const auto &item : tag) { + if (item.type == tag2 && + filter.Match(item.value)) { + result = true; + break; + } + } + + return true; + })) + return result; } return false; diff --git a/src/tag/Builder.cxx b/src/tag/Builder.cxx index 1bcdb5bda..85a77131b 100644 --- a/src/tag/Builder.cxx +++ b/src/tag/Builder.cxx @@ -186,6 +186,19 @@ TagBuilder::Complement(const Tag &other) noexcept } } +void +TagBuilder::AddItemUnchecked(TagType type, StringView value) noexcept +{ + TagItem *i; + + { + const std::lock_guard protect(tag_pool_lock); + i = tag_pool_get_item(type, value); + } + + items.push_back(i); +} + inline void TagBuilder::AddItemInternal(TagType type, StringView value) noexcept { @@ -195,15 +208,9 @@ TagBuilder::AddItemInternal(TagType type, StringView value) noexcept if (!f.IsNull()) value = { f.data, f.size }; - TagItem *i; - { - const std::lock_guard protect(tag_pool_lock); - i = tag_pool_get_item(type, value); - } + AddItemUnchecked(type, value); free(f.data); - - items.push_back(i); } void @@ -229,13 +236,7 @@ TagBuilder::AddItem(TagType type, const char *value) noexcept void TagBuilder::AddEmptyItem(TagType type) noexcept { - TagItem *i; - { - const std::lock_guard protect(tag_pool_lock); - i = tag_pool_get_item(type, ""); - } - - items.push_back(i); + AddItemUnchecked(type, ""); } void diff --git a/src/tag/Builder.hxx b/src/tag/Builder.hxx index 136e0e745..dbf4cddb8 100644 --- a/src/tag/Builder.hxx +++ b/src/tag/Builder.hxx @@ -132,6 +132,12 @@ public: */ void Complement(const Tag &other) noexcept; + /** + * A variant of AddItem() which does not attempt to fix up the + * value and does not check whether the tag type is disabled. + */ + void AddItemUnchecked(TagType type, StringView value) noexcept; + /** * Appends a new tag item. * diff --git a/src/tag/Fallback.hxx b/src/tag/Fallback.hxx new file mode 100644 index 000000000..08e4dc80a --- /dev/null +++ b/src/tag/Fallback.hxx @@ -0,0 +1,53 @@ +/* + * 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_TAG_FALLBACK_HXX +#define MPD_TAG_FALLBACK_HXX + +#include + +template +bool +ApplyTagFallback(TagType type, F &&f) noexcept +{ + if (type == TAG_ALBUM_ARTIST_SORT) { + /* fall back to "AlbumArtist", "ArtistSort" and + "Artist" if no "AlbumArtistSort" was found */ + if (f(TAG_ALBUM_ARTIST)) + return true; + + return ApplyTagFallback(TAG_ARTIST_SORT, std::forward(f)); + } + + if (type == TAG_ALBUM_ARTIST || type == TAG_ARTIST_SORT) + /* fall back to "Artist" if no + "AlbumArtist"/"ArtistSort" was found */ + return f(TAG_ARTIST); + + return false; +} + +template +bool +ApplyTagWithFallback(TagType type, F &&f) noexcept +{ + return f(type) || ApplyTagFallback(type, std::forward(f)); +} + +#endif diff --git a/src/tag/Set.cxx b/src/tag/Set.cxx deleted file mode 100644 index 0009173bb..000000000 --- a/src/tag/Set.cxx +++ /dev/null @@ -1,114 +0,0 @@ -/* - * Copyright 2003-2017 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 "Set.hxx" -#include "Builder.hxx" -#include "Mask.hxx" -#include "Settings.hxx" - -#include - -/** - * Copy all tag items of the specified type. - */ -static bool -CopyTagItem(TagBuilder &dest, TagType dest_type, - const Tag &src, TagType src_type) -{ - bool found = false; - - for (const auto &item : src) { - if (item.type == src_type) { - dest.AddItem(dest_type, item.value); - found = true; - } - } - - return found; -} - -/** - * Copy all tag items of the specified type. Fall back to "Artist" if - * there is no "AlbumArtist". - */ -static void -CopyTagItem(TagBuilder &dest, const Tag &src, TagType type) -{ - if (!CopyTagItem(dest, type, src, type) && - type == TAG_ALBUM_ARTIST) - CopyTagItem(dest, type, src, TAG_ARTIST); -} - -/** - * Copy all tag items of the types in the mask. - */ -static void -CopyTagMask(TagBuilder &dest, const Tag &src, TagMask mask) -{ - for (unsigned i = 0; i < TAG_NUM_OF_ITEM_TYPES; ++i) - if (mask.Test(TagType(i))) - CopyTagItem(dest, src, TagType(i)); -} - -void -TagSet::InsertUnique(const Tag &src, TagType type, const char *value, - TagMask group_mask) noexcept -{ - TagBuilder builder; - if (value == nullptr) - builder.AddEmptyItem(type); - else - builder.AddItem(type, value); - CopyTagMask(builder, src, group_mask); - emplace(builder.Commit()); -} - -bool -TagSet::CheckUnique(TagType dest_type, - const Tag &tag, TagType src_type, - TagMask group_mask) noexcept -{ - bool found = false; - - for (const auto &item : tag) { - if (item.type == src_type) { - InsertUnique(tag, dest_type, item.value, group_mask); - found = true; - } - } - - return found; -} - -void -TagSet::InsertUnique(const Tag &tag, - TagType type, TagMask group_mask) noexcept -{ - static_assert(sizeof(group_mask) * 8 >= TAG_NUM_OF_ITEM_TYPES, - "Mask is too small"); - - assert(!group_mask.Test(type)); - - if (!CheckUnique(type, tag, type, group_mask) && - (type != TAG_ALBUM_ARTIST || - !IsTagEnabled(TAG_ALBUM_ARTIST) || - /* fall back to "Artist" if no "AlbumArtist" was found */ - !CheckUnique(type, tag, TAG_ARTIST, group_mask))) - InsertUnique(tag, type, nullptr, group_mask); -} diff --git a/src/tag/Set.hxx b/src/tag/Set.hxx deleted file mode 100644 index e74d145f4..000000000 --- a/src/tag/Set.hxx +++ /dev/null @@ -1,74 +0,0 @@ -/* - * Copyright 2003-2017 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_TAG_SET_HXX -#define MPD_TAG_SET_HXX - -#include "util/Compiler.h" -#include "Tag.hxx" - -#include - -#include - -class TagMask; - -/** - * Helper class for #TagSet which compares two #Tag objects. - */ -struct TagLess { - gcc_pure - bool operator()(const Tag &a, const Tag &b) const noexcept { - if (a.num_items != b.num_items) - return a.num_items < b.num_items; - - const unsigned n = a.num_items; - for (unsigned i = 0; i < n; ++i) { - const TagItem &ai = *a.items[i]; - const TagItem &bi = *b.items[i]; - if (ai.type != bi.type) - return unsigned(ai.type) < unsigned(bi.type); - - const int cmp = strcmp(ai.value, bi.value); - if (cmp != 0) - return cmp < 0; - } - - return false; - } -}; - -/** - * A set of #Tag objects. - */ -class TagSet : public std::set { -public: - void InsertUnique(const Tag &tag, - TagType type, TagMask group_mask) noexcept; - -private: - void InsertUnique(const Tag &src, TagType type, const char *value, - TagMask group_mask) noexcept; - - bool CheckUnique(TagType dest_type, - const Tag &tag, TagType src_type, - TagMask group_mask) noexcept; -}; - -#endif diff --git a/src/tag/VisitFallback.hxx b/src/tag/VisitFallback.hxx new file mode 100644 index 000000000..23992d44b --- /dev/null +++ b/src/tag/VisitFallback.hxx @@ -0,0 +1,60 @@ +/* + * 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_TAG_VISIT_FALLBACK_HXX +#define MPD_TAG_VISIT_FALLBACK_HXX + +#include "Fallback.hxx" +#include "Tag.hxx" + +template +bool +VisitTagType(const Tag &tag, TagType type, F &&f) noexcept +{ + bool found = false; + + for (const auto &item : tag) { + if (item.type == type) { + found = true; + f(item.value); + } + } + + return found; +} + +template +bool +VisitTagWithFallback(const Tag &tag, TagType type, F &&f) noexcept +{ + return ApplyTagWithFallback(type, + [&](TagType type2) { + return VisitTagType(tag, type2, f); + }); +} + +template +void +VisitTagWithFallbackOrEmpty(const Tag &tag, TagType type, F &&f) noexcept +{ + if (!VisitTagWithFallback(tag, type, f)) + f(""); +} + +#endif diff --git a/src/tag/meson.build b/src/tag/meson.build index b0121a71d..c423417d1 100644 --- a/src/tag/meson.build +++ b/src/tag/meson.build @@ -9,7 +9,6 @@ tag_sources = [ 'FixString.cxx', 'Pool.cxx', 'Table.cxx', - 'Set.cxx', 'Format.cxx', 'VorbisComment.cxx', 'ReplayGain.cxx',