From da563940b477955d78d4fb7352e0d879395fab7e Mon Sep 17 00:00:00 2001 From: Max Kellermann Date: Mon, 22 Oct 2018 08:32:32 +0200 Subject: [PATCH 01/13] python/build/libs.py: upgrade libmpdclient to 2.16 --- python/build/libs.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/python/build/libs.py b/python/build/libs.py index e4f621daf..81b6cd79d 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', ) From 850d208b7b3de08acdd7b8a09dce6595add9b6ea Mon Sep 17 00:00:00 2001 From: Max Kellermann Date: Mon, 22 Oct 2018 08:44:11 +0200 Subject: [PATCH 02/13] python/build/libs.py: upgrade Opus to 1.3 --- python/build/libs.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/python/build/libs.py b/python/build/libs.py index 81b6cd79d..eed00fdbe 100644 --- a/python/build/libs.py +++ b/python/build/libs.py @@ -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', From 3f3f0af543e33ede947bdb19809243064224f4d7 Mon Sep 17 00:00:00 2001 From: Max Kellermann Date: Mon, 22 Oct 2018 08:53:30 +0200 Subject: [PATCH 03/13] python/build/libs.py: upgrade CURL to 7.61.1 --- python/build/libs.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/python/build/libs.py b/python/build/libs.py index eed00fdbe..025d933df 100644 --- a/python/build/libs.py +++ b/python/build/libs.py @@ -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', From ff58b8d2551eb15810180e3fca0fac7f761c9d47 Mon Sep 17 00:00:00 2001 From: Max Kellermann Date: Mon, 22 Oct 2018 10:03:47 +0200 Subject: [PATCH 04/13] tag/Builder: move code to AddItemUnchecked() --- src/tag/TagBuilder.cxx | 22 ++++++++++++---------- src/tag/TagBuilder.hxx | 6 ++++++ 2 files changed, 18 insertions(+), 10 deletions(-) diff --git a/src/tag/TagBuilder.cxx b/src/tag/TagBuilder.cxx index 43cf3cc6f..e88fc6edd 100644 --- a/src/tag/TagBuilder.cxx +++ b/src/tag/TagBuilder.cxx @@ -188,6 +188,16 @@ TagBuilder::Complement(const Tag &other) tag_pool_lock.unlock(); } +void +TagBuilder::AddItemUnchecked(TagType type, StringView value) noexcept +{ + tag_pool_lock.lock(); + auto i = tag_pool_get_item(type, value); + tag_pool_lock.unlock(); + + items.push_back(i); +} + inline void TagBuilder::AddItemInternal(TagType type, StringView value) { @@ -197,13 +207,9 @@ TagBuilder::AddItemInternal(TagType type, StringView value) if (!f.IsNull()) value = { f.data, f.size }; - tag_pool_lock.lock(); - auto i = tag_pool_get_item(type, value); - tag_pool_lock.unlock(); + AddItemUnchecked(type, value); free(f.data); - - items.push_back(i); } void @@ -229,11 +235,7 @@ TagBuilder::AddItem(TagType type, const char *value) void TagBuilder::AddEmptyItem(TagType type) { - tag_pool_lock.lock(); - auto i = tag_pool_get_item(type, StringView::Empty()); - tag_pool_lock.unlock(); - - items.push_back(i); + AddItemUnchecked(type, StringView::Empty()); } void diff --git a/src/tag/TagBuilder.hxx b/src/tag/TagBuilder.hxx index b69d23689..82ed0227b 100644 --- a/src/tag/TagBuilder.hxx +++ b/src/tag/TagBuilder.hxx @@ -132,6 +132,12 @@ public: */ void Complement(const Tag &other); + /** + * 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. * From 4bc5333995528d2d9f4d8ad39f820a5b96fac178 Mon Sep 17 00:00:00 2001 From: Max Kellermann Date: Mon, 22 Oct 2018 10:06:04 +0200 Subject: [PATCH 05/13] tag/Set: use TagBuilder::AddItemUnchecked() This improves the workaround from commit b5ba94f1de06c621da937241eedfcfb100f26a09 and actually gives a useful result for "list" with a disabled tag. --- src/tag/Set.cxx | 9 +++------ 1 file changed, 3 insertions(+), 6 deletions(-) diff --git a/src/tag/Set.cxx b/src/tag/Set.cxx index 34e823c89..81e9eb9b6 100644 --- a/src/tag/Set.cxx +++ b/src/tag/Set.cxx @@ -20,6 +20,7 @@ #include "Set.hxx" #include "TagBuilder.hxx" #include "Settings.hxx" +#include "util/StringView.hxx" #include @@ -70,10 +71,7 @@ TagSet::InsertUnique(const Tag &src, TagType type, const char *value, tag_mask_t group_mask) noexcept { TagBuilder builder; - if (value == nullptr) - builder.AddEmptyItem(type); - else - builder.AddItem(type, value); + builder.AddItemUnchecked(type, value); CopyTagMask(builder, src, group_mask); #if CLANG_OR_GCC_VERSION(4,8) emplace(builder.Commit()); @@ -110,8 +108,7 @@ TagSet::InsertUnique(const Tag &tag, 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); + InsertUnique(tag, type, "", group_mask); } From 6b9966e9694aa304cff4ac46308c464ae830d586 Mon Sep 17 00:00:00 2001 From: Max Kellermann Date: Mon, 22 Oct 2018 10:09:56 +0200 Subject: [PATCH 06/13] tag/Set: include cleanup --- src/tag/Set.cxx | 1 - 1 file changed, 1 deletion(-) diff --git a/src/tag/Set.cxx b/src/tag/Set.cxx index 81e9eb9b6..fa4ecaf65 100644 --- a/src/tag/Set.cxx +++ b/src/tag/Set.cxx @@ -19,7 +19,6 @@ #include "Set.hxx" #include "TagBuilder.hxx" -#include "Settings.hxx" #include "util/StringView.hxx" #include From 94aed92e9ad3da465a0f9aa6a65ad1746061fc62 Mon Sep 17 00:00:00 2001 From: Max Kellermann Date: Mon, 22 Oct 2018 09:43:16 +0200 Subject: [PATCH 07/13] tag/Set: move code to ApplyTagWithFallback() --- Makefile.am | 1 + src/tag/Fallback.hxx | 43 +++++++++++++++++++++++++++++++++++++++++++ src/tag/Set.cxx | 22 +++++++++++++--------- 3 files changed, 57 insertions(+), 9 deletions(-) create mode 100644 src/tag/Fallback.hxx diff --git a/Makefile.am b/Makefile.am index ca800617a..f746d25aa 100644 --- a/Makefile.am +++ b/Makefile.am @@ -977,6 +977,7 @@ libtag_a_SOURCES =\ src/tag/TagItem.hxx \ src/tag/TagHandler.cxx src/tag/TagHandler.hxx \ src/tag/Mask.hxx \ + src/tag/Fallback.hxx \ src/tag/Settings.cxx src/tag/Settings.hxx \ src/tag/TagConfig.cxx src/tag/TagConfig.hxx \ src/tag/TagNames.c \ diff --git a/src/tag/Fallback.hxx b/src/tag/Fallback.hxx new file mode 100644 index 000000000..5c75e8bee --- /dev/null +++ b/src/tag/Fallback.hxx @@ -0,0 +1,43 @@ +/* + * 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) + /* fall back to "Artist" if no "AlbumArtist" 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 index fa4ecaf65..d52d517aa 100644 --- a/src/tag/Set.cxx +++ b/src/tag/Set.cxx @@ -18,17 +18,20 @@ */ #include "Set.hxx" +#include "Fallback.hxx" #include "TagBuilder.hxx" #include "util/StringView.hxx" +#include + #include /** * Copy all tag items of the specified type. */ static bool -CopyTagItem(TagBuilder &dest, TagType dest_type, - const Tag &src, TagType src_type) +CopyTagItem2(TagBuilder &dest, TagType dest_type, + const Tag &src, TagType src_type) { bool found = false; @@ -49,9 +52,9 @@ CopyTagItem(TagBuilder &dest, TagType dest_type, 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); + ApplyTagWithFallback(type, + std::bind(CopyTagItem2, std::ref(dest), type, + std::cref(src), std::placeholders::_1)); } /** @@ -105,9 +108,10 @@ TagSet::InsertUnique(const Tag &tag, assert((group_mask & (tag_mask_t(1) << unsigned(type))) == 0); - if (!CheckUnique(type, tag, type, group_mask) && - (type != TAG_ALBUM_ARTIST || - /* fall back to "Artist" if no "AlbumArtist" was found */ - !CheckUnique(type, tag, TAG_ARTIST, group_mask))) + if (!ApplyTagWithFallback(type, + std::bind(&TagSet::CheckUnique, this, + type, std::cref(tag), + std::placeholders::_1, + group_mask))) InsertUnique(tag, type, "", group_mask); } From 0340b01392a750a086b19abfdc47672308e0a2da Mon Sep 17 00:00:00 2001 From: Max Kellermann Date: Mon, 22 Oct 2018 10:44:36 +0200 Subject: [PATCH 08/13] db/Count: use ApplyTagFallback() --- src/db/Count.cxx | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/src/db/Count.cxx b/src/db/Count.cxx index 9c3945677..72f492ee3 100644 --- a/src/db/Count.cxx +++ b/src/db/Count.cxx @@ -25,6 +25,7 @@ #include "client/Response.hxx" #include "LightSong.hxx" #include "tag/Tag.hxx" +#include "tag/Fallback.hxx" #include #include @@ -98,9 +99,9 @@ GroupCountVisitor(TagCountMap &map, TagType group, const LightSong &song) assert(song.tag != nullptr); 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); + ApplyTagWithFallback(group, + std::bind(CollectGroupCounts, std::ref(map), + std::placeholders::_1, std::cref(tag))); } void From 21adc78713881793958cd7606f96b099a93e020e Mon Sep 17 00:00:00 2001 From: Max Kellermann Date: Mon, 22 Oct 2018 10:41:39 +0200 Subject: [PATCH 09/13] SongFilter: use ApplyTagFallback() --- src/SongFilter.cxx | 26 ++++++++++++++++++-------- 1 file changed, 18 insertions(+), 8 deletions(-) diff --git a/src/SongFilter.cxx b/src/SongFilter.cxx index 2e0d6751a..faf5deb2a 100644 --- a/src/SongFilter.cxx +++ b/src/SongFilter.cxx @@ -22,6 +22,7 @@ #include "db/LightSong.hxx" #include "DetachedSong.hxx" #include "tag/Tag.hxx" +#include "tag/Fallback.hxx" #include "util/ConstBuffer.hxx" #include "util/StringAPI.hxx" #include "util/StringCompare.hxx" @@ -118,14 +119,23 @@ SongFilter::Item::Match(const Tag &_tag) const noexcept if (value.empty()) return true; - if (tag == 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 && - StringMatch(item.value)) - return true; - } + bool result = false; + if (ApplyTagFallback(TagType(tag), + [&](TagType tag2) { + if (!visited_types[tag2]) + return false; + + for (const auto &item : _tag) { + if (item.type == tag2 && + StringMatch(item.value)) { + result = true; + break; + } + } + + return true; + })) + return result; } return false; From 53448e463383eca5633dc1d87e2004cae5dceaae Mon Sep 17 00:00:00 2001 From: Max Kellermann Date: Mon, 22 Oct 2018 10:52:42 +0200 Subject: [PATCH 10/13] tag/Fallback: add tag fallbacks for AlbumArtistSort, ArtistSort Just like AlbumArtist falls back to Artist, AlbumArtistSort should fall back tom AlbumArtist, ArtistSort and finally Artist. Closes #355 --- NEWS | 2 ++ src/tag/Fallback.hxx | 14 ++++++++++++-- 2 files changed, 14 insertions(+), 2 deletions(-) diff --git a/NEWS b/NEWS index 69adaf45e..9507057e7 100644 --- a/NEWS +++ b/NEWS @@ -1,4 +1,6 @@ ver 0.20.22 (not yet released) +* protocol + - add tag fallbacks for AlbumArtistSort, ArtistSort * storage - curl: URL-encode paths * Android diff --git a/src/tag/Fallback.hxx b/src/tag/Fallback.hxx index 5c75e8bee..08e4dc80a 100644 --- a/src/tag/Fallback.hxx +++ b/src/tag/Fallback.hxx @@ -26,8 +26,18 @@ template bool ApplyTagFallback(TagType type, F &&f) noexcept { - if (type == TAG_ALBUM_ARTIST) - /* fall back to "Artist" if no "AlbumArtist" was found */ + 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; From 6c06244e83a7accb8fece35d755ad69f7102cb4a Mon Sep 17 00:00:00 2001 From: Max Kellermann Date: Mon, 22 Oct 2018 11:50:51 +0200 Subject: [PATCH 11/13] db/Count: move code to tag/VisitCallback.hxx --- Makefile.am | 1 + src/db/Count.cxx | 32 +++++++++--------------- src/tag/VisitFallback.hxx | 52 +++++++++++++++++++++++++++++++++++++++ 3 files changed, 65 insertions(+), 20 deletions(-) create mode 100644 src/tag/VisitFallback.hxx diff --git a/Makefile.am b/Makefile.am index f746d25aa..1061d838d 100644 --- a/Makefile.am +++ b/Makefile.am @@ -978,6 +978,7 @@ libtag_a_SOURCES =\ src/tag/TagHandler.cxx src/tag/TagHandler.hxx \ src/tag/Mask.hxx \ src/tag/Fallback.hxx \ + src/tag/VisitFallback.hxx \ src/tag/Settings.cxx src/tag/Settings.hxx \ src/tag/TagConfig.cxx src/tag/TagConfig.hxx \ src/tag/TagNames.c \ diff --git a/src/db/Count.cxx b/src/db/Count.cxx index 72f492ee3..ba0d443e7 100644 --- a/src/db/Count.cxx +++ b/src/db/Count.cxx @@ -25,7 +25,7 @@ #include "client/Response.hxx" #include "LightSong.hxx" #include "tag/Tag.hxx" -#include "tag/Fallback.hxx" +#include "tag/VisitFallback.hxx" #include #include @@ -73,24 +73,15 @@ stats_visitor_song(SearchStats &stats, const LightSong &song) stats.total_duration += duration; } -static bool -CollectGroupCounts(TagCountMap &map, TagType group, const Tag &tag) +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 @@ -99,9 +90,10 @@ GroupCountVisitor(TagCountMap &map, TagType group, const LightSong &song) assert(song.tag != nullptr); const Tag &tag = *song.tag; - ApplyTagWithFallback(group, + VisitTagWithFallback(tag, group, std::bind(CollectGroupCounts, std::ref(map), - std::placeholders::_1, std::cref(tag))); + std::cref(tag), + std::placeholders::_1)); } void diff --git a/src/tag/VisitFallback.hxx b/src/tag/VisitFallback.hxx new file mode 100644 index 000000000..3ad486f5f --- /dev/null +++ b/src/tag/VisitFallback.hxx @@ -0,0 +1,52 @@ +/* + * 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); + }); +} + +#endif From 7cfe929c3692e74f4e6073960eaf0e08a9989811 Mon Sep 17 00:00:00 2001 From: Max Kellermann Date: Mon, 22 Oct 2018 12:42:18 +0200 Subject: [PATCH 12/13] db/Count: print empty group if song without grouped tag exists Be consistent with "list" responses. --- NEWS | 1 + doc/protocol.xml | 5 +++++ src/db/Count.cxx | 8 ++++---- src/tag/VisitFallback.hxx | 8 ++++++++ 4 files changed, 18 insertions(+), 4 deletions(-) diff --git a/NEWS b/NEWS index 9507057e7..5b8a3b48c 100644 --- a/NEWS +++ b/NEWS @@ -1,6 +1,7 @@ ver 0.20.22 (not yet released) * protocol - add tag fallbacks for AlbumArtistSort, ArtistSort + - "count group ..." can print an empty group * storage - curl: URL-encode paths * Android diff --git a/doc/protocol.xml b/doc/protocol.xml index 4b9a4c95a..75cdb5478 100644 --- a/doc/protocol.xml +++ b/doc/protocol.xml @@ -1599,6 +1599,11 @@ OK per-artist counts: count 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. + diff --git a/src/db/Count.cxx b/src/db/Count.cxx index ba0d443e7..dccb3e8c7 100644 --- a/src/db/Count.cxx +++ b/src/db/Count.cxx @@ -90,10 +90,10 @@ GroupCountVisitor(TagCountMap &map, TagType group, const LightSong &song) assert(song.tag != nullptr); const Tag &tag = *song.tag; - VisitTagWithFallback(tag, group, - std::bind(CollectGroupCounts, std::ref(map), - std::cref(tag), - std::placeholders::_1)); + VisitTagWithFallbackOrEmpty(tag, group, + std::bind(CollectGroupCounts, std::ref(map), + std::cref(tag), + std::placeholders::_1)); } void diff --git a/src/tag/VisitFallback.hxx b/src/tag/VisitFallback.hxx index 3ad486f5f..23992d44b 100644 --- a/src/tag/VisitFallback.hxx +++ b/src/tag/VisitFallback.hxx @@ -49,4 +49,12 @@ VisitTagWithFallback(const Tag &tag, TagType type, F &&f) noexcept }); } +template +void +VisitTagWithFallbackOrEmpty(const Tag &tag, TagType type, F &&f) noexcept +{ + if (!VisitTagWithFallback(tag, type, f)) + f(""); +} + #endif From db27bb76e280c69d70bc14f0b0161e89d496a3c8 Mon Sep 17 00:00:00 2001 From: Max Kellermann Date: Mon, 22 Oct 2018 11:35:22 +0200 Subject: [PATCH 13/13] db: fix broken command "list ... group" Grouping in the "list" command was completely broken from the start, unlike "count group". I have no idea what I have been thinking when I wrote commit ae178c77bdc47c954fd9a4b32ffc07fe6c4a8a49, but it didn't make any sense. This commit is a rewrite of the feature. For clients to be able to detect this feature, this commit also increments the protocol version. --- Makefile.am | 1 - NEWS | 1 + configure.ac | 2 +- src/command/DatabaseCommands.cxx | 17 ++- src/db/DatabasePrint.cxx | 41 +++--- src/db/DatabasePrint.hxx | 4 +- src/db/Interface.hxx | 14 +-- src/db/UniqueTags.cxx | 48 ++++--- src/db/UniqueTags.hxx | 15 ++- src/db/plugins/ProxyDatabasePlugin.cxx | 103 +++++++-------- .../plugins/simple/SimpleDatabasePlugin.cxx | 9 +- .../plugins/simple/SimpleDatabasePlugin.hxx | 6 +- src/db/plugins/upnp/UpnpDatabasePlugin.cxx | 26 ++-- src/tag/Set.cxx | 117 ------------------ src/tag/Set.hxx | 73 ----------- 15 files changed, 145 insertions(+), 332 deletions(-) delete mode 100644 src/tag/Set.cxx delete mode 100644 src/tag/Set.hxx diff --git a/Makefile.am b/Makefile.am index 1061d838d..50b98da76 100644 --- a/Makefile.am +++ b/Makefile.am @@ -985,7 +985,6 @@ libtag_a_SOURCES =\ src/tag/TagString.cxx src/tag/TagString.hxx \ src/tag/TagPool.cxx src/tag/TagPool.hxx \ src/tag/TagTable.cxx src/tag/TagTable.hxx \ - src/tag/Set.cxx src/tag/Set.hxx \ src/tag/Format.cxx src/tag/Format.hxx \ src/tag/VorbisComment.cxx src/tag/VorbisComment.hxx \ src/tag/ReplayGain.cxx src/tag/ReplayGain.hxx \ diff --git a/NEWS b/NEWS index 5b8a3b48c..8b0029784 100644 --- a/NEWS +++ b/NEWS @@ -2,6 +2,7 @@ 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/configure.ac b/configure.ac index 4e9f8b460..687823dc4 100644 --- a/configure.ac +++ b/configure.ac @@ -14,7 +14,7 @@ AM_SILENT_RULES AC_CONFIG_HEADERS(config.h) AC_CONFIG_MACRO_DIR([m4]) -AC_DEFINE(PROTOCOL_VERSION, "0.20.0", [The MPD protocol version]) +AC_DEFINE(PROTOCOL_VERSION, "0.20.22", [The MPD protocol version]) GIT_COMMIT=`cd "$srcdir" && git describe --dirty --always 2>/dev/null` if test x$GIT_COMMIT != x; then diff --git a/src/command/DatabaseCommands.cxx b/src/command/DatabaseCommands.cxx index 42f79b8e2..3f7d18324 100644 --- a/src/command/DatabaseCommands.cxx +++ b/src/command/DatabaseCommands.cxx @@ -191,7 +191,7 @@ handle_list(Client &client, Request args, Response &r) } std::unique_ptr filter; - tag_mask_t group_mask = 0; + TagType group = TAG_NUM_OF_ITEM_TYPES; if (args.size == 1) { /* for compatibility with < 0.12.0 */ @@ -206,18 +206,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 |= tag_mask_t(1) << unsigned(gt); - args.pop_back(); args.pop_back(); } @@ -230,14 +228,13 @@ handle_list(Client &client, Request args, Response &r) } } - if (tagType < TAG_NUM_OF_ITEM_TYPES && - group_mask & (tag_mask_t(1) << tagType)) { + if (tagType < TAG_NUM_OF_ITEM_TYPES && tagType == group) { r.Error(ACK_ERROR_ARG, "Conflicting group"); return CommandResult::ERROR; } PrintUniqueTags(r, client.partition, - tagType, group_mask, filter.get()); + tagType, group, filter.get()); return CommandResult::OK; } diff --git a/src/db/DatabasePrint.cxx b/src/db/DatabasePrint.cxx index 87d441d20..d4d386912 100644 --- a/src/db/DatabasePrint.cxx +++ b/src/db/DatabasePrint.cxx @@ -187,22 +187,34 @@ PrintSongURIVisitor(Response &r, Partition &partition, const LightSong &song) } static void -PrintUniqueTag(Response &r, TagType tag_type, - const Tag &tag) +PrintUniqueTags(Response &r, TagType tag_type, + const std::set &values) { - const char *value = tag.GetValue(tag_type); - assert(value != nullptr); - r.Format("%s: %s\n", tag_item_names[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()); +} - for (const auto &item : tag) - if (item.type != tag_type) - r.Format("%s: %s\n", - tag_item_names[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, - unsigned type, tag_mask_t group_mask, + unsigned type, TagType group, const SongFilter *filter) { const Database &db = partition.GetDatabaseOrThrow(); @@ -217,10 +229,9 @@ PrintUniqueTags(Response &r, Partition &partition, } else { assert(type < TAG_NUM_OF_ITEM_TYPES); - using namespace std::placeholders; - const auto f = std::bind(PrintUniqueTag, std::ref(r), - (TagType)type, _1); - db.VisitUniqueTags(selection, (TagType)type, - group_mask, f); + PrintGroupedUniqueTags(r, TagType(type), group, + db.CollectUniqueTags(selection, + TagType(type), + group)); } } diff --git a/src/db/DatabasePrint.hxx b/src/db/DatabasePrint.hxx index 51b9a92cd..bbb52b101 100644 --- a/src/db/DatabasePrint.hxx +++ b/src/db/DatabasePrint.hxx @@ -20,7 +20,7 @@ #ifndef MPD_DB_PRINT_H #define MPD_DB_PRINT_H -#include "tag/Mask.hxx" +#include "tag/TagType.h" class SongFilter; struct DatabaseSelection; @@ -44,7 +44,7 @@ db_selection_print(Response &r, Partition &partition, void PrintUniqueTags(Response &r, Partition &partition, - unsigned type, tag_mask_t group_mask, + unsigned type, TagType group, const SongFilter *filter); #endif diff --git a/src/db/Interface.hxx b/src/db/Interface.hxx index a61c323d3..656dfaa76 100644 --- a/src/db/Interface.hxx +++ b/src/db/Interface.hxx @@ -22,9 +22,12 @@ #include "Visitor.hxx" #include "tag/TagType.h" -#include "tag/Mask.hxx" #include "Compiler.h" +#include +#include +#include + #include struct DatabasePlugin; @@ -99,12 +102,9 @@ public: return Visit(selection, VisitDirectory(), visit_song); } - /** - * Visit all unique tag values. - */ - virtual void VisitUniqueTags(const DatabaseSelection &selection, - TagType tag_type, tag_mask_t 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; virtual DatabaseStats GetStats(const DatabaseSelection &selection) const = 0; diff --git a/src/db/UniqueTags.cxx b/src/db/UniqueTags.cxx index c888fe7b4..4e33f5df6 100644 --- a/src/db/UniqueTags.cxx +++ b/src/db/UniqueTags.cxx @@ -20,34 +20,42 @@ #include "UniqueTags.hxx" #include "Interface.hxx" #include "LightSong.hxx" -#include "tag/Set.hxx" +#include "tag/VisitFallback.hxx" #include #include static void -CollectTags(TagSet &set, TagType tag_type, tag_mask_t group_mask, - const LightSong &song) +CollectTags(std::set &result, + const Tag &tag, + TagType tag_type) noexcept { - assert(song.tag != nullptr); - 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, tag_mask_t 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 efb2a089d..83e6e7f09 100644 --- a/src/db/UniqueTags.hxx +++ b/src/db/UniqueTags.hxx @@ -20,16 +20,19 @@ #ifndef MPD_DB_UNIQUE_TAGS_HXX #define MPD_DB_UNIQUE_TAGS_HXX -#include "Visitor.hxx" #include "tag/TagType.h" -#include "tag/Mask.hxx" +#include "Compiler.h" + +#include +#include +#include class Database; struct DatabaseSelection; -void -VisitUniqueTags(const Database &db, const DatabaseSelection &selection, - TagType tag_type, tag_mask_t 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 b3d2d0a05..d6954968f 100644 --- a/src/db/plugins/ProxyDatabasePlugin.cxx +++ b/src/db/plugins/ProxyDatabasePlugin.cxx @@ -120,9 +120,9 @@ public: VisitSong visit_song, VisitPlaylist visit_playlist) const override; - void VisitUniqueTags(const DatabaseSelection &selection, - TagType tag_type, tag_mask_t 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; @@ -334,28 +334,19 @@ SendConstraints(mpd_connection *connection, const DatabaseSelection &selection) } static bool -SendGroupMask(mpd_connection *connection, tag_mask_t 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) { - if ((mask & (tag_mask_t(1) << i)) == 0) - continue; + const auto tag = Convert(group); + if (tag == MPD_TAG_COUNT) + throw std::runtime_error("Unsupported tag"); - const auto tag = Convert(TagType(i)); - 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 != 0) - throw std::runtime_error("Grouping requires libmpdclient 2.12"); return true; #endif @@ -799,11 +790,9 @@ ProxyDatabase::Visit(const DatabaseSelection &selection, visit_directory, visit_song, visit_playlist); } -void -ProxyDatabase::VisitUniqueTags(const DatabaseSelection &selection, - TagType tag_type, - tag_mask_t 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(); @@ -814,54 +803,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.IsEmpty()) { - 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.IsEmpty()) { - 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 a27c070c6..1e5bf8f7e 100644 --- a/src/db/plugins/simple/SimpleDatabasePlugin.cxx +++ b/src/db/plugins/simple/SimpleDatabasePlugin.cxx @@ -312,12 +312,11 @@ SimpleDatabase::Visit(const DatabaseSelection &selection, "No such directory"); } -void -SimpleDatabase::VisitUniqueTags(const DatabaseSelection &selection, - TagType tag_type, tag_mask_t 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 4336fb58b..4c58c73d3 100644 --- a/src/db/plugins/simple/SimpleDatabasePlugin.hxx +++ b/src/db/plugins/simple/SimpleDatabasePlugin.hxx @@ -119,9 +119,9 @@ public: VisitSong visit_song, VisitPlaylist visit_playlist) const override; - void VisitUniqueTags(const DatabaseSelection &selection, - TagType tag_type, tag_mask_t 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 c49d8a725..81548f98e 100644 --- a/src/db/plugins/upnp/UpnpDatabasePlugin.cxx +++ b/src/db/plugins/upnp/UpnpDatabasePlugin.cxx @@ -87,9 +87,9 @@ public: VisitSong visit_song, VisitPlaylist visit_playlist) const override; - void VisitUniqueTags(const DatabaseSelection &selection, - TagType tag_type, tag_mask_t 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; @@ -603,17 +603,15 @@ UpnpDatabase::Visit(const DatabaseSelection &selection, visit_directory, visit_song, visit_playlist); } -void -UpnpDatabase::VisitUniqueTags(const DatabaseSelection &selection, - TagType tag, gcc_unused tag_mask_t 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); @@ -633,11 +631,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/tag/Set.cxx b/src/tag/Set.cxx deleted file mode 100644 index d52d517aa..000000000 --- a/src/tag/Set.cxx +++ /dev/null @@ -1,117 +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 "Fallback.hxx" -#include "TagBuilder.hxx" -#include "util/StringView.hxx" - -#include - -#include - -/** - * Copy all tag items of the specified type. - */ -static bool -CopyTagItem2(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) -{ - ApplyTagWithFallback(type, - std::bind(CopyTagItem2, std::ref(dest), type, - std::cref(src), std::placeholders::_1)); -} - -/** - * Copy all tag items of the types in the mask. - */ -static void -CopyTagMask(TagBuilder &dest, const Tag &src, tag_mask_t mask) -{ - for (unsigned i = 0; i < TAG_NUM_OF_ITEM_TYPES; ++i) - if ((mask & (tag_mask_t(1) << i)) != 0) - CopyTagItem(dest, src, TagType(i)); -} - -void -TagSet::InsertUnique(const Tag &src, TagType type, const char *value, - tag_mask_t group_mask) noexcept -{ - TagBuilder builder; - builder.AddItemUnchecked(type, value); - CopyTagMask(builder, src, group_mask); -#if CLANG_OR_GCC_VERSION(4,8) - emplace(builder.Commit()); -#else - insert(builder.Commit()); -#endif -} - -bool -TagSet::CheckUnique(TagType dest_type, - const Tag &tag, TagType src_type, - tag_mask_t 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, tag_mask_t group_mask) noexcept -{ - static_assert(sizeof(group_mask) * 8 >= TAG_NUM_OF_ITEM_TYPES, - "Mask is too small"); - - assert((group_mask & (tag_mask_t(1) << unsigned(type))) == 0); - - if (!ApplyTagWithFallback(type, - std::bind(&TagSet::CheckUnique, this, - type, std::cref(tag), - std::placeholders::_1, - group_mask))) - InsertUnique(tag, type, "", group_mask); -} diff --git a/src/tag/Set.hxx b/src/tag/Set.hxx deleted file mode 100644 index 8e19b7ca2..000000000 --- a/src/tag/Set.hxx +++ /dev/null @@ -1,73 +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 "Compiler.h" -#include "Tag.hxx" -#include "Mask.hxx" - -#include - -#include - -/** - * 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, tag_mask_t group_mask) noexcept; - -private: - void InsertUnique(const Tag &src, TagType type, const char *value, - tag_mask_t group_mask) noexcept; - - bool CheckUnique(TagType dest_type, - const Tag &tag, TagType src_type, - tag_mask_t group_mask) noexcept; -}; - -#endif